Enable excludes (#18)

This commit is contained in:
Charlie Marsh 2022-08-20 13:00:58 -04:00 committed by GitHub
parent 7359e862c1
commit b11a7eefa3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 140 additions and 83 deletions

1
Cargo.lock generated
View file

@ -1546,7 +1546,6 @@ dependencies = [
"common-path",
"dirs 4.0.0",
"fern",
"lazy_static",
"log",
"notify",
"pyo3",

View file

@ -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"] }

View file

@ -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

View file

@ -0,0 +1,9 @@
a = "abc"
b = f"ghi{'jkl'}"
c = f"def"
d = f"def" + "ghi"
e = (
f"def" +
"ghi"
)

View file

@ -1,2 +1,3 @@
[tool.linter]
line-length = 88
exclude = ["excluded.py"]

View file

@ -41,6 +41,12 @@ fn run_once(files: &[PathBuf], settings: &Settings, cache: bool) -> Result<Vec<M
let start = Instant::now();
let messages: Vec<Message> = 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());

View file

@ -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<Item = DirEntry> {
.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<String> {

View file

@ -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 {

View file

@ -7,7 +7,7 @@ use serde::Deserialize;
use crate::fs;
pub fn load_config<'a>(paths: impl IntoIterator<Item = &'a Path>) -> Result<Config> {
pub fn load_config<'a>(paths: impl IntoIterator<Item = &'a Path>) -> 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<Item = &'a Path>) -> Result<Conf
.tool
.and_then(|tool| tool.linter)
.unwrap_or_default();
Ok(config)
Ok((project_root, config))
}
None => Ok(Default::default()),
},
@ -29,6 +29,7 @@ pub fn load_config<'a>(paths: impl IntoIterator<Item = &'a Path>) -> Result<Conf
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Config {
pub line_length: Option<usize>,
pub exclude: Option<Vec<PathBuf>>,
}
#[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()])
}
);

View file

@ -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<PathBuf>,
}
static DEFAULT_MAX_LINE_LENGTH: usize = 88;
impl From<Config> 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<Item = &'a Path>) -> Result<Self> {
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(),
})
}
}