Improve exclusion syntax to match exact files (#209)

This commit is contained in:
Charlie Marsh
2022-09-15 21:40:49 -04:00
committed by GitHub
parent 6bbf3f46c4
commit 9d4a4478f7
13 changed files with 193 additions and 44 deletions

19
Cargo.lock generated
View File

@@ -1432,6 +1432,24 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "path-absolutize"
version = "3.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3de4b40bd9736640f14c438304c09538159802388febb02c8abaae0846c1f13"
dependencies = [
"path-dedot",
]
[[package]]
name = "path-dedot"
version = "3.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d611d5291372b3738a34ebf0d1f849e58b1dcc1101032f76a346eaa1f8ddbb5b"
dependencies = [
"once_cell",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
@@ -1802,6 +1820,7 @@ dependencies = [
"log",
"notify",
"once_cell",
"path-absolutize",
"rayon",
"regex",
"rustpython-parser",

View File

@@ -23,6 +23,7 @@ itertools = "0.10.3"
log = { version = "0.4.17" }
notify = { version = "4.0.17" }
once_cell = { version = "1.13.1" }
path-absolutize = "3.0.13"
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "7d21c6923a506e79cc041708d83cef925efd33f4" }

View File

@@ -96,20 +96,42 @@ ARGS:
<FILES>...
OPTIONS:
-e, --exit-zero Exit with status code "0", even upon detecting errors
--exclude <EXCLUDE>... List of file and/or directory patterns to exclude from checks
-f, --fix Attempt to automatically fix lint errors
--format <FORMAT> Output serialization format for error messages [default: text] [possible values: text, json]
-h, --help Print help information
--ignore <IGNORE>... List of error codes to ignore
-n, --no-cache Disable cache reads
-q, --quiet Disable all logging (but still exit with status code "1" upon
detecting errors)
--select <SELECT>... List of error codes to enable
-v, --verbose Enable verbose logging
-w, --watch Run in watch mode by re-running whenever files change
-e, --exit-zero
Exit with status code "0", even upon detecting errors
--exclude <EXCLUDE>...
List of paths, used to exclude files and/or directories from checks
--extend-exclude <EXTEND_EXCLUDE>...
Like --exclude, but adds additional files and directories on top of the excluded ones
-f, --fix
Attempt to automatically fix lint errors
--format <FORMAT>
Output serialization format for error messages [default: text] [possible values: text,
json]
-h, --help
Print help information
--ignore <IGNORE>...
List of error codes to ignore
-n, --no-cache
Disable cache reads
-q, --quiet
Disable all logging (but still exit with status code "1" upon detecting errors)
--select <SELECT>...
List of error codes to enable
-v, --verbose
Enable verbose logging
-w, --watch
Run in watch mode by re-running whenever files change
```
Exclusions are based on globs, and can be either:
- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the
tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching
`foo_*.py` ).
- Relative patterns, like `./directory/foo.py` (to exclude that specific file) or `./directory/*.py`
(to exclude any Python files in `./directory`). Note that these paths are relative to the
directory from which you execute `ruff`, and _not_ the directory of the `pyproject.toml`.
### Compatibility with Black
ruff is intended to be compatible with [Black](https://github.com/psf/black), and should be
@@ -222,28 +244,28 @@ Add this `pyproject.toml` to the CPython directory:
[tool.ruff]
line-length = 88
exclude = [
"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/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/test_fstring.py",
"Lib/test/test_grammar.py",
"Lib/test/test_importlib/test_util.py",
"Lib/test/test_named_expressions.py",
"Lib/test/test_patma.py",
"Lib/test/test_source_encoding.py",
"Tools/c-analyzer/c_parser/parser/_delim.py",
"Tools/i18n/pygettext.py",
"Tools/test2to3/maintest.py",
"Tools/test2to3/setup.py",
"Tools/test2to3/test/test_foo.py",
"Tools/test2to3/test2to3/hello.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/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/test_fstring.py",
"./resources/test/cpython/Lib/test/test_grammar.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_patma.py",
"./resources/test/cpython/Lib/test/test_source_encoding.py",
"./resources/test/cpython/Tools/c-analyzer/c_parser/parser/_delim.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",
]
```

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,6 +1,10 @@
[tool.ruff]
line-length = 88
extend-exclude = ["excluded.py", "migrations"]
extend-exclude = [
"excluded.py",
"migrations",
"./resources/test/fixtures/directory/also_excluded.py",
]
select = [
"E402",
"E501",

View File

@@ -1,3 +1,4 @@
use std::env;
use std::fs::File;
use std::io::{BufReader, Read};
use std::path::{Path, PathBuf};
@@ -5,9 +6,11 @@ use std::path::{Path, PathBuf};
use anyhow::Result;
use glob::Pattern;
use log::debug;
use path_absolutize::Absolutize;
use walkdir::{DirEntry, WalkDir};
fn is_excluded(path: &Path, exclude: &[Pattern]) -> bool {
// Check the basename.
if let Some(file_name) = path.file_name() {
if let Some(file_name) = file_name.to_str() {
for pattern in exclude {
@@ -15,13 +18,19 @@ fn is_excluded(path: &Path, exclude: &[Pattern]) -> bool {
return true;
}
}
false
} else {
false
}
} else {
false
}
// Check the complete path.
if let Some(file_name) = path.to_str() {
for pattern in exclude {
if pattern.matches(file_name) {
return true;
}
}
}
false
}
fn is_included(path: &Path) -> bool {
@@ -34,7 +43,7 @@ pub fn iter_python_files<'a>(
exclude: &'a [Pattern],
extend_exclude: &'a [Pattern],
) -> impl Iterator<Item = DirEntry> + 'a {
WalkDir::new(path)
WalkDir::new(normalize_path(path))
.follow_links(true)
.into_iter()
.filter_entry(|entry| {
@@ -60,6 +69,20 @@ pub fn iter_python_files<'a>(
})
}
pub fn normalize_path(path: &PathBuf) -> PathBuf {
if path == Path::new(".") || path == Path::new("..") {
return path.clone();
}
if let Ok(path) = path.absolutize() {
if let Ok(root) = env::current_dir() {
if let Ok(path) = path.strip_prefix(root) {
return Path::new(".").join(path);
}
}
}
path.clone()
}
pub fn read_file(path: &Path) -> Result<String> {
let file = File::open(path)?;
let mut buf_reader = BufReader::new(file);
@@ -105,6 +128,18 @@ mod tests {
let exclude = vec![Pattern::new("baz.py").unwrap()];
assert!(is_excluded(path, &exclude));
let path = Path::new("foo/bar");
let exclude = vec![Pattern::new("foo/bar").unwrap()];
assert!(is_excluded(path, &exclude));
let path = Path::new("foo/bar/baz.py");
let exclude = vec![Pattern::new("foo/bar/baz.py").unwrap()];
assert!(is_excluded(path, &exclude));
let path = Path::new("foo/bar/baz.py");
let exclude = vec![Pattern::new("foo/bar/*.py").unwrap()];
assert!(is_excluded(path, &exclude));
let path = Path::new("foo/bar/baz.py");
let exclude = vec![Pattern::new("baz").unwrap()];
assert!(!is_excluded(path, &exclude));

View File

@@ -261,7 +261,8 @@ other-attribute = 1
exclude: None,
extend_exclude: Some(vec![
Path::new("excluded.py").to_path_buf(),
Path::new("migrations").to_path_buf()
Path::new("migrations").to_path_buf(),
Path::new("./resources/test/fixtures/directory/also_excluded.py").to_path_buf()
]),
select: Some(vec![
CheckCode::E402,

View File

@@ -24,6 +24,7 @@ impl Hash for Settings {
}
}
}
static DEFAULT_EXCLUDE: Lazy<Vec<Pattern>> = Lazy::new(|| {
vec![
Pattern::new(".bzr").unwrap(),
@@ -57,7 +58,7 @@ impl Settings {
.exclude
.map(|paths| {
paths
.into_iter()
.iter()
.map(|path| {
Pattern::new(&path.to_string_lossy()).expect("Invalid pattern.")
})
@@ -68,7 +69,7 @@ impl Settings {
.extend_exclude
.map(|paths| {
paths
.into_iter()
.iter()
.map(|path| {
Pattern::new(&path.to_string_lossy()).expect("Invalid pattern.")
})

27
test/F823.py Normal file
View File

@@ -0,0 +1,27 @@
my_dict = {}
my_var = 0
def foo():
my_var += 1
def bar():
global my_var
my_var += 1
def baz():
global my_var
global my_dict
my_dict[my_var] += 1
def dec(x):
return x
@dec
def f():
dec = 1
return dec

9
test/directory/F706.py Normal file
View File

@@ -0,0 +1,9 @@
def f() -> int:
return 1
class Foo:
return 2
return 3

9
test/directory/F707.py Normal file
View File

@@ -0,0 +1,9 @@
def f() -> int:
return 1
class Foo:
return 2
return 3

9
test/ignore.py Normal file
View File

@@ -0,0 +1,9 @@
def f() -> int:
return 1
class Foo:
return 2
return 3

3
test/pyproject.toml Normal file
View File

@@ -0,0 +1,3 @@
[tool.ruff]
line-length = 88
extend-exclude = ["./directory/*"]