Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8f517c70e | ||
|
|
9f601c2abd | ||
|
|
c0ce0b0c48 | ||
|
|
e5b16973a9 | ||
|
|
de9ceb2fe1 | ||
|
|
38b19b78b7 | ||
|
|
7043e15b57 | ||
|
|
9594079235 | ||
|
|
732f208e47 | ||
|
|
32e62d9209 | ||
|
|
d9e4b0cdc1 |
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -1801,7 +1801,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.44"
|
||||
version = "0.0.45"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -1846,7 +1846,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=7d21c6923a506e79cc041708d83cef925efd33f4#7d21c6923a506e79cc041708d83cef925efd33f4"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=966a80597d626a9a47eaec78471164422d341453#966a80597d626a9a47eaec78471164422d341453"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-compiler-core",
|
||||
@@ -1855,7 +1855,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=7d21c6923a506e79cc041708d83cef925efd33f4#7d21c6923a506e79cc041708d83cef925efd33f4"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=966a80597d626a9a47eaec78471164422d341453#966a80597d626a9a47eaec78471164422d341453"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -1872,7 +1872,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=7d21c6923a506e79cc041708d83cef925efd33f4#7d21c6923a506e79cc041708d83cef925efd33f4"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=966a80597d626a9a47eaec78471164422d341453#966a80597d626a9a47eaec78471164422d341453"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.44"
|
||||
version = "0.0.45"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -18,15 +18,15 @@ common-path = { version = "1.0.0" }
|
||||
dirs = { version = "4.0.0" }
|
||||
fern = { version = "0.6.1" }
|
||||
filetime = { version = "0.2.17" }
|
||||
glob = "0.3.0"
|
||||
itertools = "0.10.3"
|
||||
glob = { version = "0.3.0" }
|
||||
itertools = { version = "0.10.3" }
|
||||
log = { version = "0.4.17" }
|
||||
notify = { version = "4.0.17" }
|
||||
once_cell = { version = "1.13.1" }
|
||||
path-absolutize = { version = "3.0.13", features = ["once_cell_cache"] }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "7d21c6923a506e79cc041708d83cef925efd33f4" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "966a80597d626a9a47eaec78471164422d341453" }
|
||||
serde = { version = "1.0.143", features = ["derive"] }
|
||||
serde_json = { version = "1.0.83" }
|
||||
toml = { version = "0.5.9" }
|
||||
|
||||
71
README.md
71
README.md
@@ -86,7 +86,7 @@ ruff path/to/code/ --select F401 F403
|
||||
See `ruff --help` for more:
|
||||
|
||||
```shell
|
||||
ruff (v0.0.44)
|
||||
ruff (v0.0.45) 0.0.45
|
||||
An extremely fast Python linter.
|
||||
|
||||
USAGE:
|
||||
@@ -96,12 +96,20 @@ ARGS:
|
||||
<FILES>...
|
||||
|
||||
OPTIONS:
|
||||
-e, --exit-zero
|
||||
Exit with status code "0", even upon detecting errors
|
||||
--select <SELECT>...
|
||||
List of error codes to enable
|
||||
--extend-select <EXTEND_SELECT>...
|
||||
Like --select, but adds additional error codes on top of the selected ones
|
||||
--ignore <IGNORE>...
|
||||
List of error codes to ignore
|
||||
--extend-ignore <EXTEND_IGNORE>...
|
||||
Like --ignore, but adds additional error codes on top of the ignored ones
|
||||
--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
|
||||
-e, --exit-zero
|
||||
Exit with status code "0", even upon detecting errors
|
||||
-f, --fix
|
||||
Attempt to automatically fix lint errors
|
||||
--format <FORMAT>
|
||||
@@ -109,14 +117,16 @@ OPTIONS:
|
||||
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
|
||||
--add-noqa
|
||||
Enable automatic additions of noqa directives to failing lines
|
||||
--show-files
|
||||
See the files ruff will be run against with the current settings
|
||||
--show-settings
|
||||
See ruff's settings
|
||||
-v, --verbose
|
||||
Enable verbose logging
|
||||
-V, --version
|
||||
@@ -125,6 +135,8 @@ OPTIONS:
|
||||
Run in watch mode by re-running whenever files change
|
||||
```
|
||||
|
||||
### Excluding files
|
||||
|
||||
Exclusions are based on globs, and can be either:
|
||||
|
||||
- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the
|
||||
@@ -134,6 +146,50 @@ Exclusions are based on globs, and can be either:
|
||||
(to exclude any Python files in `directory`). Note that these paths are relative to the
|
||||
project root (e.g., the directory containing your `pyproject.toml`).
|
||||
|
||||
### Ignoring errors
|
||||
|
||||
To omit a lint check entirely, add it to the "ignore" list via `--ignore` or `--extend-ignore`,
|
||||
either on the command-line or in your `project.toml` file.
|
||||
|
||||
To ignore an error in-line, ruff uses a `noqa` system similar to [Flake8](https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html).
|
||||
To ignore an individual error, add `# noqa: {code}` to the end of the line, like so:
|
||||
|
||||
```python
|
||||
# Ignore F841.
|
||||
x = 1 # noqa: F841
|
||||
|
||||
# Ignore E741 and F841.
|
||||
i = 1 # noqa: E741, F841
|
||||
|
||||
# Ignore _all_ errors.
|
||||
x = 1 # noqa
|
||||
```
|
||||
|
||||
Note that, for multi-line strings, the `noqa` directive should come at the end of the string, and
|
||||
will apply to the entire body, like so:
|
||||
|
||||
```python
|
||||
"""Lorem ipsum dolor sit amet.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa: E501
|
||||
```
|
||||
|
||||
ruff supports several (experimental) workflows to aid in `noqa` management.
|
||||
|
||||
First, ruff provides a special error code, `M001`, to enforce that your `noqa` directives are
|
||||
"valid", in that the errors they _say_ they ignore are actually being triggered on that line (and
|
||||
thus suppressed). **You can run `ruff /path/to/file.py --extend-select M001` to flag unused `noqa`
|
||||
directives.**
|
||||
|
||||
Second, ruff can _automatically remove_ unused `noqa` directives via its autofix functionality.
|
||||
**You can run `ruff /path/to/file.py --extend-select M001 --fix` to automatically remove unused
|
||||
`noqa` directives.**
|
||||
|
||||
Third, ruff can _automatically add_ `noqa` directives to all failing lines. This is useful when
|
||||
migrating a new codebase to ruff. **You can run `ruff /path/to/file.py --add-noqa` to automatically
|
||||
add `noqa` directives to all failing lines, with the appropriate error codes.**
|
||||
|
||||
### Compatibility with Black
|
||||
|
||||
ruff is compatible with [Black](https://github.com/psf/black) out-of-the-box, as long as
|
||||
@@ -205,6 +261,7 @@ Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis F
|
||||
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` |
|
||||
| R001 | UselessObjectInheritance | Class `...` inherits from object |
|
||||
| R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead |
|
||||
| M001 | UnusedNOQA | Unused `noqa` directive |
|
||||
|
||||
## Development
|
||||
|
||||
|
||||
7
resources/test/fixtures/E501.py
vendored
7
resources/test/fixtures/E501.py
vendored
@@ -5,5 +5,12 @@
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
"""
|
||||
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa: E501
|
||||
|
||||
_ = "---------------------------------------------------------------------------AAAAAAA"
|
||||
_ = "---------------------------------------------------------------------------亜亜亜亜亜亜亜"
|
||||
|
||||
57
resources/test/fixtures/M001.py
vendored
Normal file
57
resources/test/fixtures/M001.py
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
def f() -> None:
|
||||
# Valid
|
||||
a = 1 # noqa
|
||||
|
||||
# Valid
|
||||
b = 2 # noqa: F841
|
||||
|
||||
# Invalid
|
||||
c = 1 # noqa
|
||||
print(c)
|
||||
|
||||
# Invalid
|
||||
d = 1 # noqa: E501
|
||||
|
||||
# Invalid
|
||||
d = 1 # noqa: F841, E501
|
||||
|
||||
|
||||
# Valid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa: E501
|
||||
|
||||
# Valid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa
|
||||
|
||||
# Invalid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa: E501, F841
|
||||
|
||||
# Invalid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
""" # noqa: E501
|
||||
|
||||
# Invalid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
""" # noqa
|
||||
@@ -1,6 +1,11 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode, CheckKind, Fix};
|
||||
use crate::noqa;
|
||||
use crate::noqa::Directive;
|
||||
use crate::settings::Settings;
|
||||
|
||||
/// Whether the given line is too long and should be reported.
|
||||
@@ -19,17 +24,58 @@ fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_lines(checks: &mut Vec<Check>, contents: &str, settings: &Settings) {
|
||||
pub fn check_lines(
|
||||
checks: &mut Vec<Check>,
|
||||
contents: &str,
|
||||
noqa_line_for: &[usize],
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) {
|
||||
let enforce_line_too_long = settings.select.contains(&CheckCode::E501);
|
||||
let enforce_noqa = settings.select.contains(&CheckCode::M001);
|
||||
|
||||
let mut noqa_directives: BTreeMap<usize, (Directive, Vec<&str>)> = BTreeMap::new();
|
||||
|
||||
let mut line_checks = vec![];
|
||||
let mut ignored = vec![];
|
||||
for (row, line) in contents.lines().enumerate() {
|
||||
|
||||
let lines: Vec<&str> = contents.lines().collect();
|
||||
for (lineno, line) in lines.iter().enumerate() {
|
||||
// Grab the noqa (logical) line number for the current (physical) line.
|
||||
// If there are newlines at the end of the file, they won't be represented in
|
||||
// `noqa_line_for`, so fallback to the current line.
|
||||
let noqa_lineno = noqa_line_for
|
||||
.get(lineno)
|
||||
.map(|lineno| lineno - 1)
|
||||
.unwrap_or(lineno);
|
||||
|
||||
if enforce_noqa {
|
||||
noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
}
|
||||
|
||||
// Remove any ignored checks.
|
||||
// TODO(charlie): Only validate checks for the current line.
|
||||
for (index, check) in checks.iter().enumerate() {
|
||||
if check.location.row() == row + 1 && check.is_inline_ignored(line) {
|
||||
ignored.push(index);
|
||||
if check.location.row() == lineno + 1 {
|
||||
let noqa = noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
|
||||
match noqa {
|
||||
(Directive::All(_), matches) => {
|
||||
matches.push(check.kind.code().as_str());
|
||||
ignored.push(index)
|
||||
}
|
||||
(Directive::Codes(_, codes), matches) => {
|
||||
if codes.contains(&check.kind.code().as_str()) {
|
||||
matches.push(check.kind.code().as_str());
|
||||
ignored.push(index);
|
||||
}
|
||||
}
|
||||
(Directive::None, _) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,16 +83,94 @@ pub fn check_lines(checks: &mut Vec<Check>, contents: &str, settings: &Settings)
|
||||
if enforce_line_too_long {
|
||||
let line_length = line.chars().count();
|
||||
if should_enforce_line_length(line, line_length, settings.line_length) {
|
||||
let noqa = noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
|
||||
let check = Check::new(
|
||||
CheckKind::LineTooLong(line_length, settings.line_length),
|
||||
Location::new(row + 1, settings.line_length + 1),
|
||||
Location::new(lineno + 1, settings.line_length + 1),
|
||||
);
|
||||
if !check.is_inline_ignored(line) {
|
||||
line_checks.push(check);
|
||||
|
||||
match noqa {
|
||||
(Directive::All(_), matches) => {
|
||||
matches.push(check.kind.code().as_str());
|
||||
}
|
||||
(Directive::Codes(_, codes), matches) => {
|
||||
if codes.contains(&check.kind.code().as_str()) {
|
||||
matches.push(check.kind.code().as_str());
|
||||
} else {
|
||||
line_checks.push(check);
|
||||
}
|
||||
}
|
||||
(Directive::None, _) => line_checks.push(check),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce that the noqa directive was actually used.
|
||||
if enforce_noqa {
|
||||
for (row, (directive, matches)) in noqa_directives {
|
||||
match directive {
|
||||
Directive::All(column) => {
|
||||
if matches.is_empty() {
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedNOQA(None),
|
||||
Location::new(row + 1, column + 1),
|
||||
);
|
||||
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
check.amend(Fix {
|
||||
content: "".to_string(),
|
||||
start: Location::new(row + 1, column + 1),
|
||||
end: Location::new(row + 1, lines[row].chars().count() + 1),
|
||||
applied: false,
|
||||
});
|
||||
}
|
||||
line_checks.push(check);
|
||||
}
|
||||
}
|
||||
Directive::Codes(column, codes) => {
|
||||
let mut invalid_codes = vec![];
|
||||
let mut valid_codes = vec![];
|
||||
for code in codes {
|
||||
if !matches.contains(&code) {
|
||||
invalid_codes.push(code);
|
||||
} else {
|
||||
valid_codes.push(code);
|
||||
}
|
||||
}
|
||||
|
||||
if !invalid_codes.is_empty() {
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedNOQA(Some(invalid_codes.join(", "))),
|
||||
Location::new(row + 1, column + 1),
|
||||
);
|
||||
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if valid_codes.is_empty() {
|
||||
check.amend(Fix {
|
||||
content: "".to_string(),
|
||||
start: Location::new(row + 1, column + 1),
|
||||
end: Location::new(row + 1, lines[row].chars().count() + 1),
|
||||
applied: false,
|
||||
});
|
||||
} else {
|
||||
check.amend(Fix {
|
||||
content: format!(" # noqa: {}", valid_codes.join(", ")),
|
||||
start: Location::new(row + 1, column + 1),
|
||||
end: Location::new(row + 1, lines[row].chars().count() + 1),
|
||||
applied: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
line_checks.push(check);
|
||||
}
|
||||
}
|
||||
Directive::None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ignored.sort();
|
||||
for index in ignored.iter().rev() {
|
||||
checks.swap_remove(*index);
|
||||
@@ -64,15 +188,24 @@ mod tests {
|
||||
#[test]
|
||||
fn e501_non_ascii_char() {
|
||||
let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8.
|
||||
let noqa_line_for: Vec<usize> = vec![1];
|
||||
let check_with_max_line_length = |line_length: usize| {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
let settings = Settings {
|
||||
pyproject: None,
|
||||
project_root: None,
|
||||
line_length,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from_iter(vec![CheckCode::E501]),
|
||||
};
|
||||
check_lines(&mut checks, line, &settings);
|
||||
check_lines(
|
||||
&mut checks,
|
||||
line,
|
||||
&noqa_line_for,
|
||||
&settings,
|
||||
&fixer::Mode::Generate,
|
||||
);
|
||||
return checks;
|
||||
};
|
||||
assert!(!check_with_max_line_length(6).is_empty());
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::ast::Location;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const ALL_CHECK_CODES: [CheckCode; 44] = [
|
||||
pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
@@ -49,6 +47,52 @@ pub const ALL_CHECK_CODES: [CheckCode; 44] = [
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F901,
|
||||
];
|
||||
|
||||
pub const ALL_CHECK_CODES: [CheckCode; 45] = [
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
CheckCode::E721,
|
||||
CheckCode::E722,
|
||||
CheckCode::E731,
|
||||
CheckCode::E741,
|
||||
CheckCode::E742,
|
||||
CheckCode::E743,
|
||||
CheckCode::E902,
|
||||
CheckCode::E999,
|
||||
CheckCode::F401,
|
||||
CheckCode::F402,
|
||||
CheckCode::F403,
|
||||
CheckCode::F404,
|
||||
CheckCode::F405,
|
||||
CheckCode::F406,
|
||||
CheckCode::F407,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
CheckCode::F602,
|
||||
CheckCode::F621,
|
||||
CheckCode::F622,
|
||||
CheckCode::F631,
|
||||
CheckCode::F632,
|
||||
CheckCode::F633,
|
||||
CheckCode::F634,
|
||||
CheckCode::F701,
|
||||
CheckCode::F702,
|
||||
CheckCode::F704,
|
||||
CheckCode::F706,
|
||||
CheckCode::F707,
|
||||
CheckCode::F722,
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F901,
|
||||
CheckCode::M001,
|
||||
CheckCode::R001,
|
||||
CheckCode::R002,
|
||||
];
|
||||
@@ -99,6 +143,7 @@ pub enum CheckCode {
|
||||
F901,
|
||||
R001,
|
||||
R002,
|
||||
M001,
|
||||
}
|
||||
|
||||
impl FromStr for CheckCode {
|
||||
@@ -150,6 +195,7 @@ impl FromStr for CheckCode {
|
||||
"F901" => Ok(CheckCode::F901),
|
||||
"R001" => Ok(CheckCode::R001),
|
||||
"R002" => Ok(CheckCode::R002),
|
||||
"M001" => Ok(CheckCode::M001),
|
||||
_ => Err(anyhow::anyhow!("Unknown check code: {s}")),
|
||||
}
|
||||
}
|
||||
@@ -202,13 +248,14 @@ impl CheckCode {
|
||||
CheckCode::F901 => "F901",
|
||||
CheckCode::R001 => "R001",
|
||||
CheckCode::R002 => "R002",
|
||||
CheckCode::M001 => "M001",
|
||||
}
|
||||
}
|
||||
|
||||
/// The source for the check (either the AST, the filesystem, or the physical lines).
|
||||
pub fn lint_source(&self) -> &'static LintSource {
|
||||
match self {
|
||||
CheckCode::E501 => &LintSource::Lines,
|
||||
CheckCode::E501 | CheckCode::M001 => &LintSource::Lines,
|
||||
CheckCode::E902 | CheckCode::E999 => &LintSource::FileSystem,
|
||||
_ => &LintSource::AST,
|
||||
}
|
||||
@@ -259,6 +306,7 @@ impl CheckCode {
|
||||
CheckCode::F831 => CheckKind::DuplicateArgumentName,
|
||||
CheckCode::F841 => CheckKind::UnusedVariable("...".to_string()),
|
||||
CheckCode::F901 => CheckKind::RaiseNotImplemented,
|
||||
CheckCode::M001 => CheckKind::UnusedNOQA(None),
|
||||
CheckCode::R001 => CheckKind::UselessObjectInheritance("...".to_string()),
|
||||
CheckCode::R002 => CheckKind::NoAssertEquals,
|
||||
}
|
||||
@@ -280,6 +328,7 @@ pub enum RejectedCmpop {
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CheckKind {
|
||||
UnusedNOQA(Option<String>),
|
||||
AmbiguousClassName(String),
|
||||
AmbiguousFunctionName(String),
|
||||
AmbiguousVariableName(String),
|
||||
@@ -376,6 +425,7 @@ impl CheckKind {
|
||||
CheckKind::UnusedVariable(_) => "UnusedVariable",
|
||||
CheckKind::UselessObjectInheritance(_) => "UselessObjectInheritance",
|
||||
CheckKind::YieldOutsideFunction => "YieldOutsideFunction",
|
||||
CheckKind::UnusedNOQA(_) => "UnusedNOQA",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,8 +449,8 @@ impl CheckKind {
|
||||
CheckKind::IfTuple => &CheckCode::F634,
|
||||
CheckKind::ImportShadowedByLoopVar(_, _) => &CheckCode::F402,
|
||||
CheckKind::ImportStarNotPermitted(_) => &CheckCode::F406,
|
||||
CheckKind::ImportStarUsed(_) => &CheckCode::F403,
|
||||
CheckKind::ImportStarUsage(_, _) => &CheckCode::F405,
|
||||
CheckKind::ImportStarUsed(_) => &CheckCode::F403,
|
||||
CheckKind::InvalidPrintSyntax => &CheckCode::F633,
|
||||
CheckKind::IsLiteral => &CheckCode::F632,
|
||||
CheckKind::LateFutureImport => &CheckCode::F404,
|
||||
@@ -423,6 +473,7 @@ impl CheckKind {
|
||||
CheckKind::UndefinedLocal(_) => &CheckCode::F823,
|
||||
CheckKind::UndefinedName(_) => &CheckCode::F821,
|
||||
CheckKind::UnusedImport(_) => &CheckCode::F401,
|
||||
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
|
||||
CheckKind::UnusedVariable(_) => &CheckCode::F841,
|
||||
CheckKind::UselessObjectInheritance(_) => &CheckCode::R001,
|
||||
CheckKind::YieldOutsideFunction => &CheckCode::F704,
|
||||
@@ -556,6 +607,10 @@ impl CheckKind {
|
||||
CheckKind::YieldOutsideFunction => {
|
||||
"a `yield` or `yield from` statement outside of a function/method".to_string()
|
||||
}
|
||||
CheckKind::UnusedNOQA(code) => match code {
|
||||
None => "Unused `noqa` directive".to_string(),
|
||||
Some(code) => format!("Unused `noqa` directive for: {code}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,7 +618,9 @@ impl CheckKind {
|
||||
pub fn fixable(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
CheckKind::NoAssertEquals | CheckKind::UselessObjectInheritance(_)
|
||||
CheckKind::NoAssertEquals
|
||||
| CheckKind::UselessObjectInheritance(_)
|
||||
| CheckKind::UnusedNOQA(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -583,11 +640,6 @@ pub struct Check {
|
||||
pub fix: Option<Fix>,
|
||||
}
|
||||
|
||||
static NO_QA_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r"(?i)# noqa(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?").expect("Invalid regex")
|
||||
});
|
||||
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").expect("Invalid regex"));
|
||||
|
||||
impl Check {
|
||||
pub fn new(kind: CheckKind, location: Location) -> Self {
|
||||
Self {
|
||||
@@ -600,25 +652,4 @@ impl Check {
|
||||
pub fn amend(&mut self, fix: Fix) {
|
||||
self.fix = Some(fix);
|
||||
}
|
||||
|
||||
pub fn is_inline_ignored(&self, line: &str) -> bool {
|
||||
match NO_QA_REGEX.captures(line) {
|
||||
Some(caps) => match caps.name("codes") {
|
||||
Some(codes) => {
|
||||
for code in SPLIT_COMMA_REGEX
|
||||
.split(codes.as_str())
|
||||
.map(|code| code.trim())
|
||||
.filter(|code| !code.is_empty())
|
||||
{
|
||||
if code == self.kind.code().as_str() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
None => true,
|
||||
},
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ pub mod fs;
|
||||
pub mod linter;
|
||||
pub mod logging;
|
||||
pub mod message;
|
||||
mod noqa;
|
||||
pub mod printer;
|
||||
pub mod pyproject;
|
||||
mod python;
|
||||
|
||||
356
src/linter.rs
356
src/linter.rs
@@ -2,7 +2,8 @@ use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::debug;
|
||||
use rustpython_parser::parser;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use rustpython_parser::{lexer, parser};
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::autofix::fixer::fix_file;
|
||||
@@ -10,25 +11,30 @@ use crate::check_ast::check_ast;
|
||||
use crate::check_lines::check_lines;
|
||||
use crate::checks::{Check, CheckCode, CheckKind, LintSource};
|
||||
use crate::message::Message;
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::settings::Settings;
|
||||
use crate::{cache, fs};
|
||||
use crate::{cache, fs, noqa};
|
||||
|
||||
fn check_path(
|
||||
path: &Path,
|
||||
contents: &str,
|
||||
tokens: Vec<LexResult>,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Vec<Check> {
|
||||
// Aggregate all checks.
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
// Determine the noqa line for every line in the source.
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
|
||||
// Run the AST-based checks.
|
||||
if settings
|
||||
.select
|
||||
.iter()
|
||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::AST))
|
||||
{
|
||||
match parser::parse_program(contents, "<filename>") {
|
||||
match parser::parse_program_tokens(tokens, "<filename>") {
|
||||
Ok(python_ast) => {
|
||||
checks.extend(check_ast(&python_ast, contents, settings, autofix, path))
|
||||
}
|
||||
@@ -44,7 +50,7 @@ fn check_path(
|
||||
}
|
||||
|
||||
// Run the lines-based checks.
|
||||
check_lines(&mut checks, contents, settings);
|
||||
check_lines(&mut checks, contents, &noqa_line_for, settings, autofix);
|
||||
|
||||
checks
|
||||
}
|
||||
@@ -66,8 +72,11 @@ pub fn lint_path(
|
||||
// Read the file from disk.
|
||||
let contents = fs::read_file(path)?;
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = lexer::make_tokenizer(&contents).collect();
|
||||
|
||||
// Generate checks.
|
||||
let mut checks = check_path(path, &contents, settings, autofix);
|
||||
let mut checks = check_path(path, &contents, tokens, settings, autofix);
|
||||
|
||||
// Apply autofix.
|
||||
if matches!(autofix, fixer::Mode::Apply) {
|
||||
@@ -89,12 +98,29 @@ pub fn lint_path(
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
|
||||
// Read the file from disk.
|
||||
let contents = fs::read_file(path)?;
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = lexer::make_tokenizer(&contents).collect();
|
||||
|
||||
// Determine the noqa line for every line in the source.
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
|
||||
// Generate checks.
|
||||
let checks = check_path(path, &contents, tokens, settings, &fixer::Mode::None);
|
||||
|
||||
add_noqa(&checks, &contents, &noqa_line_for, path)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeSet;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
@@ -108,19 +134,17 @@ mod tests {
|
||||
autofix: &fixer::Mode,
|
||||
) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
Ok(linter::check_path(path, &contents, settings, autofix))
|
||||
let tokens: Vec<LexResult> = lexer::make_tokenizer(&contents).collect();
|
||||
Ok(linter::check_path(
|
||||
path, &contents, tokens, settings, autofix,
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn e402() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E402.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E402]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E402),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -132,12 +156,7 @@ mod tests {
|
||||
fn e501() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E501.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E501]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E501),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -149,12 +168,7 @@ mod tests {
|
||||
fn e711() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E711.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E711]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E711),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -166,12 +180,7 @@ mod tests {
|
||||
fn e712() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E712.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E712]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E712),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -183,12 +192,7 @@ mod tests {
|
||||
fn e713() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E713.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E713]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E713),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -200,12 +204,7 @@ mod tests {
|
||||
fn e721() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E721.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E721]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E721),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -217,12 +216,7 @@ mod tests {
|
||||
fn e722() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E722.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E722]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E722),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -234,12 +228,7 @@ mod tests {
|
||||
fn e714() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E714.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E714]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E714),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -251,12 +240,7 @@ mod tests {
|
||||
fn e731() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E731.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E731]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E731),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -268,12 +252,7 @@ mod tests {
|
||||
fn e741() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E741.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E741]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E741),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -285,12 +264,7 @@ mod tests {
|
||||
fn e742() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E742.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E742]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E742),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -302,12 +276,7 @@ mod tests {
|
||||
fn e743() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E743.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E743]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E743),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -319,12 +288,7 @@ mod tests {
|
||||
fn f401() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F401.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F401]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F401),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -336,12 +300,7 @@ mod tests {
|
||||
fn f402() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F402.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F402]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F402),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -353,12 +312,7 @@ mod tests {
|
||||
fn f403() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F403.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F403]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F403),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -370,12 +324,7 @@ mod tests {
|
||||
fn f404() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F404.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F404]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F404),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -387,12 +336,7 @@ mod tests {
|
||||
fn f405() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F405.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F405]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F405),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -404,12 +348,7 @@ mod tests {
|
||||
fn f406() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F406.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F406]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F406),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -421,12 +360,7 @@ mod tests {
|
||||
fn f407() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F407.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F407]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F407),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -438,12 +372,7 @@ mod tests {
|
||||
fn f541() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F541.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F541]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F541),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -455,12 +384,7 @@ mod tests {
|
||||
fn f601() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F601.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F601]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F601),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -472,12 +396,7 @@ mod tests {
|
||||
fn f602() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F602.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F602]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F602),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -489,12 +408,7 @@ mod tests {
|
||||
fn f622() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F622.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F622]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F622),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -506,12 +420,7 @@ mod tests {
|
||||
fn f631() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F631.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F631]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F631),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -523,12 +432,7 @@ mod tests {
|
||||
fn f632() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F632.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F632]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F632),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -540,12 +444,7 @@ mod tests {
|
||||
fn f633() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F633.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F633]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F633),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -557,12 +456,7 @@ mod tests {
|
||||
fn f634() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F634.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F634]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F634),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -574,12 +468,7 @@ mod tests {
|
||||
fn f701() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F701.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F701]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F701),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -591,12 +480,7 @@ mod tests {
|
||||
fn f702() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F702.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F702]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F702),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -608,12 +492,7 @@ mod tests {
|
||||
fn f704() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F704.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F704]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F704),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -625,12 +504,7 @@ mod tests {
|
||||
fn f706() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F706.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F706]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F706),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -642,12 +516,7 @@ mod tests {
|
||||
fn f707() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F707.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F707]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F707),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -659,12 +528,7 @@ mod tests {
|
||||
fn f722() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F722.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F722]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F722),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -676,12 +540,7 @@ mod tests {
|
||||
fn f821() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F821.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F821]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F821),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -693,12 +552,7 @@ mod tests {
|
||||
fn f822() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F822.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F822]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F822),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -710,12 +564,7 @@ mod tests {
|
||||
fn f823() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F823.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F823]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F823),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -727,12 +576,7 @@ mod tests {
|
||||
fn f831() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F831.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F831]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F831),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -744,12 +588,7 @@ mod tests {
|
||||
fn f841() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F841.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F841]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F841),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -761,12 +600,19 @@ mod tests {
|
||||
fn f901() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F901.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F901]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F901),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn m001() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/M001.py"),
|
||||
&settings::Settings::for_rules(vec![CheckCode::M001, CheckCode::E501, CheckCode::F841]),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -778,12 +624,7 @@ mod tests {
|
||||
fn r001() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/R001.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::R001]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::R001),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -795,12 +636,7 @@ mod tests {
|
||||
fn r002() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/R002.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::R002]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::R002),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -812,12 +648,7 @@ mod tests {
|
||||
fn init() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/__init__.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F821, CheckCode::F822]),
|
||||
},
|
||||
&settings::Settings::for_rules(vec![CheckCode::F821, CheckCode::F822]),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -829,12 +660,7 @@ mod tests {
|
||||
fn future_annotations() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/future_annotations.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F401, CheckCode::F821]),
|
||||
},
|
||||
&settings::Settings::for_rules(vec![CheckCode::F401, CheckCode::F821]),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
|
||||
119
src/main.rs
119
src/main.rs
@@ -25,6 +25,7 @@ use ::ruff::printer::{Printer, SerializationFormat};
|
||||
use ::ruff::pyproject;
|
||||
use ::ruff::settings::{FilePattern, Settings};
|
||||
use ::ruff::tell_user;
|
||||
use ruff::linter::add_noqa_to_path;
|
||||
|
||||
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
||||
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
@@ -57,9 +58,15 @@ struct Cli {
|
||||
/// List of error codes to enable.
|
||||
#[clap(long, multiple = true)]
|
||||
select: Vec<CheckCode>,
|
||||
/// Like --select, but adds additional error codes on top of the selected ones.
|
||||
#[clap(long, multiple = true)]
|
||||
extend_select: Vec<CheckCode>,
|
||||
/// List of error codes to ignore.
|
||||
#[clap(long, multiple = true)]
|
||||
ignore: Vec<CheckCode>,
|
||||
/// Like --ignore, but adds additional error codes on top of the ignored ones.
|
||||
#[clap(long, multiple = true)]
|
||||
extend_ignore: Vec<CheckCode>,
|
||||
/// List of paths, used to exclude files and/or directories from checks.
|
||||
#[clap(long, multiple = true)]
|
||||
exclude: Vec<String>,
|
||||
@@ -69,6 +76,15 @@ struct Cli {
|
||||
/// Output serialization format for error messages.
|
||||
#[clap(long, arg_enum, default_value_t=SerializationFormat::Text)]
|
||||
format: SerializationFormat,
|
||||
/// See the files ruff will be run against with the current settings.
|
||||
#[clap(long, action)]
|
||||
show_files: bool,
|
||||
/// See ruff's settings.
|
||||
#[clap(long, action)]
|
||||
show_settings: bool,
|
||||
/// Enable automatic additions of noqa directives to failing lines.
|
||||
#[clap(long, action)]
|
||||
add_noqa: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "update-informer")]
|
||||
@@ -95,6 +111,22 @@ fn check_for_updates() {
|
||||
}
|
||||
}
|
||||
|
||||
fn show_settings(settings: &Settings) {
|
||||
println!("{:#?}", settings);
|
||||
}
|
||||
|
||||
fn show_files(files: &[PathBuf], settings: &Settings) {
|
||||
let mut entries: Vec<DirEntry> = files
|
||||
.iter()
|
||||
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
|
||||
.flatten()
|
||||
.collect();
|
||||
entries.sort_by(|a, b| a.path().cmp(b.path()));
|
||||
for entry in entries {
|
||||
println!("{}", entry.path().to_string_lossy());
|
||||
}
|
||||
}
|
||||
|
||||
fn run_once(
|
||||
files: &[PathBuf],
|
||||
settings: &Settings,
|
||||
@@ -155,6 +187,35 @@ fn run_once(
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
fn add_noqa(files: &[PathBuf], settings: &Settings) -> Result<usize> {
|
||||
// Collect all the files to check.
|
||||
let start = Instant::now();
|
||||
let paths: Vec<Result<DirEntry, walkdir::Error>> = files
|
||||
.iter()
|
||||
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
|
||||
.collect();
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
let start = Instant::now();
|
||||
let modifications: usize = paths
|
||||
.par_iter()
|
||||
.map(|entry| match entry {
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
add_noqa_to_path(path, settings)
|
||||
}
|
||||
Err(_) => Ok(0),
|
||||
})
|
||||
.flatten()
|
||||
.sum();
|
||||
|
||||
let duration = start.elapsed();
|
||||
debug!("Added noqa to files in: {:?}", duration);
|
||||
|
||||
Ok(modifications)
|
||||
}
|
||||
|
||||
fn inner_main() -> Result<ExitCode> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
@@ -173,26 +234,49 @@ fn inner_main() -> Result<ExitCode> {
|
||||
};
|
||||
|
||||
// Parse the settings from the pyproject.toml and command-line arguments.
|
||||
let mut settings = Settings::from_pyproject(&pyproject, &project_root);
|
||||
let exclude: Vec<FilePattern> = cli
|
||||
.exclude
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.collect();
|
||||
let extend_exclude: Vec<FilePattern> = cli
|
||||
.extend_exclude
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.collect();
|
||||
|
||||
let mut settings = Settings::from_pyproject(pyproject, project_root);
|
||||
if !exclude.is_empty() {
|
||||
settings.exclude = exclude;
|
||||
}
|
||||
if !extend_exclude.is_empty() {
|
||||
settings.extend_exclude = extend_exclude;
|
||||
}
|
||||
if !cli.select.is_empty() {
|
||||
settings.clear();
|
||||
settings.select(cli.select);
|
||||
}
|
||||
if !cli.extend_select.is_empty() {
|
||||
settings.select(cli.extend_select);
|
||||
}
|
||||
if !cli.ignore.is_empty() {
|
||||
settings.ignore(&cli.ignore);
|
||||
}
|
||||
if !cli.exclude.is_empty() {
|
||||
settings.exclude = cli
|
||||
.exclude
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.collect();
|
||||
if !cli.extend_ignore.is_empty() {
|
||||
settings.ignore(&cli.extend_ignore);
|
||||
}
|
||||
if !cli.extend_exclude.is_empty() {
|
||||
settings.extend_exclude = cli
|
||||
.extend_exclude
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.collect();
|
||||
|
||||
if cli.show_settings && cli.show_files {
|
||||
println!("Error: specify --show-settings or show-files (not both).");
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
if cli.show_settings {
|
||||
show_settings(&settings);
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
if cli.show_files {
|
||||
show_files(&cli.files, &settings);
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
cache::init()?;
|
||||
@@ -203,6 +287,10 @@ fn inner_main() -> Result<ExitCode> {
|
||||
println!("Warning: --fix is not enabled in watch mode.");
|
||||
}
|
||||
|
||||
if cli.add_noqa {
|
||||
println!("Warning: --no-qa is not enabled in watch mode.");
|
||||
}
|
||||
|
||||
if cli.format != SerializationFormat::Text {
|
||||
println!("Warning: --format 'text' is used in watch mode.");
|
||||
}
|
||||
@@ -241,6 +329,11 @@ fn inner_main() -> Result<ExitCode> {
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
} else if cli.add_noqa {
|
||||
let modifications = add_noqa(&cli.files, &settings)?;
|
||||
if modifications > 0 {
|
||||
println!("Added {modifications} noqa directives.");
|
||||
}
|
||||
} else {
|
||||
let messages = run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?;
|
||||
if !cli.quiet {
|
||||
|
||||
280
src/noqa.rs
Normal file
280
src/noqa.rs
Normal file
@@ -0,0 +1,280 @@
|
||||
use std::cmp::{max, min};
|
||||
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use anyhow::Result;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::lexer::{LexResult, Tok};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
static NO_QA_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r"(?i)(?P<noqa>\s*# noqa(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?)")
|
||||
.expect("Invalid regex")
|
||||
});
|
||||
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").expect("Invalid regex"));
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Directive<'a> {
|
||||
None,
|
||||
All(usize),
|
||||
Codes(usize, Vec<&'a str>),
|
||||
}
|
||||
|
||||
pub fn extract_noqa_directive(line: &str) -> Directive {
|
||||
match NO_QA_REGEX.captures(line) {
|
||||
Some(caps) => match caps.name("noqa") {
|
||||
Some(noqa) => match caps.name("codes") {
|
||||
Some(codes) => Directive::Codes(
|
||||
noqa.start(),
|
||||
SPLIT_COMMA_REGEX
|
||||
.split(codes.as_str())
|
||||
.map(|code| code.trim())
|
||||
.filter(|code| !code.is_empty())
|
||||
.collect(),
|
||||
),
|
||||
None => Directive::All(noqa.start()),
|
||||
},
|
||||
None => Directive::None,
|
||||
},
|
||||
None => Directive::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_noqa_line_for(lxr: &[LexResult]) -> Vec<usize> {
|
||||
let mut noqa_line_for: Vec<usize> = vec![];
|
||||
|
||||
let mut last_is_string = false;
|
||||
let mut last_seen = usize::MIN;
|
||||
let mut min_line = usize::MAX;
|
||||
let mut max_line = usize::MIN;
|
||||
|
||||
for (start, tok, end) in lxr.iter().flatten() {
|
||||
if matches!(tok, Tok::EndOfFile) {
|
||||
break;
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Newline) {
|
||||
min_line = min(min_line, start.row());
|
||||
max_line = max(max_line, start.row());
|
||||
|
||||
// For now, we only care about preserving noqa directives across multi-line strings.
|
||||
if last_is_string {
|
||||
noqa_line_for.extend(vec![max_line; (max_line + 1) - min_line]);
|
||||
} else {
|
||||
for i in (min_line - 1)..(max_line) {
|
||||
noqa_line_for.push(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
min_line = usize::MAX;
|
||||
max_line = usize::MIN;
|
||||
} else {
|
||||
// Handle empty lines.
|
||||
if start.row() > last_seen {
|
||||
for i in last_seen..(start.row() - 1) {
|
||||
noqa_line_for.push(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
min_line = min(min_line, start.row());
|
||||
max_line = max(max_line, end.row());
|
||||
}
|
||||
last_seen = start.row();
|
||||
last_is_string = matches!(tok, Tok::String { .. });
|
||||
}
|
||||
|
||||
noqa_line_for
|
||||
}
|
||||
|
||||
fn add_noqa_inner(
|
||||
checks: &Vec<Check>,
|
||||
contents: &str,
|
||||
noqa_line_for: &[usize],
|
||||
) -> Result<(usize, String)> {
|
||||
let lines: Vec<&str> = contents.lines().collect();
|
||||
let mut matches_by_line: BTreeMap<usize, BTreeSet<&CheckCode>> = BTreeMap::new();
|
||||
for lineno in 0..lines.len() {
|
||||
let mut codes: BTreeSet<&CheckCode> = BTreeSet::new();
|
||||
for check in checks {
|
||||
if check.location.row() == lineno + 1 {
|
||||
codes.insert(check.kind.code());
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the noqa (logical) line number for the current (physical) line.
|
||||
// If there are newlines at the end of the file, they won't be represented in
|
||||
// `noqa_line_for`, so fallback to the current line.
|
||||
let noqa_lineno = noqa_line_for
|
||||
.get(lineno)
|
||||
.map(|lineno| lineno - 1)
|
||||
.unwrap_or(lineno);
|
||||
|
||||
if !codes.is_empty() {
|
||||
let matches = matches_by_line
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(BTreeSet::new);
|
||||
matches.append(&mut codes);
|
||||
}
|
||||
}
|
||||
|
||||
let mut count: usize = 0;
|
||||
let mut output = "".to_string();
|
||||
for (lineno, line) in lines.iter().enumerate() {
|
||||
match matches_by_line.get(&lineno) {
|
||||
None => {
|
||||
output.push_str(line);
|
||||
output.push('\n');
|
||||
}
|
||||
Some(codes) => {
|
||||
match extract_noqa_directive(line) {
|
||||
Directive::None => {
|
||||
output.push_str(line);
|
||||
}
|
||||
Directive::All(start) => output.push_str(&line[..start]),
|
||||
Directive::Codes(start, _) => output.push_str(&line[..start]),
|
||||
};
|
||||
let codes: Vec<&str> = codes.iter().map(|code| code.as_str()).collect();
|
||||
output.push_str(" # noqa: ");
|
||||
output.push_str(&codes.join(", "));
|
||||
output.push('\n');
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((count, output))
|
||||
}
|
||||
|
||||
pub fn add_noqa(
|
||||
checks: &Vec<Check>,
|
||||
contents: &str,
|
||||
noqa_line_for: &[usize],
|
||||
path: &Path,
|
||||
) -> Result<usize> {
|
||||
let (count, output) = add_noqa_inner(checks, contents, noqa_line_for)?;
|
||||
fs::write(path, output)?;
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use anyhow::Result;
|
||||
use rustpython_parser::ast::Location;
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use crate::noqa::{add_noqa_inner, extract_noqa_line_for};
|
||||
|
||||
#[test]
|
||||
fn extraction() -> Result<()> {
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3]);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"
|
||||
x = 1
|
||||
y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3, 4]);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = 2
|
||||
z = x + 1
|
||||
",
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3]);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
|
||||
y = 2
|
||||
z = x + 1
|
||||
",
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3, 4]);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = '''abc
|
||||
def
|
||||
ghi
|
||||
'''
|
||||
y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![4, 4, 4, 4, 5, 6]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modification() -> Result<()> {
|
||||
let checks = vec![];
|
||||
let contents = "x = 1";
|
||||
let noqa_line_for = vec![1];
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
|
||||
assert_eq!(count, 0);
|
||||
assert_eq!(output.trim(), contents.trim());
|
||||
|
||||
let checks = vec![Check::new(
|
||||
CheckKind::UnusedVariable("x".to_string()),
|
||||
Location::new(1, 1),
|
||||
)];
|
||||
let contents = "x = 1";
|
||||
let noqa_line_for = vec![1];
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output.trim(), "x = 1 # noqa: F841".trim());
|
||||
|
||||
let checks = vec![
|
||||
Check::new(
|
||||
CheckKind::AmbiguousVariableName("x".to_string()),
|
||||
Location::new(1, 1),
|
||||
),
|
||||
Check::new(
|
||||
CheckKind::UnusedVariable("x".to_string()),
|
||||
Location::new(1, 1),
|
||||
),
|
||||
];
|
||||
let contents = "x = 1 # noqa: E741";
|
||||
let noqa_line_for = vec![1];
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());
|
||||
|
||||
let checks = vec![
|
||||
Check::new(
|
||||
CheckKind::AmbiguousVariableName("x".to_string()),
|
||||
Location::new(1, 1),
|
||||
),
|
||||
Check::new(
|
||||
CheckKind::UnusedVariable("x".to_string()),
|
||||
Location::new(1, 1),
|
||||
),
|
||||
];
|
||||
let contents = "x = 1 # noqa";
|
||||
let noqa_line_for = vec![1];
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
|
||||
use glob::Pattern;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::checks::{CheckCode, ALL_CHECK_CODES};
|
||||
use crate::checks::{CheckCode, DEFAULT_CHECK_CODES};
|
||||
use crate::fs;
|
||||
use crate::pyproject::load_config;
|
||||
|
||||
@@ -36,12 +36,38 @@ impl FilePattern {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Settings {
|
||||
pub pyproject: Option<PathBuf>,
|
||||
pub project_root: Option<PathBuf>,
|
||||
pub line_length: usize,
|
||||
pub exclude: Vec<FilePattern>,
|
||||
pub extend_exclude: Vec<FilePattern>,
|
||||
pub select: BTreeSet<CheckCode>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn for_rule(check_code: CheckCode) -> Self {
|
||||
Self {
|
||||
pyproject: None,
|
||||
project_root: None,
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([check_code]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_rules(check_codes: Vec<CheckCode>) -> Self {
|
||||
Self {
|
||||
pyproject: None,
|
||||
project_root: None,
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from_iter(check_codes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Settings {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.line_length.hash(state);
|
||||
@@ -76,8 +102,8 @@ static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
|
||||
});
|
||||
|
||||
impl Settings {
|
||||
pub fn from_pyproject(path: &Option<PathBuf>, project_root: &Option<PathBuf>) -> Self {
|
||||
let config = load_config(path);
|
||||
pub fn from_pyproject(pyproject: Option<PathBuf>, project_root: Option<PathBuf>) -> Self {
|
||||
let config = load_config(&pyproject);
|
||||
let mut settings = Settings {
|
||||
line_length: config.line_length.unwrap_or(88),
|
||||
exclude: config
|
||||
@@ -85,7 +111,7 @@ impl Settings {
|
||||
.map(|paths| {
|
||||
paths
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, project_root))
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
|
||||
@@ -94,15 +120,17 @@ impl Settings {
|
||||
.map(|paths| {
|
||||
paths
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, project_root))
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
select: if let Some(select) = config.select {
|
||||
BTreeSet::from_iter(select)
|
||||
} else {
|
||||
BTreeSet::from_iter(ALL_CHECK_CODES)
|
||||
BTreeSet::from_iter(DEFAULT_CHECK_CODES)
|
||||
},
|
||||
pyproject,
|
||||
project_root,
|
||||
};
|
||||
if let Some(ignore) = &config.ignore {
|
||||
settings.ignore(ignore);
|
||||
@@ -110,8 +138,11 @@ impl Settings {
|
||||
settings
|
||||
}
|
||||
|
||||
pub fn select(&mut self, codes: Vec<CheckCode>) {
|
||||
pub fn clear(&mut self) {
|
||||
self.select.clear();
|
||||
}
|
||||
|
||||
pub fn select(&mut self, codes: Vec<CheckCode>) {
|
||||
for code in codes {
|
||||
self.select.insert(code);
|
||||
}
|
||||
|
||||
89
src/snapshots/ruff__linter__tests__m001.snap
Normal file
89
src/snapshots/ruff__linter__tests__m001.snap
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnusedNOQA: ~
|
||||
location:
|
||||
row: 9
|
||||
column: 10
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
row: 9
|
||||
column: 10
|
||||
end:
|
||||
row: 9
|
||||
column: 18
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA: E501
|
||||
location:
|
||||
row: 13
|
||||
column: 10
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
row: 13
|
||||
column: 10
|
||||
end:
|
||||
row: 13
|
||||
column: 24
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA: E501
|
||||
location:
|
||||
row: 16
|
||||
column: 10
|
||||
fix:
|
||||
content: " # noqa: F841"
|
||||
start:
|
||||
row: 16
|
||||
column: 10
|
||||
end:
|
||||
row: 16
|
||||
column: 30
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA: F841
|
||||
location:
|
||||
row: 41
|
||||
column: 4
|
||||
fix:
|
||||
content: " # noqa: E501"
|
||||
start:
|
||||
row: 41
|
||||
column: 4
|
||||
end:
|
||||
row: 41
|
||||
column: 24
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA: E501
|
||||
location:
|
||||
row: 49
|
||||
column: 4
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
row: 49
|
||||
column: 4
|
||||
end:
|
||||
row: 49
|
||||
column: 18
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA: ~
|
||||
location:
|
||||
row: 57
|
||||
column: 4
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
row: 57
|
||||
column: 4
|
||||
end:
|
||||
row: 57
|
||||
column: 12
|
||||
applied: false
|
||||
|
||||
Reference in New Issue
Block a user