From b11a7eefa3947203f56b6ddf3db3afe135acad9e Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sat, 20 Aug 2022 13:00:58 -0400 Subject: [PATCH] Enable excludes (#18) --- Cargo.lock | 1 - Cargo.toml | 1 - README.md | 61 ++++++++++++++++++++++++++++--- resources/test/src/excluded.py | 9 +++++ resources/test/src/pyproject.toml | 1 + src/bin/rust_python_linter.rs | 6 +++ src/fs.rs | 55 ---------------------------- src/linter.rs | 25 ++++++++++--- src/pyproject.rs | 33 ++++++++++++++--- src/settings.rs | 31 ++++++++++------ 10 files changed, 140 insertions(+), 83 deletions(-) create mode 100644 resources/test/src/excluded.py diff --git a/Cargo.lock b/Cargo.lock index 3fe6496706..7d1d621416 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1546,7 +1546,6 @@ dependencies = [ "common-path", "dirs 4.0.0", "fern", - "lazy_static", "log", "notify", "pyo3", diff --git a/Cargo.toml b/Cargo.toml index d5295e46d3..20d4e0c606 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,6 @@ colored = { version = "2.0.0" } common-path = "1.0.0" dirs = "4.0.0" fern = { version = "0.6.1" } -lazy_static = { version = "1.4.0" } log = { version = "0.4.17" } notify = { version = "4.0.17" } pyo3 = { version = "0.16.5", features = ["extension-module", "abi3-py37"] } diff --git a/README.md b/README.md index 1d8edf57d8..89ef70d594 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,10 @@ in Rust. Features: -- Python 3.8 compatibility -- [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired - cache semantics -- [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html) - -inspired `--watch` semantics +- Python 3.9 compatibility +- [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired cache semantics +- [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` semantics +- `pyproject.toml` support ## Installation @@ -66,6 +65,58 @@ support pattern matching, which was introduced in v3.10. git clone --branch 3.9 https://github.com/python/cpython.git resources/test/cpython ``` +Add this `pyproject.toml` to the directory: + +```toml +[tool.linter] +line-length = 88 +exclude = [ + "Lib/ctypes/test/test_numbers.py", + "Lib/dataclasses.py", + "Lib/lib2to3/tests/data/bom.py", + "Lib/lib2to3/tests/data/crlf.py", + "Lib/lib2to3/tests/data/different_encoding.py", + "Lib/lib2to3/tests/data/false_encoding.py", + "Lib/lib2to3/tests/data/py2_test_grammar.py", + "Lib/sqlite3/test/factory.py", + "Lib/sqlite3/test/hooks.py", + "Lib/sqlite3/test/regression.py", + "Lib/sqlite3/test/transactions.py", + "Lib/sqlite3/test/types.py", + "Lib/test/bad_coding2.py", + "Lib/test/badsyntax_3131.py", + "Lib/test/badsyntax_pep3120.py", + "Lib/test/encoded_modules/module_iso_8859_1.py", + "Lib/test/encoded_modules/module_koi8_r.py", + "Lib/test/sortperf.py", + "Lib/test/test_email/torture_test.py", + "Lib/test/test_fstring.py", + "Lib/test/test_genericpath.py", + "Lib/test/test_getopt.py", + "Lib/test/test_htmlparser.py", + "Lib/test/test_importlib/stubs.py", + "Lib/test/test_importlib/test_files.py", + "Lib/test/test_importlib/test_metadata_api.py", + "Lib/test/test_importlib/test_open.py", + "Lib/test/test_importlib/test_util.py", + "Lib/test/test_named_expressions.py", + "Lib/test/test_peg_generator/__main__.py", + "Lib/test/test_pipes.py", + "Lib/test/test_source_encoding.py", + "Lib/test/test_weakref.py", + "Lib/test/test_webbrowser.py", + "Lib/tkinter/__main__.py", + "Lib/tkinter/test/test_tkinter/test_variables.py", + "Modules/_decimal/libmpdec/literature/fnt.py", + "Modules/_decimal/tests/deccheck.py", + "Tools/i18n/pygettext.py", + "Tools/test2to3/maintest.py", + "Tools/test2to3/setup.py", + "Tools/test2to3/test/test_foo.py", + "Tools/test2to3/test2to3/hello.py", +] +``` + Next, to benchmark the release build: ```shell diff --git a/resources/test/src/excluded.py b/resources/test/src/excluded.py new file mode 100644 index 0000000000..c9b6c43d41 --- /dev/null +++ b/resources/test/src/excluded.py @@ -0,0 +1,9 @@ +a = "abc" +b = f"ghi{'jkl'}" + +c = f"def" +d = f"def" + "ghi" +e = ( + f"def" + + "ghi" +) diff --git a/resources/test/src/pyproject.toml b/resources/test/src/pyproject.toml index 5293d5396a..53a96360ca 100644 --- a/resources/test/src/pyproject.toml +++ b/resources/test/src/pyproject.toml @@ -1,2 +1,3 @@ [tool.linter] line-length = 88 +exclude = ["excluded.py"] diff --git a/src/bin/rust_python_linter.rs b/src/bin/rust_python_linter.rs index f25a560250..aeddb52f39 100644 --- a/src/bin/rust_python_linter.rs +++ b/src/bin/rust_python_linter.rs @@ -41,6 +41,12 @@ fn run_once(files: &[PathBuf], settings: &Settings, cache: bool) -> Result = files .par_iter() + .filter(|entry| { + !settings + .exclude + .iter() + .any(|exclusion| entry.path().starts_with(exclusion)) + }) .map(|entry| { check_path(entry.path(), settings, &cache.into()).unwrap_or_else(|e| { error!("Failed to check {}: {e:?}", entry.path().to_string_lossy()); diff --git a/src/fs.rs b/src/fs.rs index bfc4fc6ba8..09d5eb0769 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,64 +1,10 @@ -use std::collections::HashSet; use std::fs::File; use std::io::{BufRead, BufReader, Read}; -use std::ops::Deref; use std::path::{Path, PathBuf}; use anyhow::Result; -use lazy_static::lazy_static; use walkdir::{DirEntry, WalkDir}; -lazy_static! { - // TODO(charlie): Make these configurable. - static ref EXCLUDES: HashSet<&'static str> = vec![ - "resources/test/cpython/Lib/ctypes/test/test_numbers.py", - "resources/test/cpython/Lib/dataclasses.py", - "resources/test/cpython/Lib/lib2to3/tests/data/bom.py", - "resources/test/cpython/Lib/lib2to3/tests/data/crlf.py", - "resources/test/cpython/Lib/lib2to3/tests/data/different_encoding.py", - "resources/test/cpython/Lib/lib2to3/tests/data/false_encoding.py", - "resources/test/cpython/Lib/lib2to3/tests/data/py2_test_grammar.py", - "resources/test/cpython/Lib/sqlite3/test/factory.py", - "resources/test/cpython/Lib/sqlite3/test/hooks.py", - "resources/test/cpython/Lib/sqlite3/test/regression.py", - "resources/test/cpython/Lib/sqlite3/test/transactions.py", - "resources/test/cpython/Lib/sqlite3/test/types.py", - "resources/test/cpython/Lib/test/bad_coding2.py", - "resources/test/cpython/Lib/test/badsyntax_3131.py", - "resources/test/cpython/Lib/test/badsyntax_pep3120.py", - "resources/test/cpython/Lib/test/encoded_modules/module_iso_8859_1.py", - "resources/test/cpython/Lib/test/encoded_modules/module_koi8_r.py", - "resources/test/cpython/Lib/test/sortperf.py", - "resources/test/cpython/Lib/test/test_email/torture_test.py", - "resources/test/cpython/Lib/test/test_fstring.py", - "resources/test/cpython/Lib/test/test_genericpath.py", - "resources/test/cpython/Lib/test/test_getopt.py", - "resources/test/cpython/Lib/test/test_htmlparser.py", - "resources/test/cpython/Lib/test/test_importlib/stubs.py", - "resources/test/cpython/Lib/test/test_importlib/test_files.py", - "resources/test/cpython/Lib/test/test_importlib/test_metadata_api.py", - "resources/test/cpython/Lib/test/test_importlib/test_open.py", - "resources/test/cpython/Lib/test/test_importlib/test_util.py", - "resources/test/cpython/Lib/test/test_named_expressions.py", - "resources/test/cpython/Lib/test/test_peg_generator/__main__.py", - "resources/test/cpython/Lib/test/test_pipes.py", - "resources/test/cpython/Lib/test/test_source_encoding.py", - "resources/test/cpython/Lib/test/test_weakref.py", - "resources/test/cpython/Lib/test/test_webbrowser.py", - "resources/test/cpython/Lib/tkinter/__main__.py", - "resources/test/cpython/Lib/tkinter/test/test_tkinter/test_variables.py", - "resources/test/cpython/Modules/_decimal/libmpdec/literature/fnt.py", - "resources/test/cpython/Modules/_decimal/tests/deccheck.py", - "resources/test/cpython/Tools/i18n/pygettext.py", - "resources/test/cpython/Tools/test2to3/maintest.py", - "resources/test/cpython/Tools/test2to3/setup.py", - "resources/test/cpython/Tools/test2to3/test/test_foo.py", - "resources/test/cpython/Tools/test2to3/test2to3/hello.py", - ] - .into_iter() - .collect(); -} - fn is_not_hidden(entry: &DirEntry) -> bool { entry .file_name() @@ -74,7 +20,6 @@ pub fn iter_python_files(path: &PathBuf) -> impl Iterator { .filter_entry(is_not_hidden) .filter_map(|entry| entry.ok()) .filter(|entry| entry.path().to_string_lossy().ends_with(".py")) - .filter(|entry| !EXCLUDES.contains(entry.path().to_string_lossy().deref())) } pub fn read_line(path: &Path, row: &usize) -> Result { diff --git a/src/linter.rs b/src/linter.rs index f2eb1a9e0b..e0cd23b62b 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -55,7 +55,10 @@ mod tests { fn duplicate_argument_name() -> Result<()> { let actual = check_path( &Path::new("./resources/test/src/duplicate_argument_name.py"), - &settings::Settings { line_length: 88 }, + &settings::Settings { + line_length: 88, + exclude: vec![], + }, &cache::Mode::None, )?; let expected = vec![ @@ -87,7 +90,10 @@ mod tests { fn f_string_missing_placeholders() -> Result<()> { let actual = check_path( &Path::new("./resources/test/src/f_string_missing_placeholders.py"), - &settings::Settings { line_length: 88 }, + &settings::Settings { + line_length: 88, + exclude: vec![], + }, &cache::Mode::None, )?; let expected = vec![ @@ -119,7 +125,10 @@ mod tests { fn if_tuple() -> Result<()> { let actual = check_path( &Path::new("./resources/test/src/if_tuple.py"), - &settings::Settings { line_length: 88 }, + &settings::Settings { + line_length: 88, + exclude: vec![], + }, &cache::Mode::None, )?; let expected = vec![ @@ -146,7 +155,10 @@ mod tests { fn import_star_usage() -> Result<()> { let actual = check_path( &Path::new("./resources/test/src/import_star_usage.py"), - &settings::Settings { line_length: 88 }, + &settings::Settings { + line_length: 88, + exclude: vec![], + }, &cache::Mode::None, )?; let expected = vec![ @@ -173,7 +185,10 @@ mod tests { fn line_too_long() -> Result<()> { let actual = check_path( &Path::new("./resources/test/src/line_too_long.py"), - &settings::Settings { line_length: 88 }, + &settings::Settings { + line_length: 88, + exclude: vec![], + }, &cache::Mode::None, )?; let expected = vec![Message { diff --git a/src/pyproject.rs b/src/pyproject.rs index 64a27a8b55..22e6bf9d08 100644 --- a/src/pyproject.rs +++ b/src/pyproject.rs @@ -7,7 +7,7 @@ use serde::Deserialize; use crate::fs; -pub fn load_config<'a>(paths: impl IntoIterator) -> Result { +pub fn load_config<'a>(paths: impl IntoIterator) -> Result<(PathBuf, Config)> { match find_project_root(paths) { Some(project_root) => match find_pyproject_toml(&project_root) { Some(path) => { @@ -17,7 +17,7 @@ pub fn load_config<'a>(paths: impl IntoIterator) -> Result Ok(Default::default()), }, @@ -29,6 +29,7 @@ pub fn load_config<'a>(paths: impl IntoIterator) -> Result, + pub exclude: Option>, } #[derive(Debug, PartialEq, Eq, Deserialize)] @@ -110,7 +111,10 @@ mod tests { assert_eq!( pyproject.tool, Some(Tools { - linter: Some(Config { line_length: None }) + linter: Some(Config { + line_length: None, + exclude: None + }) }) ); @@ -125,7 +129,25 @@ line-length = 79 pyproject.tool, Some(Tools { linter: Some(Config { - line_length: Some(79) + line_length: Some(79), + exclude: None + }) + }) + ); + + let pyproject: PyProject = toml::from_str( + r#" +[tool.black] +[tool.linter] +exclude = ["foo.py"] +"#, + )?; + assert_eq!( + pyproject.tool, + Some(Tools { + linter: Some(Config { + line_length: None, + exclude: Some(vec![Path::new("foo.py").to_path_buf()]) }) }) ); @@ -170,7 +192,8 @@ other-attribute = 1 assert_eq!( config, Config { - line_length: Some(88) + line_length: Some(88), + exclude: Some(vec![Path::new("excluded.py").to_path_buf()]) } ); diff --git a/src/settings.rs b/src/settings.rs index 015ffd5166..f85b50e323 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,25 +1,34 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use anyhow::Result; -use crate::pyproject::{load_config, Config}; +use crate::pyproject::load_config; pub struct Settings { pub line_length: usize, + pub exclude: Vec, } static DEFAULT_MAX_LINE_LENGTH: usize = 88; -impl From for Settings { - fn from(config: Config) -> Settings { - Settings { - line_length: config.line_length.unwrap_or(DEFAULT_MAX_LINE_LENGTH), - } - } -} - impl Settings { pub fn from_paths<'a>(paths: impl IntoIterator) -> Result { - load_config(paths).map(|config| config.into()) + let (project_root, config) = load_config(paths)?; + + Ok(Settings { + line_length: config.line_length.unwrap_or(DEFAULT_MAX_LINE_LENGTH), + exclude: config + .exclude + .unwrap_or_default() + .into_iter() + .map(|path| { + if path.is_relative() { + project_root.join(path) + } else { + path + } + }) + .collect(), + }) } }