Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b60242fb7 | ||
|
|
65b77feeb8 | ||
|
|
04b9c0a31d | ||
|
|
49dc8231be | ||
|
|
92ca114882 | ||
|
|
553bc7443a | ||
|
|
a3af6c1ea5 | ||
|
|
b50016fe89 | ||
|
|
2cf2805848 | ||
|
|
33fbef7700 | ||
|
|
68668a584b | ||
|
|
6cd8655d29 | ||
|
|
72a9bd3cfb | ||
|
|
58aac21a36 | ||
|
|
77e0be3464 | ||
|
|
bd08fc359d | ||
|
|
19ad6ab4f5 | ||
|
|
4b2df99e78 | ||
|
|
66975876b2 | ||
|
|
7316b120ba | ||
|
|
fec887e481 | ||
|
|
bdd32c0850 | ||
|
|
10dcd5fd0a | ||
|
|
b922e6ecc8 | ||
|
|
f59799e0c4 | ||
|
|
814ddeb7ea | ||
|
|
9315b9f459 | ||
|
|
113b5a10bf | ||
|
|
fe77fb70a1 | ||
|
|
c3f6170503 | ||
|
|
a46160f0e2 | ||
|
|
9a66cf2ffb | ||
|
|
3205473612 | ||
|
|
0bb8b14ae1 | ||
|
|
9cf4621071 | ||
|
|
bbc9ed1b21 | ||
|
|
517ca2604a | ||
|
|
348ff509c0 | ||
|
|
fb545551f8 | ||
|
|
3b33a431d6 | ||
|
|
bc95690725 | ||
|
|
9dc788d089 | ||
|
|
15f63494a7 | ||
|
|
68668c20e8 | ||
|
|
9462335371 | ||
|
|
3dd6522b4f | ||
|
|
6b935121a7 | ||
|
|
8d9d9b3204 | ||
|
|
0a506eff34 | ||
|
|
7c7489c1dd | ||
|
|
ae90eccb7f | ||
|
|
7a61edbe46 |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -82,7 +82,7 @@ jobs:
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::pedantic
|
||||
|
||||
cargo_test:
|
||||
name: "cargo test"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.134
|
||||
rev: v0.0.138
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
25
Cargo.lock
generated
25
Cargo.lock
generated
@@ -670,7 +670,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.134-dev.0"
|
||||
version = "0.0.138-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.22",
|
||||
@@ -769,10 +769,17 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.0"
|
||||
name = "globset"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr 0.2.17",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
@@ -1029,7 +1036,7 @@ checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=f2f0b7a487a8725d161fe8b3ed73a6758b21e177#f2f0b7a487a8725d161fe8b3ed73a6758b21e177"
|
||||
dependencies = [
|
||||
"chic",
|
||||
"itertools",
|
||||
@@ -1044,7 +1051,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libcst_derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=f2f0b7a487a8725d161fe8b3ed73a6758b21e177#f2f0b7a487a8725d161fe8b3ed73a6758b21e177"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -1768,7 +1775,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.134"
|
||||
version = "0.0.138"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1787,7 +1794,7 @@ dependencies = [
|
||||
"fern",
|
||||
"filetime",
|
||||
"getrandom 0.2.8",
|
||||
"glob",
|
||||
"globset",
|
||||
"insta",
|
||||
"itertools",
|
||||
"libcst",
|
||||
@@ -1818,7 +1825,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.134"
|
||||
version = "0.0.138"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.22",
|
||||
|
||||
@@ -6,8 +6,9 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.134"
|
||||
version = "0.0.138"
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
|
||||
[lib]
|
||||
name = "ruff"
|
||||
@@ -26,9 +27,9 @@ common-path = { version = "1.0.0" }
|
||||
dirs = { version = "4.0.0" }
|
||||
fern = { version = "0.6.1" }
|
||||
filetime = { version = "0.2.17" }
|
||||
glob = { version = "0.3.0" }
|
||||
globset = {version = "0.4.9" }
|
||||
itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "a13ec97dd4eb925bde4d426c6e422582793b260c" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
|
||||
log = { version = "0.4.17" }
|
||||
nohash-hasher = { version = "0.2.0" }
|
||||
notify = { version = "4.0.17" }
|
||||
|
||||
25
LICENSE
25
LICENSE
@@ -443,3 +443,28 @@ are:
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
- RustPython, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 RustPython Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
20
README.md
20
README.md
@@ -32,12 +32,14 @@ of plugins), [`isort`](https://pypi.org/project/isort/), [`pydocstyle`](https://
|
||||
and [`autoflake`](https://pypi.org/project/autoflake/) all while executing tens or hundreds of times
|
||||
faster than any individual tool.
|
||||
|
||||
(Coming from Flake8? Try [`flake8-to-ruff`](https://pypi.org/project/flake8-to-ruff/) to
|
||||
automatically convert your existing configuration.)
|
||||
Ruff is actively developed and used in major open-source projects like:
|
||||
|
||||
Ruff is actively developed and used in major open-source projects
|
||||
like [FastAPI](https://github.com/tiangolo/fastapi), [Zulip](https://github.com/zulip/zulip),
|
||||
[pydantic](https://github.com/pydantic/pydantic), and [Saleor](https://github.com/saleor/saleor).
|
||||
- [FastAPI](https://github.com/tiangolo/fastapi)
|
||||
- [Bokeh](https://github.com/bokeh/bokeh)
|
||||
- [Zulip](https://github.com/zulip/zulip)
|
||||
- [Pydantic](https://github.com/pydantic/pydantic)
|
||||
- [Saleor](https://github.com/saleor/saleor)
|
||||
- [Hatch](https://github.com/pypa/hatch)
|
||||
|
||||
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
|
||||
@@ -105,7 +107,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.116
|
||||
rev: v0.0.138
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -348,6 +350,7 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
|
||||
| F405 | ImportStarUsage | `...` may be undefined, or defined from star imports: `...` | |
|
||||
| F406 | ImportStarNotPermitted | `from ... import *` only allowed at module level | |
|
||||
| F407 | FutureFeatureNotDefined | Future feature `...` is not defined | |
|
||||
| F521 | StringDotFormatInvalidFormat | '...'.format(...) has invalid format string: ... | |
|
||||
| F541 | FStringMissingPlaceholders | f-string without any placeholders | |
|
||||
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated | |
|
||||
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated | |
|
||||
@@ -572,6 +575,7 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/
|
||||
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | |
|
||||
| B026 | StarArgUnpackingAfterKeywordArg | Star-arg unpacking after a keyword argument is strongly discouraged | |
|
||||
| B027 | EmptyMethodWithoutAbstractDecorator | `...` is an empty method in an abstract base class, but has no abstract decorator | |
|
||||
| B904 | RaiseWithoutFromInsideExcept | Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling | |
|
||||
|
||||
### flake8-builtins
|
||||
|
||||
@@ -821,7 +825,7 @@ including:
|
||||
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (25/32)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (27/32)
|
||||
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
|
||||
- [`flake8-blind-except`](https://pypi.org/project/flake8-blind-except/)
|
||||
- [`flake8-boolean-trap`](https://pypi.org/project/flake8-boolean-trap/)
|
||||
@@ -852,7 +856,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
|
||||
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (26/32)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (27/32)
|
||||
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
|
||||
- [`flake8-blind-except`](https://pypi.org/project/flake8-blind-except/)
|
||||
- [`flake8-boolean-trap`](https://pypi.org/project/flake8-boolean-trap/)
|
||||
|
||||
@@ -10,7 +10,7 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||
b.iter(|| {
|
||||
let rope = Rope::from_str(black_box(&contents));
|
||||
rope.line_to_char(black_box(4));
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
8
flake8_to_ruff/Cargo.lock
generated
8
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.134"
|
||||
version = "0.0.138"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1265,7 +1265,7 @@ checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=f2f0b7a487a8725d161fe8b3ed73a6758b21e177#f2f0b7a487a8725d161fe8b3ed73a6758b21e177"
|
||||
dependencies = [
|
||||
"chic",
|
||||
"itertools",
|
||||
@@ -1280,7 +1280,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libcst_derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=f2f0b7a487a8725d161fe8b3ed73a6758b21e177#f2f0b7a487a8725d161fe8b3ed73a6758b21e177"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.134"
|
||||
version = "0.0.138"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.134-dev.0"
|
||||
version = "0.0.138-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -18,7 +18,7 @@ pub fn convert(
|
||||
plugins: Option<Vec<Plugin>>,
|
||||
) -> Result<Pyproject> {
|
||||
// Extract all referenced check code prefixes, to power plugin inference.
|
||||
let mut referenced_codes: BTreeSet<CheckCodePrefix> = Default::default();
|
||||
let mut referenced_codes: BTreeSet<CheckCodePrefix> = BTreeSet::default();
|
||||
for (key, value) in flake8 {
|
||||
if let Some(value) = value {
|
||||
match key.as_str() {
|
||||
@@ -70,13 +70,13 @@ pub fn convert(
|
||||
.unwrap_or_default();
|
||||
|
||||
// Parse each supported option.
|
||||
let mut options: Options = Default::default();
|
||||
let mut flake8_annotations: flake8_annotations::settings::Options = Default::default();
|
||||
let mut flake8_bugbear: flake8_bugbear::settings::Options = Default::default();
|
||||
let mut flake8_quotes: flake8_quotes::settings::Options = Default::default();
|
||||
let mut flake8_tidy_imports: flake8_tidy_imports::settings::Options = Default::default();
|
||||
let mut mccabe: mccabe::settings::Options = Default::default();
|
||||
let mut pep8_naming: pep8_naming::settings::Options = Default::default();
|
||||
let mut options = Options::default();
|
||||
let mut flake8_annotations = flake8_annotations::settings::Options::default();
|
||||
let mut flake8_bugbear = flake8_bugbear::settings::Options::default();
|
||||
let mut flake8_quotes = flake8_quotes::settings::Options::default();
|
||||
let mut flake8_tidy_imports = flake8_tidy_imports::settings::Options::default();
|
||||
let mut mccabe = mccabe::settings::Options::default();
|
||||
let mut pep8_naming = pep8_naming::settings::Options::default();
|
||||
for (key, value) in flake8 {
|
||||
if let Some(value) = value {
|
||||
match key.as_str() {
|
||||
@@ -110,7 +110,7 @@ pub fn convert(
|
||||
match parser::parse_files_to_codes_mapping(value.as_ref()) {
|
||||
Ok(per_file_ignores) => {
|
||||
options.per_file_ignores =
|
||||
Some(parser::collect_per_file_ignores(per_file_ignores))
|
||||
Some(parser::collect_per_file_ignores(per_file_ignores));
|
||||
}
|
||||
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
|
||||
}
|
||||
@@ -181,7 +181,7 @@ pub fn convert(
|
||||
"ban-relative-imports" | "ban_relative_imports" => match value.trim() {
|
||||
"true" => flake8_tidy_imports.ban_relative_imports = Some(Strictness::All),
|
||||
"parents" => {
|
||||
flake8_tidy_imports.ban_relative_imports = Some(Strictness::Parents)
|
||||
flake8_tidy_imports.ban_relative_imports = Some(Strictness::Parents);
|
||||
}
|
||||
_ => eprintln!("Unexpected '{key}' value: {value}"),
|
||||
},
|
||||
@@ -203,22 +203,22 @@ pub fn convert(
|
||||
// Deduplicate and sort.
|
||||
options.select = Some(Vec::from_iter(select));
|
||||
options.ignore = Some(Vec::from_iter(ignore));
|
||||
if flake8_annotations != Default::default() {
|
||||
if flake8_annotations != flake8_annotations::settings::Options::default() {
|
||||
options.flake8_annotations = Some(flake8_annotations);
|
||||
}
|
||||
if flake8_bugbear != Default::default() {
|
||||
if flake8_bugbear != flake8_bugbear::settings::Options::default() {
|
||||
options.flake8_bugbear = Some(flake8_bugbear);
|
||||
}
|
||||
if flake8_quotes != Default::default() {
|
||||
if flake8_quotes != flake8_quotes::settings::Options::default() {
|
||||
options.flake8_quotes = Some(flake8_quotes);
|
||||
}
|
||||
if flake8_tidy_imports != Default::default() {
|
||||
if flake8_tidy_imports != flake8_tidy_imports::settings::Options::default() {
|
||||
options.flake8_tidy_imports = Some(flake8_tidy_imports);
|
||||
}
|
||||
if mccabe != Default::default() {
|
||||
if mccabe != mccabe::settings::Options::default() {
|
||||
options.mccabe = Some(mccabe);
|
||||
}
|
||||
if pep8_naming != Default::default() {
|
||||
if pep8_naming != pep8_naming::settings::Options::default() {
|
||||
options.pep8_naming = Some(pep8_naming);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
#![allow(clippy::collapsible_if, clippy::collapsible_else_if)]
|
||||
#![allow(
|
||||
clippy::collapsible_else_if,
|
||||
clippy::collapsible_if,
|
||||
clippy::implicit_hasher,
|
||||
clippy::match_same_arms,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::must_use_candidate,
|
||||
clippy::similar_names,
|
||||
clippy::too_many_lines
|
||||
)]
|
||||
|
||||
pub mod converter;
|
||||
mod parser;
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
//! Utility to generate Ruff's pyproject.toml section from a Flake8 INI file.
|
||||
#![allow(
|
||||
clippy::collapsible_else_if,
|
||||
clippy::collapsible_if,
|
||||
clippy::implicit_hasher,
|
||||
clippy::match_same_arms,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::must_use_candidate,
|
||||
clippy::similar_names,
|
||||
clippy::too_many_lines
|
||||
)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ pub fn parse_prefix_codes(value: &str) -> Vec<CheckCodePrefix> {
|
||||
pub fn parse_strings(value: &str) -> Vec<String> {
|
||||
COMMA_SEPARATED_LIST_RE
|
||||
.split(value)
|
||||
.map(|part| part.trim())
|
||||
.map(str::trim)
|
||||
.filter(|part| !part.is_empty())
|
||||
.map(String::from)
|
||||
.collect()
|
||||
@@ -92,7 +92,7 @@ impl State {
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(_) => eprintln!("Skipping unrecognized prefix: {}", code),
|
||||
Err(_) => eprintln!("Skipping unrecognized prefix: {code}"),
|
||||
}
|
||||
}
|
||||
codes
|
||||
@@ -129,14 +129,14 @@ fn tokenize_files_to_codes_mapping(value: &str) -> Vec<Token> {
|
||||
}
|
||||
tokens.push(Token {
|
||||
token_name: TokenType::Eof,
|
||||
src: "".to_string(),
|
||||
src: String::new(),
|
||||
});
|
||||
tokens
|
||||
}
|
||||
|
||||
/// Parse a 'files-to-codes' mapping, mimicking Flake8's internal logic.
|
||||
///
|
||||
/// See: https://github.com/PyCQA/flake8/blob/7dfe99616fc2f07c0017df2ba5fa884158f3ea8a/src/flake8/utils.py#L45
|
||||
/// See: <https://github.com/PyCQA/flake8/blob/7dfe99616fc2f07c0017df2ba5fa884158f3ea8a/src/flake8/utils.py#L45>
|
||||
pub fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<PatternPrefixPair>> {
|
||||
if value.trim().is_empty() {
|
||||
return Ok(vec![]);
|
||||
|
||||
55
resources/test/fixtures/B904.py
vendored
Normal file
55
resources/test/fixtures/B904.py
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
Should emit:
|
||||
B904 - on lines 10, 11 and 16
|
||||
"""
|
||||
|
||||
try:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
if "abc":
|
||||
raise TypeError
|
||||
raise UserWarning
|
||||
except AssertionError:
|
||||
raise # Bare `raise` should not be an error
|
||||
except Exception as err:
|
||||
assert err
|
||||
raise Exception("No cause here...")
|
||||
except BaseException as base_err:
|
||||
# Might use this instead of bare raise with the `.with_traceback()` method
|
||||
raise base_err
|
||||
finally:
|
||||
raise Exception("Nothing to chain from, so no warning here")
|
||||
|
||||
try:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
# should not emit, since we are not raising something
|
||||
def proxy():
|
||||
raise NameError
|
||||
|
||||
|
||||
try:
|
||||
from preferred_library import Thing
|
||||
except ImportError:
|
||||
try:
|
||||
from fallback_library import Thing
|
||||
except ImportError:
|
||||
|
||||
class Thing:
|
||||
def __getattr__(self, name):
|
||||
# same as the case above, should not emit.
|
||||
raise AttributeError
|
||||
|
||||
|
||||
try:
|
||||
from preferred_library import Thing
|
||||
except ImportError:
|
||||
try:
|
||||
from fallback_library import Thing
|
||||
except ImportError:
|
||||
|
||||
def context_switch():
|
||||
try:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
raise
|
||||
29
resources/test/fixtures/F521.py
vendored
Normal file
29
resources/test/fixtures/F521.py
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
"{".format(1)
|
||||
"}".format(1)
|
||||
"{foo[}".format(foo=1)
|
||||
# too much string recursion (placeholder-in-placeholder)
|
||||
"{:{:{}}}".format(1, 2, 3)
|
||||
# ruff picks these issues up, but flake8 doesn't
|
||||
"{foo[]}".format(foo={"": 1})
|
||||
"{foo..}".format(foo=1)
|
||||
"{foo..bar}".format(foo=1)
|
||||
|
||||
# "{} {1}".format(1, 2) # F525
|
||||
# "{0} {}".format(1, 2) # F525
|
||||
# "{}".format(1, 2) # F523
|
||||
# "{}".format(1, bar=2) # F522
|
||||
# "{} {}".format(1) # F524
|
||||
# "{2}".format() # F524
|
||||
# "{bar}".format() # F524
|
||||
|
||||
# The following are all "good" uses of .format
|
||||
"{.__class__}".format("")
|
||||
"{foo[bar]}".format(foo={"bar": "barv"})
|
||||
"{[bar]}".format({"bar": "barv"})
|
||||
"{:{}} {}".format(1, 15, 2)
|
||||
"{:2}".format(1)
|
||||
"{foo}-{}".format(1, foo=2)
|
||||
a = ()
|
||||
"{}".format(*a)
|
||||
k = {}
|
||||
"{foo}".format(**k)
|
||||
13
resources/test/fixtures/F841.py
vendored
13
resources/test/fixtures/F841.py
vendored
@@ -52,3 +52,16 @@ def f5():
|
||||
|
||||
def f7():
|
||||
nonlocal b
|
||||
|
||||
|
||||
def f6():
|
||||
annotations = []
|
||||
assert len([annotations for annotations in annotations])
|
||||
|
||||
|
||||
def f7():
|
||||
def connect():
|
||||
return None, None
|
||||
|
||||
with connect() as (connection, cursor):
|
||||
cursor.execute("SELECT * FROM users")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.134"
|
||||
version = "0.0.138"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -8,7 +8,7 @@ anyhow = { version = "1.0.66" }
|
||||
clap = { version = "4.0.1", features = ["derive"] }
|
||||
codegen = { version = "0.2.0" }
|
||||
itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "a13ec97dd4eb925bde4d426c6e422582793b260c" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
|
||||
ruff = { path = ".." }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
|
||||
|
||||
@@ -24,7 +24,7 @@ pub struct Cli {
|
||||
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
// Build up a map from prefix to matching CheckCodes.
|
||||
let mut prefix_to_codes: BTreeMap<String, BTreeSet<CheckCode>> = Default::default();
|
||||
let mut prefix_to_codes: BTreeMap<String, BTreeSet<CheckCode>> = BTreeMap::default();
|
||||
for check_code in CheckCode::iter() {
|
||||
let as_ref: String = check_code.as_ref().to_string();
|
||||
let prefix_len = as_ref
|
||||
@@ -106,7 +106,7 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
2 => "Tens",
|
||||
1 => "Hundreds",
|
||||
0 => "Category",
|
||||
_ => panic!("Invalid prefix: {}", prefix),
|
||||
_ => panic!("Invalid prefix: {prefix}"),
|
||||
};
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => PrefixSpecificity::{},",
|
||||
@@ -132,10 +132,10 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
|
||||
// Write the output to `src/checks_gen.rs` (or stdout).
|
||||
if cli.dry_run {
|
||||
println!("{}", output);
|
||||
println!("{output}");
|
||||
} else {
|
||||
let mut f = OpenOptions::new().write(true).truncate(true).open(FILE)?;
|
||||
write!(f, "{}", output)?;
|
||||
write!(f, "{output}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -61,7 +61,7 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
}
|
||||
|
||||
if cli.dry_run {
|
||||
print!("{}", output);
|
||||
print!("{output}");
|
||||
} else {
|
||||
// Read the existing file.
|
||||
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
@@ -84,9 +84,9 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
|
||||
// Write the prefix, new contents, and suffix.
|
||||
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
|
||||
write!(f, "{}\n\n", prefix)?;
|
||||
write!(f, "{}", output)?;
|
||||
write!(f, "{}", suffix)?;
|
||||
write!(f, "{prefix}\n\n")?;
|
||||
write!(f, "{output}")?;
|
||||
write!(f, "{suffix}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -19,7 +19,7 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
let contents = fs::read_to_string(&cli.file)?;
|
||||
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
|
||||
let mut generator = SourceGenerator::new();
|
||||
generator.unparse_suite(&python_ast)?;
|
||||
generator.unparse_suite(&python_ast);
|
||||
println!("{}", generator.generate()?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
#![allow(
|
||||
clippy::collapsible_else_if,
|
||||
clippy::collapsible_if,
|
||||
clippy::implicit_hasher,
|
||||
clippy::match_same_arms,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::must_use_candidate,
|
||||
clippy::similar_names,
|
||||
clippy::too_many_lines
|
||||
)]
|
||||
|
||||
pub mod generate_check_code_prefix;
|
||||
pub mod generate_rules_table;
|
||||
pub mod generate_source_code;
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
#![allow(
|
||||
clippy::collapsible_else_if,
|
||||
clippy::collapsible_if,
|
||||
clippy::implicit_hasher,
|
||||
clippy::match_same_arms,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::must_use_candidate,
|
||||
clippy::similar_names,
|
||||
clippy::too_many_lines
|
||||
)]
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use ruff_dev::{
|
||||
|
||||
@@ -17,6 +17,6 @@ pub struct Cli {
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
let contents = fs::read_to_string(&cli.file)?;
|
||||
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
|
||||
println!("{:#?}", python_ast);
|
||||
println!("{python_ast:#?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Print the LibCST CST for a given Python file.
|
||||
//! Print the `LibCST` CST for a given Python file.
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
@@ -17,7 +17,7 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
let contents = fs::read_to_string(&cli.file)?;
|
||||
match libcst_native::parse_module(&contents, None) {
|
||||
Ok(python_cst) => {
|
||||
println!("{:#?}", python_cst);
|
||||
println!("{python_cst:#?}");
|
||||
Ok(())
|
||||
}
|
||||
Err(_) => Err(anyhow::anyhow!("Failed to parse CST")),
|
||||
|
||||
@@ -17,7 +17,7 @@ pub struct Cli {
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
let contents = fs::read_to_string(&cli.file)?;
|
||||
for (_, tok, _) in lexer::make_tokenizer(&contents).flatten() {
|
||||
println!("{:#?}", tok);
|
||||
println!("{tok:#?}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location,
|
||||
use crate::ast::types::Range;
|
||||
use crate::SourceCodeLocator;
|
||||
|
||||
#[inline(always)]
|
||||
fn collect_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
|
||||
match &expr.node {
|
||||
ExprKind::Call { func, .. } => {
|
||||
@@ -24,7 +23,6 @@ fn collect_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
|
||||
}
|
||||
|
||||
/// Convert an `Expr` to its call path (like `List`, or `typing.List`).
|
||||
#[inline(always)]
|
||||
pub fn compose_call_path(expr: &Expr) -> Option<String> {
|
||||
let segments = collect_call_paths(expr);
|
||||
if segments.is_empty() {
|
||||
@@ -35,7 +33,6 @@ pub fn compose_call_path(expr: &Expr) -> Option<String> {
|
||||
}
|
||||
|
||||
/// Convert an `Expr` to its call path segments (like ["typing", "List"]).
|
||||
#[inline(always)]
|
||||
pub fn collect_call_paths(expr: &Expr) -> Vec<&str> {
|
||||
let mut segments = vec![];
|
||||
collect_call_path_inner(expr, &mut segments);
|
||||
@@ -121,10 +118,9 @@ pub fn match_call_path(
|
||||
// `Match`).
|
||||
if num_segments == 0 {
|
||||
module.is_empty()
|
||||
|| from_imports
|
||||
.get(module)
|
||||
.map(|imports| imports.contains(member) || imports.contains("*"))
|
||||
.unwrap_or(false)
|
||||
|| from_imports.get(module).map_or(false, |imports| {
|
||||
imports.contains(member) || imports.contains("*")
|
||||
})
|
||||
} else {
|
||||
let components: Vec<&str> = module.split('.').collect();
|
||||
|
||||
@@ -148,8 +144,7 @@ pub fn match_call_path(
|
||||
let member = components[cut];
|
||||
if from_imports
|
||||
.get(&module.as_str())
|
||||
.map(|imports| imports.contains(member))
|
||||
.unwrap_or(false)
|
||||
.map_or(false, |imports| imports.contains(member))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ use crate::ast::types::{BindingKind, Scope};
|
||||
|
||||
/// Extract the names bound to a given __all__ assignment.
|
||||
pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
|
||||
let mut names: Vec<String> = vec![];
|
||||
|
||||
fn add_to_names(names: &mut Vec<String>, elts: &[Expr]) {
|
||||
for elt in elts {
|
||||
if let ExprKind::Constant {
|
||||
@@ -13,11 +11,13 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
|
||||
..
|
||||
} = &elt.node
|
||||
{
|
||||
names.push(value.to_string())
|
||||
names.push(value.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut names: Vec<String> = vec![];
|
||||
|
||||
// Grab the existing bound __all__ values.
|
||||
if let StmtKind::AugAssign { .. } = &stmt.node {
|
||||
if let Some(binding) = scope.values.get("__all__") {
|
||||
@@ -35,7 +35,7 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
|
||||
} {
|
||||
match &value.node {
|
||||
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
|
||||
add_to_names(&mut names, elts)
|
||||
add_to_names(&mut names, elts);
|
||||
}
|
||||
ExprKind::BinOp { left, right, .. } => {
|
||||
let mut current_left = left;
|
||||
|
||||
@@ -19,8 +19,8 @@ pub trait Visitor<'a> {
|
||||
fn visit_constant(&mut self, constant: &'a Constant) {
|
||||
walk_constant(self, constant);
|
||||
}
|
||||
fn visit_expr_context(&mut self, expr_content: &'a ExprContext) {
|
||||
walk_expr_context(self, expr_content);
|
||||
fn visit_expr_context(&mut self, expr_context: &'a ExprContext) {
|
||||
walk_expr_context(self, expr_context);
|
||||
}
|
||||
fn visit_boolop(&mut self, boolop: &'a Boolop) {
|
||||
walk_boolop(self, boolop);
|
||||
@@ -249,7 +249,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
||||
visitor.visit_stmt(stmt);
|
||||
}
|
||||
for excepthandler in handlers {
|
||||
visitor.visit_excepthandler(excepthandler)
|
||||
visitor.visit_excepthandler(excepthandler);
|
||||
}
|
||||
for stmt in orelse {
|
||||
visitor.visit_stmt(stmt);
|
||||
@@ -447,7 +447,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
|
||||
pub fn walk_constant<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, constant: &'a Constant) {
|
||||
if let Constant::Tuple(constants) = constant {
|
||||
for constant in constants {
|
||||
visitor.visit_constant(constant)
|
||||
visitor.visit_constant(constant);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -456,8 +456,8 @@ pub fn walk_comprehension<'a, V: Visitor<'a> + ?Sized>(
|
||||
visitor: &mut V,
|
||||
comprehension: &'a Comprehension,
|
||||
) {
|
||||
visitor.visit_expr(&comprehension.target);
|
||||
visitor.visit_expr(&comprehension.iter);
|
||||
visitor.visit_expr(&comprehension.target);
|
||||
for expr in &comprehension.ifs {
|
||||
visitor.visit_expr(expr);
|
||||
}
|
||||
@@ -577,7 +577,6 @@ pub fn walk_pattern<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, pattern: &'a P
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_expr_context<'a, V: Visitor<'a> + ?Sized>(
|
||||
visitor: &mut V,
|
||||
expr_context: &'a ExprContext,
|
||||
@@ -585,21 +584,16 @@ pub fn walk_expr_context<'a, V: Visitor<'a> + ?Sized>(
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_boolop<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, boolop: &'a Boolop) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_operator<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, operator: &'a Operator) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_unaryop<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, unaryop: &'a Unaryop) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_cmpop<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, cmpop: &'a Cmpop) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_alias<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, alias: &'a Alias) {}
|
||||
|
||||
@@ -19,7 +19,7 @@ pub fn leading_space(line: &str) -> String {
|
||||
}
|
||||
|
||||
/// Extract the leading indentation from a line.
|
||||
pub fn indentation<'a, T>(checker: &'a Checker, located: &Located<T>) -> String {
|
||||
pub fn indentation<T>(checker: &Checker, located: &Located<T>) -> String {
|
||||
let range = Range::from_located(located);
|
||||
checker
|
||||
.locator
|
||||
|
||||
@@ -10,8 +10,6 @@ use crate::autofix::{Fix, Patch};
|
||||
use crate::checks::Check;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
// TODO(charlie): The model here is awkward because `Apply` is only relevant at
|
||||
// higher levels in the execution flow.
|
||||
#[derive(Hash)]
|
||||
pub enum Mode {
|
||||
Generate,
|
||||
@@ -19,55 +17,55 @@ pub enum Mode {
|
||||
None,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
/// Return `true` if a patch should be generated under the given `Mode`.
|
||||
pub fn patch(&self) -> bool {
|
||||
match &self {
|
||||
Mode::Generate => true,
|
||||
Mode::Apply => true,
|
||||
Mode::None => false,
|
||||
impl From<bool> for Mode {
|
||||
fn from(value: bool) -> Self {
|
||||
if value {
|
||||
Mode::Apply
|
||||
} else {
|
||||
Mode::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Mode {
|
||||
fn from(value: bool) -> Self {
|
||||
impl From<&Mode> for bool {
|
||||
fn from(value: &Mode) -> Self {
|
||||
match value {
|
||||
true => Mode::Apply,
|
||||
false => Mode::None,
|
||||
Mode::Generate | Mode::Apply => true,
|
||||
Mode::None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Auto-fix errors in a file, and write the fixed source code to disk.
|
||||
pub fn fix_file<'a>(
|
||||
checks: &'a mut [Check],
|
||||
checks: &'a [Check],
|
||||
locator: &'a SourceCodeLocator<'a>,
|
||||
) -> Option<Cow<'a, str>> {
|
||||
) -> Option<(Cow<'a, str>, usize)> {
|
||||
if checks.iter().all(|check| check.fix.is_none()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(apply_fixes(
|
||||
checks.iter_mut().filter_map(|check| check.fix.as_mut()),
|
||||
checks.iter().filter_map(|check| check.fix.as_ref()),
|
||||
locator,
|
||||
))
|
||||
}
|
||||
|
||||
/// Apply a series of fixes.
|
||||
fn apply_fixes<'a>(
|
||||
fixes: impl Iterator<Item = &'a mut Fix>,
|
||||
fixes: impl Iterator<Item = &'a Fix>,
|
||||
locator: &'a SourceCodeLocator<'a>,
|
||||
) -> Cow<'a, str> {
|
||||
) -> (Cow<'a, str>, usize) {
|
||||
let mut output = RopeBuilder::new();
|
||||
let mut last_pos: Location = Location::new(1, 0);
|
||||
let mut applied: BTreeSet<&Patch> = BTreeSet::default();
|
||||
let mut num_fixed: usize = 0;
|
||||
|
||||
for fix in fixes.sorted_by_key(|fix| fix.patch.location) {
|
||||
// If we already applied an identical fix as part of another correction, skip
|
||||
// any re-application.
|
||||
if applied.contains(&fix.patch) {
|
||||
fix.applied = true;
|
||||
num_fixed += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -90,19 +88,18 @@ fn apply_fixes<'a>(
|
||||
// Track that the fix was applied.
|
||||
last_pos = fix.patch.end_location;
|
||||
applied.insert(&fix.patch);
|
||||
fix.applied = true;
|
||||
num_fixed += 1;
|
||||
}
|
||||
|
||||
// Add the remaining content.
|
||||
let slice = locator.slice_source_code_at(last_pos);
|
||||
output.append(&slice);
|
||||
|
||||
Cow::from(output.finish())
|
||||
(Cow::from(output.finish()), num_fixed)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::autofix::fixer::apply_fixes;
|
||||
@@ -110,115 +107,117 @@ mod tests {
|
||||
use crate::SourceCodeLocator;
|
||||
|
||||
#[test]
|
||||
fn empty_file() -> Result<()> {
|
||||
let mut fixes = vec![];
|
||||
let locator = SourceCodeLocator::new("");
|
||||
let actual = apply_fixes(fixes.iter_mut(), &locator);
|
||||
let expected = "";
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
fn empty_file() {
|
||||
let fixes = vec![];
|
||||
let locator = SourceCodeLocator::new(r#""#);
|
||||
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
|
||||
assert_eq!(contents, "");
|
||||
assert_eq!(fixed, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_single_replacement() -> Result<()> {
|
||||
let mut fixes = vec![Fix {
|
||||
fn apply_single_replacement() {
|
||||
let fixes = vec![Fix {
|
||||
patch: Patch {
|
||||
content: "Bar".to_string(),
|
||||
location: Location::new(1, 8),
|
||||
end_location: Location::new(1, 14),
|
||||
},
|
||||
applied: false,
|
||||
}];
|
||||
let locator = SourceCodeLocator::new(
|
||||
"class A(object):
|
||||
...
|
||||
",
|
||||
r#"
|
||||
class A(object):
|
||||
...
|
||||
"#
|
||||
.trim(),
|
||||
);
|
||||
let actual = apply_fixes(fixes.iter_mut(), &locator);
|
||||
|
||||
let expected = "class A(Bar):
|
||||
...
|
||||
";
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
|
||||
assert_eq!(
|
||||
contents,
|
||||
r#"
|
||||
class A(Bar):
|
||||
...
|
||||
"#
|
||||
.trim(),
|
||||
);
|
||||
assert_eq!(fixed, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_single_removal() -> Result<()> {
|
||||
let mut fixes = vec![Fix {
|
||||
fn apply_single_removal() {
|
||||
let fixes = vec![Fix {
|
||||
patch: Patch {
|
||||
content: "".to_string(),
|
||||
content: String::new(),
|
||||
location: Location::new(1, 7),
|
||||
end_location: Location::new(1, 15),
|
||||
},
|
||||
applied: false,
|
||||
}];
|
||||
let locator = SourceCodeLocator::new(
|
||||
"class A(object):
|
||||
...
|
||||
",
|
||||
r#"
|
||||
class A(object):
|
||||
...
|
||||
"#
|
||||
.trim(),
|
||||
);
|
||||
let actual = apply_fixes(fixes.iter_mut(), &locator);
|
||||
|
||||
let expected = "class A:
|
||||
...
|
||||
";
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
|
||||
assert_eq!(
|
||||
contents,
|
||||
r#"
|
||||
class A:
|
||||
...
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
assert_eq!(fixed, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_double_removal() -> Result<()> {
|
||||
let mut fixes = vec![
|
||||
fn apply_double_removal() {
|
||||
let fixes = vec![
|
||||
Fix {
|
||||
patch: Patch {
|
||||
content: "".to_string(),
|
||||
content: String::new(),
|
||||
location: Location::new(1, 7),
|
||||
end_location: Location::new(1, 16),
|
||||
},
|
||||
applied: false,
|
||||
},
|
||||
Fix {
|
||||
patch: Patch {
|
||||
content: "".to_string(),
|
||||
content: String::new(),
|
||||
location: Location::new(1, 16),
|
||||
end_location: Location::new(1, 23),
|
||||
},
|
||||
applied: false,
|
||||
},
|
||||
];
|
||||
let locator = SourceCodeLocator::new(
|
||||
"class A(object, object):
|
||||
...
|
||||
",
|
||||
r#"
|
||||
class A(object, object):
|
||||
...
|
||||
"#
|
||||
.trim(),
|
||||
);
|
||||
let actual = apply_fixes(fixes.iter_mut(), &locator);
|
||||
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
|
||||
|
||||
let expected = "class A:
|
||||
...
|
||||
";
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
assert_eq!(
|
||||
contents,
|
||||
r#"
|
||||
class A:
|
||||
...
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
assert_eq!(fixed, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_overlapping_fixes() -> Result<()> {
|
||||
let mut fixes = vec![
|
||||
fn ignore_overlapping_fixes() {
|
||||
let fixes = vec![
|
||||
Fix {
|
||||
patch: Patch {
|
||||
content: "".to_string(),
|
||||
content: String::new(),
|
||||
location: Location::new(1, 7),
|
||||
end_location: Location::new(1, 15),
|
||||
},
|
||||
applied: false,
|
||||
},
|
||||
Fix {
|
||||
patch: Patch {
|
||||
@@ -226,22 +225,24 @@ mod tests {
|
||||
location: Location::new(1, 9),
|
||||
end_location: Location::new(1, 11),
|
||||
},
|
||||
applied: false,
|
||||
},
|
||||
];
|
||||
let locator = SourceCodeLocator::new(
|
||||
"class A(object):
|
||||
r#"
|
||||
class A(object):
|
||||
...
|
||||
",
|
||||
"#
|
||||
.trim(),
|
||||
);
|
||||
let actual = apply_fixes(fixes.iter_mut(), &locator);
|
||||
|
||||
let expected = "class A:
|
||||
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
|
||||
assert_eq!(
|
||||
contents,
|
||||
r#"
|
||||
class A:
|
||||
...
|
||||
";
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
"#
|
||||
.trim(),
|
||||
);
|
||||
assert_eq!(fixed, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,18 +14,16 @@ pub struct Patch {
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Fix {
|
||||
pub patch: Patch,
|
||||
pub applied: bool,
|
||||
}
|
||||
|
||||
impl Fix {
|
||||
pub fn deletion(start: Location, end: Location) -> Self {
|
||||
Self {
|
||||
patch: Patch {
|
||||
content: "".to_string(),
|
||||
content: String::new(),
|
||||
location: start,
|
||||
end_location: end,
|
||||
},
|
||||
applied: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +34,6 @@ impl Fix {
|
||||
location: start,
|
||||
end_location: end,
|
||||
},
|
||||
applied: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,18 +44,16 @@ impl Fix {
|
||||
location: at,
|
||||
end_location: at,
|
||||
},
|
||||
applied: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dummy(location: Location) -> Self {
|
||||
Self {
|
||||
patch: Patch {
|
||||
content: "".to_string(),
|
||||
content: String::new(),
|
||||
location,
|
||||
end_location: location,
|
||||
},
|
||||
applied: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,9 +63,10 @@ impl Mode {
|
||||
|
||||
impl From<bool> for Mode {
|
||||
fn from(value: bool) -> Self {
|
||||
match value {
|
||||
true => Mode::ReadWrite,
|
||||
false => Mode::None,
|
||||
if value {
|
||||
Mode::ReadWrite
|
||||
} else {
|
||||
Mode::None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,6 +178,6 @@ pub fn set(
|
||||
cache_key(path, settings, autofix),
|
||||
&bincode::serialize(&check_result).unwrap(),
|
||||
) {
|
||||
error!("Failed to write to cache: {e:?}")
|
||||
error!("Failed to write to cache: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
184
src/check_ast.rs
184
src/check_ast.rs
@@ -1,12 +1,12 @@
|
||||
//! Lint rules based on AST traversal.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::Withitem;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
|
||||
KeywordData, Operator, Stmt, StmtKind, Suite,
|
||||
@@ -19,11 +19,11 @@ use crate::ast::helpers::{
|
||||
use crate::ast::operations::extract_all_names;
|
||||
use crate::ast::relocate::relocate_expr;
|
||||
use crate::ast::types::{
|
||||
Binding, BindingContext, BindingKind, ClassScope, ImportKind, Range, Scope, ScopeKind,
|
||||
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, ImportKind, Range, Scope,
|
||||
ScopeKind,
|
||||
};
|
||||
use crate::ast::visitor::{walk_excepthandler, Visitor};
|
||||
use crate::ast::visitor::{walk_excepthandler, walk_withitem, Visitor};
|
||||
use crate::ast::{helpers, operations, visitor};
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
|
||||
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
@@ -42,10 +42,11 @@ use crate::{
|
||||
|
||||
const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Checker<'a> {
|
||||
// Input data.
|
||||
path: &'a Path,
|
||||
autofix: &'a fixer::Mode,
|
||||
autofix: bool,
|
||||
pub(crate) settings: &'a Settings,
|
||||
pub(crate) locator: &'a SourceCodeLocator<'a>,
|
||||
// Computed checks.
|
||||
@@ -77,6 +78,7 @@ pub struct Checker<'a> {
|
||||
in_deferred_string_annotation: bool,
|
||||
in_literal: bool,
|
||||
in_subscript: bool,
|
||||
in_withitem: bool,
|
||||
seen_import_boundary: bool,
|
||||
futures_allowed: bool,
|
||||
annotations_future_enabled: bool,
|
||||
@@ -86,7 +88,7 @@ pub struct Checker<'a> {
|
||||
impl<'a> Checker<'a> {
|
||||
pub fn new(
|
||||
settings: &'a Settings,
|
||||
autofix: &'a fixer::Mode,
|
||||
autofix: bool,
|
||||
path: &'a Path,
|
||||
locator: &'a SourceCodeLocator,
|
||||
) -> Checker<'a> {
|
||||
@@ -95,40 +97,40 @@ impl<'a> Checker<'a> {
|
||||
autofix,
|
||||
path,
|
||||
locator,
|
||||
checks: Default::default(),
|
||||
definitions: Default::default(),
|
||||
deletions: Default::default(),
|
||||
from_imports: Default::default(),
|
||||
import_aliases: Default::default(),
|
||||
parents: Default::default(),
|
||||
parent_stack: Default::default(),
|
||||
scopes: Default::default(),
|
||||
scope_stack: Default::default(),
|
||||
dead_scopes: Default::default(),
|
||||
deferred_string_annotations: Default::default(),
|
||||
deferred_annotations: Default::default(),
|
||||
deferred_functions: Default::default(),
|
||||
deferred_lambdas: Default::default(),
|
||||
deferred_assignments: Default::default(),
|
||||
checks: vec![],
|
||||
definitions: vec![],
|
||||
deletions: FxHashSet::default(),
|
||||
from_imports: FxHashMap::default(),
|
||||
import_aliases: FxHashMap::default(),
|
||||
parents: vec![],
|
||||
parent_stack: vec![],
|
||||
scopes: vec![],
|
||||
scope_stack: vec![],
|
||||
dead_scopes: vec![],
|
||||
deferred_string_annotations: vec![],
|
||||
deferred_annotations: vec![],
|
||||
deferred_functions: vec![],
|
||||
deferred_lambdas: vec![],
|
||||
deferred_assignments: vec![],
|
||||
// Internal, derivative state.
|
||||
visible_scope: VisibleScope {
|
||||
modifier: Modifier::Module,
|
||||
visibility: module_visibility(path),
|
||||
},
|
||||
in_f_string: Default::default(),
|
||||
in_annotation: Default::default(),
|
||||
in_deferred_string_annotation: Default::default(),
|
||||
in_literal: Default::default(),
|
||||
in_subscript: Default::default(),
|
||||
seen_import_boundary: Default::default(),
|
||||
in_f_string: None,
|
||||
in_annotation: false,
|
||||
in_deferred_string_annotation: false,
|
||||
in_literal: false,
|
||||
in_subscript: false,
|
||||
in_withitem: false,
|
||||
seen_import_boundary: false,
|
||||
futures_allowed: true,
|
||||
annotations_future_enabled: Default::default(),
|
||||
except_handlers: Default::default(),
|
||||
annotations_future_enabled: false,
|
||||
except_handlers: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a `Check` to the `Checker`.
|
||||
#[inline(always)]
|
||||
pub(crate) fn add_check(&mut self, check: Check) {
|
||||
// If we're in an f-string, override the location. RustPython doesn't produce
|
||||
// reliable locations for expressions within f-strings, so we use the
|
||||
@@ -145,7 +147,6 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
/// Add multiple `Check` items to the `Checker`.
|
||||
#[inline(always)]
|
||||
pub(crate) fn add_checks(&mut self, checks: impl Iterator<Item = Check>) {
|
||||
for check in checks {
|
||||
self.add_check(check);
|
||||
@@ -157,7 +158,7 @@ impl<'a> Checker<'a> {
|
||||
pub fn patch(&self, code: &CheckCode) -> bool {
|
||||
// TODO(charlie): We can't fix errors in f-strings until RustPython adds
|
||||
// location data.
|
||||
self.autofix.patch() && self.in_f_string.is_none() && self.settings.fixable.contains(code)
|
||||
self.autofix && self.in_f_string.is_none() && self.settings.fixable.contains(code)
|
||||
}
|
||||
|
||||
/// Return `true` if the `Expr` is a reference to `typing.${target}`.
|
||||
@@ -536,20 +537,20 @@ where
|
||||
self.check_builtin_shadowing(name, Range::from_located(stmt), false);
|
||||
|
||||
for expr in bases {
|
||||
self.visit_expr(expr)
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
for keyword in keywords {
|
||||
self.visit_keyword(keyword)
|
||||
self.visit_keyword(keyword);
|
||||
}
|
||||
for expr in decorator_list {
|
||||
self.visit_expr(expr)
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.push_scope(Scope::new(ScopeKind::Class(ClassScope {
|
||||
name,
|
||||
bases,
|
||||
keywords,
|
||||
decorator_list,
|
||||
})))
|
||||
})));
|
||||
}
|
||||
StmtKind::Import { names } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E402) {
|
||||
@@ -578,7 +579,7 @@ where
|
||||
used: None,
|
||||
range: Range::from_located(stmt),
|
||||
},
|
||||
)
|
||||
);
|
||||
} else {
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
self.check_builtin_shadowing(asname, Range::from_located(stmt), false);
|
||||
@@ -603,8 +604,7 @@ where
|
||||
.node
|
||||
.asname
|
||||
.as_ref()
|
||||
.map(|asname| asname == &alias.node.name)
|
||||
.unwrap_or(false)
|
||||
.map_or(false, |asname| asname == &alias.node.name)
|
||||
{
|
||||
Some((
|
||||
self.scopes[*(self
|
||||
@@ -619,7 +619,7 @@ where
|
||||
},
|
||||
range: Range::from_located(stmt),
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
@@ -695,7 +695,7 @@ where
|
||||
.iter()
|
||||
.filter(|alias| alias.node.asname.is_none())
|
||||
.map(|alias| alias.node.name.as_str()),
|
||||
)
|
||||
);
|
||||
}
|
||||
for alias in names {
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
@@ -744,7 +744,7 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::F407) {
|
||||
if !ALL_FEATURE_NAMES.contains(&alias.node.name.deref()) {
|
||||
if !ALL_FEATURE_NAMES.contains(&&*alias.node.name) {
|
||||
self.add_check(Check::new(
|
||||
CheckKind::FutureFeatureNotDefined(alias.node.name.to_string()),
|
||||
Range::from_located(stmt),
|
||||
@@ -807,7 +807,7 @@ where
|
||||
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
|
||||
let full_name = match module {
|
||||
None => alias.node.name.to_string(),
|
||||
Some(parent) => format!("{}.{}", parent, alias.node.name),
|
||||
Some(parent) => format!("{parent}.{}", alias.node.name),
|
||||
};
|
||||
self.add_binding(
|
||||
name,
|
||||
@@ -823,8 +823,7 @@ where
|
||||
.node
|
||||
.asname
|
||||
.as_ref()
|
||||
.map(|asname| asname == &alias.node.name)
|
||||
.unwrap_or(false)
|
||||
.map_or(false, |asname| asname == &alias.node.name)
|
||||
{
|
||||
Some((
|
||||
self.scopes[*(self
|
||||
@@ -839,7 +838,7 @@ where
|
||||
},
|
||||
range: Range::from_located(stmt),
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::I252) {
|
||||
@@ -940,7 +939,7 @@ where
|
||||
self,
|
||||
stmt,
|
||||
test,
|
||||
msg.as_ref().map(|expr| expr.deref()),
|
||||
msg.as_ref().map(|expr| &**expr),
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::S101) {
|
||||
@@ -983,7 +982,7 @@ where
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E731) {
|
||||
if let [target] = &targets[..] {
|
||||
pycodestyle::plugins::do_not_assign_lambda(self, target, value, stmt)
|
||||
pycodestyle::plugins::do_not_assign_lambda(self, target, value, stmt);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::U001) {
|
||||
@@ -1020,7 +1019,7 @@ where
|
||||
StmtKind::Delete { .. } => {}
|
||||
StmtKind::Expr { value, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::B015) {
|
||||
flake8_bugbear::plugins::useless_comparison(self, value)
|
||||
flake8_bugbear::plugins::useless_comparison(self, value);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -1085,7 +1084,7 @@ where
|
||||
}
|
||||
self.except_handlers.pop();
|
||||
for excepthandler in handlers {
|
||||
self.visit_excepthandler(excepthandler)
|
||||
self.visit_excepthandler(excepthandler);
|
||||
}
|
||||
for stmt in orelse {
|
||||
self.visit_stmt(stmt);
|
||||
@@ -1244,6 +1243,28 @@ where
|
||||
args,
|
||||
keywords,
|
||||
} => {
|
||||
// pyflakes
|
||||
if let ExprKind::Attribute { value, attr, .. } = &func.node {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} = &value.node
|
||||
{
|
||||
if attr == "format" {
|
||||
// "...".format(...) call
|
||||
if self.settings.enabled.contains(&CheckCode::F521) {
|
||||
let location = Range::from_located(expr);
|
||||
if let Some(check) =
|
||||
pyflakes::checks::string_dot_format_invalid(value, location)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pyupgrade
|
||||
if self.settings.enabled.contains(&CheckCode::U005) {
|
||||
pyupgrade::plugins::deprecated_unittest_alias(self, func);
|
||||
}
|
||||
@@ -1624,7 +1645,7 @@ where
|
||||
comparators,
|
||||
check_none_comparisons,
|
||||
check_true_false_comparisons,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::F632) {
|
||||
@@ -1721,7 +1742,7 @@ where
|
||||
for expr in &args.defaults {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.push_scope(Scope::new(ScopeKind::Lambda))
|
||||
self.push_scope(Scope::new(ScopeKind::Lambda));
|
||||
}
|
||||
ExprKind::ListComp { elt, generators } | ExprKind::SetComp { elt, generators } => {
|
||||
if self.settings.enabled.contains(&CheckCode::C416) {
|
||||
@@ -1736,10 +1757,10 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
self.push_scope(Scope::new(ScopeKind::Generator))
|
||||
self.push_scope(Scope::new(ScopeKind::Generator));
|
||||
}
|
||||
ExprKind::GeneratorExp { .. } | ExprKind::DictComp { .. } => {
|
||||
self.push_scope(Scope::new(ScopeKind::Generator))
|
||||
self.push_scope(Scope::new(ScopeKind::Generator));
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
@@ -1898,7 +1919,7 @@ where
|
||||
error!(
|
||||
"Found non-ExprKind::Tuple argument to PEP 593 \
|
||||
Annotation."
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1930,13 +1951,20 @@ where
|
||||
|
||||
fn visit_excepthandler(&mut self, excepthandler: &'b Excepthandler) {
|
||||
match &excepthandler.node {
|
||||
ExcepthandlerKind::ExceptHandler { type_, name, .. } => {
|
||||
ExcepthandlerKind::ExceptHandler {
|
||||
type_, name, body, ..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::E722) && type_.is_none() {
|
||||
self.add_check(Check::new(
|
||||
CheckKind::DoNotUseBareExcept,
|
||||
Range::from_located(excepthandler),
|
||||
));
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B904) {
|
||||
{
|
||||
flake8_bugbear::plugins::raise_without_from_inside_except(self, body);
|
||||
}
|
||||
}
|
||||
match name {
|
||||
Some(name) => {
|
||||
if self.settings.enabled.contains(&CheckCode::E741) {
|
||||
@@ -2018,10 +2046,10 @@ where
|
||||
.extend(pyflakes::checks::duplicate_arguments(arguments));
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B006) {
|
||||
flake8_bugbear::plugins::mutable_argument_default(self, arguments)
|
||||
flake8_bugbear::plugins::mutable_argument_default(self, arguments);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B008) {
|
||||
flake8_bugbear::plugins::function_call_argument_default(self, arguments)
|
||||
flake8_bugbear::plugins::function_call_argument_default(self, arguments);
|
||||
}
|
||||
|
||||
// flake8-boolean-trap
|
||||
@@ -2084,6 +2112,13 @@ where
|
||||
|
||||
self.check_builtin_arg_shadowing(&arg.node.arg, Range::from_located(arg));
|
||||
}
|
||||
|
||||
fn visit_withitem(&mut self, withitem: &'b Withitem) {
|
||||
let prev_in_withitem = self.in_withitem;
|
||||
self.in_withitem = true;
|
||||
walk_withitem(self, withitem);
|
||||
self.in_withitem = prev_in_withitem;
|
||||
}
|
||||
}
|
||||
|
||||
fn try_mark_used(scope: &mut Scope, scope_id: usize, id: &str, expr: &Expr) -> bool {
|
||||
@@ -2161,7 +2196,7 @@ impl<'a> Checker<'a> {
|
||||
builtin,
|
||||
Binding {
|
||||
kind: BindingKind::Builtin,
|
||||
range: Default::default(),
|
||||
range: Range::default(),
|
||||
used: None,
|
||||
},
|
||||
);
|
||||
@@ -2171,7 +2206,7 @@ impl<'a> Checker<'a> {
|
||||
builtin,
|
||||
Binding {
|
||||
kind: BindingKind::Builtin,
|
||||
range: Default::default(),
|
||||
range: Range::default(),
|
||||
used: None,
|
||||
},
|
||||
);
|
||||
@@ -2193,7 +2228,7 @@ impl<'a> Checker<'a> {
|
||||
pub fn binding_context(&self) -> BindingContext {
|
||||
let mut rev = self.parent_stack.iter().rev().fuse();
|
||||
let defined_by = *rev.next().expect("Expected to bind within a statement.");
|
||||
let defined_in = rev.next().cloned();
|
||||
let defined_in = rev.next().copied();
|
||||
BindingContext {
|
||||
defined_by,
|
||||
defined_in,
|
||||
@@ -2314,7 +2349,7 @@ impl<'a> Checker<'a> {
|
||||
self.add_check(Check::new(
|
||||
CheckKind::UndefinedName(id.clone()),
|
||||
Range::from_located(expr),
|
||||
))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2341,23 +2376,24 @@ impl<'a> Checker<'a> {
|
||||
.current_scope()
|
||||
.values
|
||||
.get(id)
|
||||
.map(|binding| matches!(binding.kind, BindingKind::Global))
|
||||
.unwrap_or(false)
|
||||
.map_or(false, |binding| matches!(binding.kind, BindingKind::Global))
|
||||
{
|
||||
pep8_naming::plugins::non_lowercase_variable_in_function(self, expr, parent, id)
|
||||
pep8_naming::plugins::non_lowercase_variable_in_function(
|
||||
self, expr, parent, id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N815) {
|
||||
if matches!(self.current_scope().kind, ScopeKind::Class(..)) {
|
||||
pep8_naming::plugins::mixed_case_variable_in_class_scope(self, expr, parent, id)
|
||||
pep8_naming::plugins::mixed_case_variable_in_class_scope(self, expr, parent, id);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N816) {
|
||||
if matches!(self.current_scope().kind, ScopeKind::Module) {
|
||||
pep8_naming::plugins::mixed_case_variable_in_global_scope(self, expr, parent, id)
|
||||
pep8_naming::plugins::mixed_case_variable_in_global_scope(self, expr, parent, id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2389,7 +2425,7 @@ impl<'a> Checker<'a> {
|
||||
return;
|
||||
}
|
||||
|
||||
if operations::is_unpacking_assignment(parent) {
|
||||
if self.in_withitem || operations::is_unpacking_assignment(parent) {
|
||||
self.add_binding(
|
||||
id,
|
||||
Binding {
|
||||
@@ -2453,7 +2489,7 @@ impl<'a> Checker<'a> {
|
||||
self.add_check(Check::new(
|
||||
CheckKind::UndefinedName(id.to_string()),
|
||||
Range::from_located(expr),
|
||||
))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2523,7 +2559,7 @@ impl<'a> Checker<'a> {
|
||||
self.parent_stack = parents;
|
||||
self.scope_stack = scopes;
|
||||
self.visible_scope = visibility;
|
||||
self.push_scope(Scope::new(ScopeKind::Function(Default::default())));
|
||||
self.push_scope(Scope::new(ScopeKind::Function(FunctionScope::default())));
|
||||
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { body, args, .. }
|
||||
@@ -2588,9 +2624,7 @@ impl<'a> Checker<'a> {
|
||||
let all_binding: Option<&Binding> = scope.values.get("__all__");
|
||||
let all_names: Option<Vec<&str>> =
|
||||
all_binding.and_then(|binding| match &binding.kind {
|
||||
BindingKind::Export(names) => {
|
||||
Some(names.iter().map(|name| name.as_str()).collect())
|
||||
}
|
||||
BindingKind::Export(names) => Some(names.iter().map(String::as_str).collect()),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
@@ -2648,7 +2682,7 @@ impl<'a> Checker<'a> {
|
||||
let mut unused: BTreeMap<(ImportKind, usize, Option<usize>), Vec<&str>> =
|
||||
BTreeMap::new();
|
||||
|
||||
for (name, binding) in scope.values.iter() {
|
||||
for (name, binding) in &scope.values {
|
||||
if !matches!(
|
||||
binding.kind,
|
||||
BindingKind::Importation(..)
|
||||
@@ -2892,7 +2926,7 @@ pub fn check_ast(
|
||||
python_ast: &Suite,
|
||||
locator: &SourceCodeLocator,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
autofix: bool,
|
||||
path: &Path,
|
||||
) -> Vec<Check> {
|
||||
let mut checker = Checker::new(settings, autofix, path, locator);
|
||||
|
||||
@@ -4,7 +4,6 @@ use nohash_hasher::IntSet;
|
||||
use rustpython_parser::ast::Suite;
|
||||
|
||||
use crate::ast::visitor::Visitor;
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::Check;
|
||||
use crate::isort;
|
||||
use crate::isort::track::ImportTracker;
|
||||
@@ -15,12 +14,12 @@ fn check_import_blocks(
|
||||
tracker: ImportTracker,
|
||||
locator: &SourceCodeLocator,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
autofix: bool,
|
||||
) -> Vec<Check> {
|
||||
let mut checks = vec![];
|
||||
for block in tracker.into_iter() {
|
||||
if !block.is_empty() {
|
||||
if let Some(check) = isort::plugins::check_imports(block, locator, settings, autofix) {
|
||||
if let Some(check) = isort::plugins::check_imports(&block, locator, settings, autofix) {
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
@@ -33,7 +32,7 @@ pub fn check_imports(
|
||||
locator: &SourceCodeLocator,
|
||||
exclusions: &IntSet<usize>,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
autofix: bool,
|
||||
) -> Vec<Check> {
|
||||
let mut tracker = ImportTracker::new(exclusions);
|
||||
for stmt in python_ast {
|
||||
|
||||
@@ -6,7 +6,7 @@ use regex::Regex;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::{fixer, Fix};
|
||||
use crate::autofix::Fix;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::noqa;
|
||||
use crate::noqa::Directive;
|
||||
@@ -37,7 +37,7 @@ pub fn check_lines(
|
||||
contents: &str,
|
||||
noqa_line_for: &IntMap<usize, usize>,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
autofix: bool,
|
||||
) {
|
||||
let enforce_unnecessary_coding_comment = settings.enabled.contains(&CheckCode::U009);
|
||||
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
|
||||
@@ -73,7 +73,7 @@ pub fn check_lines(
|
||||
end_location: Location::new(lineno + 1, line_length + 1),
|
||||
},
|
||||
);
|
||||
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
|
||||
if autofix && settings.fixable.contains(check.kind.code()) {
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(lineno + 1, 0),
|
||||
Location::new(lineno + 1, line_length + 1),
|
||||
@@ -101,7 +101,7 @@ pub fn check_lines(
|
||||
match noqa {
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index)
|
||||
ignored.push(index);
|
||||
}
|
||||
(Directive::Codes(.., codes), matches) => {
|
||||
if codes.contains(&check.kind.code().as_ref()) {
|
||||
@@ -195,7 +195,7 @@ pub fn check_lines(
|
||||
end_location: Location::new(row + 1, end),
|
||||
},
|
||||
);
|
||||
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
|
||||
if autofix && settings.fixable.contains(check.kind.code()) {
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(row + 1, start - spaces),
|
||||
Location::new(row + 1, lines[row].chars().count()),
|
||||
@@ -208,10 +208,10 @@ pub fn check_lines(
|
||||
let mut invalid_codes = vec![];
|
||||
let mut valid_codes = vec![];
|
||||
for code in codes {
|
||||
if !matches.contains(&code) {
|
||||
invalid_codes.push(code.to_string());
|
||||
} else {
|
||||
if matches.contains(&code) {
|
||||
valid_codes.push(code.to_string());
|
||||
} else {
|
||||
invalid_codes.push(code.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ pub fn check_lines(
|
||||
end_location: Location::new(row + 1, end),
|
||||
},
|
||||
);
|
||||
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
|
||||
if autofix && settings.fixable.contains(check.kind.code()) {
|
||||
if valid_codes.is_empty() {
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(row + 1, start - spaces),
|
||||
@@ -257,14 +257,13 @@ mod tests {
|
||||
use nohash_hasher::IntMap;
|
||||
|
||||
use super::check_lines;
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::settings::Settings;
|
||||
|
||||
#[test]
|
||||
fn e501_non_ascii_char() {
|
||||
let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8.
|
||||
let noqa_line_for: IntMap<usize, usize> = Default::default();
|
||||
let noqa_line_for: IntMap<usize, usize> = IntMap::default();
|
||||
let check_with_max_line_length = |line_length: usize| {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
check_lines(
|
||||
@@ -275,7 +274,7 @@ mod tests {
|
||||
line_length,
|
||||
..Settings::for_rule(CheckCode::E501)
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
);
|
||||
checks
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
use rustpython_parser::lexer::{LexResult, Tok};
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::lex::docstring_detection::StateMachine;
|
||||
use crate::rules::checks::Context;
|
||||
@@ -10,12 +9,13 @@ use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::{flake8_quotes, pycodestyle, rules, Settings};
|
||||
|
||||
pub fn check_tokens(
|
||||
checks: &mut Vec<Check>,
|
||||
locator: &SourceCodeLocator,
|
||||
tokens: &[LexResult],
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) {
|
||||
autofix: bool,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
let enforce_ambiguous_unicode_character = settings.enabled.contains(&CheckCode::RUF001)
|
||||
|| settings.enabled.contains(&CheckCode::RUF002)
|
||||
|| settings.enabled.contains(&CheckCode::RUF003);
|
||||
@@ -25,7 +25,7 @@ pub fn check_tokens(
|
||||
|| settings.enabled.contains(&CheckCode::Q003);
|
||||
let enforce_invalid_escape_sequence = settings.enabled.contains(&CheckCode::W605);
|
||||
|
||||
let mut state_machine: StateMachine = Default::default();
|
||||
let mut state_machine = StateMachine::default();
|
||||
for &(start, ref tok, end) in tokens.iter().flatten() {
|
||||
let is_docstring = if enforce_ambiguous_unicode_character || enforce_quotes {
|
||||
state_machine.consume(tok)
|
||||
@@ -81,4 +81,6 @@ pub fn check_tokens(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checks
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ pub enum CheckCode {
|
||||
F405,
|
||||
F406,
|
||||
F407,
|
||||
F521,
|
||||
F541,
|
||||
F601,
|
||||
F602,
|
||||
@@ -103,6 +104,7 @@ pub enum CheckCode {
|
||||
B025,
|
||||
B026,
|
||||
B027,
|
||||
B904,
|
||||
// flake8-blind-except
|
||||
BLE001,
|
||||
// flake8-comprehensions
|
||||
@@ -402,6 +404,7 @@ pub enum CheckKind {
|
||||
MultiValueRepeatedKeyVariable(String),
|
||||
RaiseNotImplemented,
|
||||
ReturnOutsideFunction,
|
||||
StringDotFormatInvalidFormat(String),
|
||||
TwoStarredExpressions,
|
||||
UndefinedExport(String),
|
||||
UndefinedLocal(String),
|
||||
@@ -441,6 +444,7 @@ pub enum CheckKind {
|
||||
DuplicateTryBlockException(String),
|
||||
StarArgUnpackingAfterKeywordArg,
|
||||
EmptyMethodWithoutAbstractDecorator(String),
|
||||
RaiseWithoutFromInsideExcept,
|
||||
// flake8-comprehensions
|
||||
UnnecessaryGeneratorList,
|
||||
UnnecessaryGeneratorSet,
|
||||
@@ -613,7 +617,7 @@ impl CheckCode {
|
||||
}
|
||||
}
|
||||
|
||||
/// A placeholder representation of the CheckKind for the check.
|
||||
/// A placeholder representation of the `CheckKind` for the check.
|
||||
pub fn kind(&self) -> CheckKind {
|
||||
match self {
|
||||
// pycodestyle errors
|
||||
@@ -644,6 +648,7 @@ impl CheckCode {
|
||||
}
|
||||
CheckCode::F406 => CheckKind::ImportStarNotPermitted("...".to_string()),
|
||||
CheckCode::F407 => CheckKind::FutureFeatureNotDefined("...".to_string()),
|
||||
CheckCode::F521 => CheckKind::StringDotFormatInvalidFormat("...".to_string()),
|
||||
CheckCode::F541 => CheckKind::FStringMissingPlaceholders,
|
||||
CheckCode::F601 => CheckKind::MultiValueRepeatedKeyLiteral,
|
||||
CheckCode::F602 => CheckKind::MultiValueRepeatedKeyVariable("...".to_string()),
|
||||
@@ -699,6 +704,7 @@ impl CheckCode {
|
||||
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
|
||||
CheckCode::B026 => CheckKind::StarArgUnpackingAfterKeywordArg,
|
||||
CheckCode::B027 => CheckKind::EmptyMethodWithoutAbstractDecorator("...".to_string()),
|
||||
CheckCode::B904 => CheckKind::RaiseWithoutFromInsideExcept,
|
||||
// flake8-comprehensions
|
||||
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
|
||||
CheckCode::C401 => CheckKind::UnnecessaryGeneratorSet,
|
||||
@@ -909,6 +915,7 @@ impl CheckCode {
|
||||
CheckCode::F405 => CheckCategory::Pyflakes,
|
||||
CheckCode::F406 => CheckCategory::Pyflakes,
|
||||
CheckCode::F407 => CheckCategory::Pyflakes,
|
||||
CheckCode::F521 => CheckCategory::Pyflakes,
|
||||
CheckCode::F541 => CheckCategory::Pyflakes,
|
||||
CheckCode::F601 => CheckCategory::Pyflakes,
|
||||
CheckCode::F602 => CheckCategory::Pyflakes,
|
||||
@@ -958,6 +965,7 @@ impl CheckCode {
|
||||
CheckCode::B025 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B026 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B027 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B904 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::BLE001 => CheckCategory::Flake8BlindExcept,
|
||||
CheckCode::C400 => CheckCategory::Flake8Comprehensions,
|
||||
CheckCode::C401 => CheckCategory::Flake8Comprehensions,
|
||||
@@ -1132,6 +1140,7 @@ impl CheckKind {
|
||||
CheckKind::NotIsTest => &CheckCode::E714,
|
||||
CheckKind::RaiseNotImplemented => &CheckCode::F901,
|
||||
CheckKind::ReturnOutsideFunction => &CheckCode::F706,
|
||||
CheckKind::StringDotFormatInvalidFormat(_) => &CheckCode::F521,
|
||||
CheckKind::SyntaxError(_) => &CheckCode::E999,
|
||||
CheckKind::ExpressionsInStarAssignment => &CheckCode::F621,
|
||||
CheckKind::TrueFalseComparison(..) => &CheckCode::E712,
|
||||
@@ -1176,6 +1185,7 @@ impl CheckKind {
|
||||
CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
|
||||
CheckKind::StarArgUnpackingAfterKeywordArg => &CheckCode::B026,
|
||||
CheckKind::EmptyMethodWithoutAbstractDecorator(_) => &CheckCode::B027,
|
||||
CheckKind::RaiseWithoutFromInsideExcept => &CheckCode::B904,
|
||||
// flake8-blind-except
|
||||
CheckKind::BlindExcept => &CheckCode::BLE001,
|
||||
// flake8-comprehensions
|
||||
@@ -1334,13 +1344,13 @@ impl CheckKind {
|
||||
match self {
|
||||
// pycodestyle errors
|
||||
CheckKind::AmbiguousClassName(name) => {
|
||||
format!("Ambiguous class name: `{}`", name)
|
||||
format!("Ambiguous class name: `{name}`")
|
||||
}
|
||||
CheckKind::AmbiguousFunctionName(name) => {
|
||||
format!("Ambiguous function name: `{}`", name)
|
||||
format!("Ambiguous function name: `{name}`")
|
||||
}
|
||||
CheckKind::AmbiguousVariableName(name) => {
|
||||
format!("Ambiguous variable name: `{}`", name)
|
||||
format!("Ambiguous variable name: `{name}`")
|
||||
}
|
||||
CheckKind::AssertTuple => {
|
||||
"Assert test is a non-empty tuple, which is always `True`".to_string()
|
||||
@@ -1383,7 +1393,7 @@ impl CheckKind {
|
||||
CheckKind::ImportStarUsage(name, sources) => {
|
||||
let sources = sources
|
||||
.iter()
|
||||
.map(|source| format!("`{}`", source))
|
||||
.map(|source| format!("`{source}`"))
|
||||
.join(", ");
|
||||
format!("`{name}` may be undefined, or defined from star imports: {sources}")
|
||||
}
|
||||
@@ -1417,28 +1427,25 @@ impl CheckKind {
|
||||
CheckKind::ReturnOutsideFunction => {
|
||||
"`return` statement outside of a function/method".to_string()
|
||||
}
|
||||
CheckKind::StringDotFormatInvalidFormat(message) => {
|
||||
format!("'...'.format(...) has invalid format string: {message}")
|
||||
}
|
||||
CheckKind::SyntaxError(message) => format!("SyntaxError: {message}"),
|
||||
CheckKind::ExpressionsInStarAssignment => {
|
||||
"Too many expressions in star-unpacking assignment".to_string()
|
||||
}
|
||||
CheckKind::TrueFalseComparison(value, op) => match *value {
|
||||
true => match op {
|
||||
RejectedCmpop::Eq => {
|
||||
"Comparison to `True` should be `cond is True`".to_string()
|
||||
}
|
||||
RejectedCmpop::NotEq => {
|
||||
"Comparison to `True` should be `cond is not True`".to_string()
|
||||
}
|
||||
},
|
||||
false => match op {
|
||||
RejectedCmpop::Eq => {
|
||||
"Comparison to `False` should be `cond is False`".to_string()
|
||||
}
|
||||
RejectedCmpop::NotEq => {
|
||||
"Comparison to `False` should be `cond is not False`".to_string()
|
||||
}
|
||||
},
|
||||
},
|
||||
CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq) => {
|
||||
"Comparison to `True` should be `cond is True`".to_string()
|
||||
}
|
||||
CheckKind::TrueFalseComparison(true, RejectedCmpop::NotEq) => {
|
||||
"Comparison to `True` should be `cond is not True`".to_string()
|
||||
}
|
||||
CheckKind::TrueFalseComparison(false, RejectedCmpop::Eq) => {
|
||||
"Comparison to `False` should be `cond is False`".to_string()
|
||||
}
|
||||
CheckKind::TrueFalseComparison(false, RejectedCmpop::NotEq) => {
|
||||
"Comparison to `False` should be `cond is not False`".to_string()
|
||||
}
|
||||
CheckKind::TwoStarredExpressions => "Two starred expressions in assignment".to_string(),
|
||||
CheckKind::TypeComparison => "Do not compare types, use `isinstance()`".to_string(),
|
||||
CheckKind::UndefinedExport(name) => {
|
||||
@@ -1586,6 +1593,11 @@ impl CheckKind {
|
||||
decorator"
|
||||
)
|
||||
}
|
||||
CheckKind::RaiseWithoutFromInsideExcept => {
|
||||
"Within an except clause, raise exceptions with raise ... from err or raise ... \
|
||||
from None to distinguish them from errors in exception handling"
|
||||
.to_string()
|
||||
}
|
||||
// flake8-comprehensions
|
||||
CheckKind::UnnecessaryGeneratorList => {
|
||||
"Unnecessary generator (rewrite as a `list` comprehension)".to_string()
|
||||
@@ -2161,13 +2173,12 @@ impl Check {
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
|
||||
#[test]
|
||||
fn check_code_serialization() -> Result<()> {
|
||||
fn check_code_serialization() {
|
||||
for check_code in CheckCode::iter() {
|
||||
assert!(
|
||||
CheckCode::from_str(check_code.as_ref()).is_ok(),
|
||||
@@ -2175,6 +2186,5 @@ mod tests {
|
||||
check_code
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! File automatically generated by examples/generate_check_code_prefix.rs.
|
||||
//! File automatically generated by `examples/generate_check_code_prefix.rs`.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::EnumString;
|
||||
@@ -63,6 +63,9 @@ pub enum CheckCodePrefix {
|
||||
B025,
|
||||
B026,
|
||||
B027,
|
||||
B9,
|
||||
B90,
|
||||
B904,
|
||||
BLE,
|
||||
BLE0,
|
||||
BLE00,
|
||||
@@ -416,6 +419,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::B025,
|
||||
CheckCode::B026,
|
||||
CheckCode::B027,
|
||||
CheckCode::B904,
|
||||
],
|
||||
CheckCodePrefix::B0 => vec![
|
||||
CheckCode::B002,
|
||||
@@ -500,6 +504,9 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B025 => vec![CheckCode::B025],
|
||||
CheckCodePrefix::B026 => vec![CheckCode::B026],
|
||||
CheckCodePrefix::B027 => vec![CheckCode::B027],
|
||||
CheckCodePrefix::B9 => vec![CheckCode::B904],
|
||||
CheckCodePrefix::B90 => vec![CheckCode::B904],
|
||||
CheckCodePrefix::B904 => vec![CheckCode::B904],
|
||||
CheckCodePrefix::BLE => vec![CheckCode::BLE001],
|
||||
CheckCodePrefix::BLE0 => vec![CheckCode::BLE001],
|
||||
CheckCodePrefix::BLE00 => vec![CheckCode::BLE001],
|
||||
@@ -840,6 +847,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::F405,
|
||||
CheckCode::F406,
|
||||
CheckCode::F407,
|
||||
CheckCode::F521,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
CheckCode::F602,
|
||||
@@ -1285,6 +1293,9 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B025 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B026 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B027 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B9 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::B90 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::B904 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::BLE => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::BLE0 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::BLE00 => PrefixSpecificity::Tens,
|
||||
|
||||
23
src/cli.rs
23
src/cli.rs
@@ -1,18 +1,19 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{command, Parser};
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::logging::LogLevel;
|
||||
use crate::printer::SerializationFormat;
|
||||
use crate::settings::types::{PatternPrefixPair, PerFileIgnore, PythonVersion};
|
||||
use crate::settings::types::{FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, about = "Ruff: An extremely fast Python linter.")]
|
||||
#[command(version)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Cli {
|
||||
#[arg(required = true)]
|
||||
pub files: Vec<PathBuf>,
|
||||
@@ -59,11 +60,11 @@ pub struct Cli {
|
||||
pub extend_ignore: Vec<CheckCodePrefix>,
|
||||
/// List of paths, used to exclude files and/or directories from checks.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub exclude: Vec<String>,
|
||||
pub exclude: Vec<FilePattern>,
|
||||
/// Like --exclude, but adds additional files and directories on top of the
|
||||
/// excluded ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_exclude: Vec<String>,
|
||||
pub extend_exclude: Vec<FilePattern>,
|
||||
/// List of error codes to treat as eligible for autofix. Only applicable
|
||||
/// when autofix itself is enabled (e.g., via `--fix`).
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
@@ -76,7 +77,7 @@ pub struct Cli {
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub per_file_ignores: Vec<PatternPrefixPair>,
|
||||
/// Output serialization format for error messages.
|
||||
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
|
||||
#[arg(long, value_enum, default_value_t = SerializationFormat::Text)]
|
||||
pub format: SerializationFormat,
|
||||
/// Show violations with source code.
|
||||
#[arg(long)]
|
||||
@@ -110,6 +111,9 @@ pub struct Cli {
|
||||
/// The name of the file when passing it through stdin.
|
||||
#[arg(long)]
|
||||
pub stdin_filename: Option<String>,
|
||||
/// Explain a rule.
|
||||
#[arg(long)]
|
||||
pub explain: Option<CheckCode>,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
@@ -144,10 +148,7 @@ pub fn extract_log_level(cli: &Cli) -> LogLevel {
|
||||
}
|
||||
|
||||
/// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`.
|
||||
pub fn collect_per_file_ignores(
|
||||
pairs: Vec<PatternPrefixPair>,
|
||||
project_root: Option<&PathBuf>,
|
||||
) -> Result<Vec<PerFileIgnore>> {
|
||||
pub fn collect_per_file_ignores(pairs: Vec<PatternPrefixPair>) -> Vec<PerFileIgnore> {
|
||||
let mut per_file_ignores: FxHashMap<String, Vec<CheckCodePrefix>> = FxHashMap::default();
|
||||
for pair in pairs {
|
||||
per_file_ignores
|
||||
@@ -156,7 +157,7 @@ pub fn collect_per_file_ignores(
|
||||
.push(pair.prefix);
|
||||
}
|
||||
per_file_ignores
|
||||
.iter()
|
||||
.map(|(pattern, prefixes)| PerFileIgnore::new(pattern, prefixes, project_root))
|
||||
.into_iter()
|
||||
.map(|(pattern, prefixes)| PerFileIgnore::new(pattern, &prefixes))
|
||||
.collect()
|
||||
}
|
||||
|
||||
677
src/code_gen.rs
677
src/code_gen.rs
File diff suppressed because it is too large
Load Diff
66
src/commands.rs
Normal file
66
src/commands.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::Serialize;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::fs::iter_python_files;
|
||||
use crate::printer::SerializationFormat;
|
||||
use crate::{Configuration, Settings};
|
||||
|
||||
/// Print the user-facing configuration settings.
|
||||
pub fn show_settings(
|
||||
configuration: &Configuration,
|
||||
project_root: Option<&PathBuf>,
|
||||
pyproject: Option<&PathBuf>,
|
||||
) {
|
||||
println!("Resolved configuration: {configuration:#?}");
|
||||
println!("Found project root at: {project_root:?}");
|
||||
println!("Found pyproject.toml at: {pyproject:?}");
|
||||
}
|
||||
|
||||
/// Show the list of files to be checked based on current settings.
|
||||
pub 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());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Explanation<'a> {
|
||||
code: &'a str,
|
||||
category: &'a str,
|
||||
summary: &'a str,
|
||||
}
|
||||
|
||||
/// Explain a `CheckCode` to the user.
|
||||
pub fn explain(code: &CheckCode, format: SerializationFormat) -> Result<()> {
|
||||
match format {
|
||||
SerializationFormat::Text => {
|
||||
println!(
|
||||
"{} ({}): {}",
|
||||
code.as_ref(),
|
||||
code.category().title(),
|
||||
code.kind().summary()
|
||||
);
|
||||
}
|
||||
SerializationFormat::Json => {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&Explanation {
|
||||
code: code.as_ref(),
|
||||
category: code.category().title(),
|
||||
summary: &code.kind().summary(),
|
||||
})?
|
||||
);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
@@ -11,8 +11,8 @@ use crate::{Settings, SourceCodeLocator};
|
||||
|
||||
bitflags! {
|
||||
pub struct Flags: u32 {
|
||||
const NOQA = 0b00000001;
|
||||
const ISORT = 0b00000010;
|
||||
const NOQA = 0b0000_0001;
|
||||
const ISORT = 0b0000_0010;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,12 +44,12 @@ pub fn extract_directives(
|
||||
noqa_line_for: if flags.contains(Flags::NOQA) {
|
||||
extract_noqa_line_for(lxr)
|
||||
} else {
|
||||
Default::default()
|
||||
IntMap::default()
|
||||
},
|
||||
isort_exclusions: if flags.contains(Flags::ISORT) {
|
||||
extract_isort_exclusions(lxr, locator)
|
||||
} else {
|
||||
Default::default()
|
||||
IntSet::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ pub fn extract_isort_exclusions(lxr: &[LexResult], locator: &SourceCodeLocator)
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
use nohash_hasher::IntMap;
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
@@ -121,8 +121,8 @@ mod tests {
|
||||
use crate::directives::extract_noqa_line_for;
|
||||
|
||||
#[test]
|
||||
fn extraction() -> Result<()> {
|
||||
let empty: IntMap<usize, usize> = Default::default();
|
||||
fn extraction() {
|
||||
let empty: IntMap<usize, usize> = IntMap::default();
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
@@ -200,7 +200,5 @@ z = x + 1",
|
||||
extract_noqa_line_for(&lxr),
|
||||
IntMap::from_iter([(2, 5), (3, 5), (4, 5)])
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ mod tests {
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::{flake8_annotations, Settings};
|
||||
@@ -31,7 +30,7 @@ mod tests {
|
||||
CheckCode::ANN401,
|
||||
])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
@@ -57,7 +56,7 @@ mod tests {
|
||||
CheckCode::ANN102,
|
||||
])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
@@ -83,7 +82,7 @@ mod tests {
|
||||
CheckCode::ANN206,
|
||||
])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
@@ -109,7 +108,7 @@ mod tests {
|
||||
CheckCode::ANN206,
|
||||
])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
@@ -129,7 +128,7 @@ mod tests {
|
||||
},
|
||||
..Settings::for_rules(vec![CheckCode::ANN401])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use rustpython_ast::{Arguments, Constant, Expr, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
@@ -25,16 +23,14 @@ where
|
||||
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
|
||||
// No recurse.
|
||||
}
|
||||
StmtKind::Return { value } => {
|
||||
self.returns.push(value.as_ref().map(|expr| expr.deref()))
|
||||
}
|
||||
StmtKind::Return { value } => self.returns.push(value.as_ref().map(|expr| &**expr)),
|
||||
_ => visitor::walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_none_returning(body: &[Stmt]) -> bool {
|
||||
let mut visitor: ReturnStatementVisitor = Default::default();
|
||||
let mut visitor = ReturnStatementVisitor::default();
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ pub struct Options {
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, Default)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Settings {
|
||||
pub mypy_init_return: bool,
|
||||
pub suppress_dummy_args: bool,
|
||||
@@ -29,6 +30,7 @@ pub struct Settings {
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn from_options(options: Options) -> Self {
|
||||
Self {
|
||||
mypy_init_return: options.mypy_init_return.unwrap_or_default(),
|
||||
|
||||
@@ -51,7 +51,7 @@ pub fn check_boolean_default_value_in_function_definition(
|
||||
checker: &mut Checker,
|
||||
arguments: &Arguments,
|
||||
) {
|
||||
for arg in arguments.defaults.iter() {
|
||||
for arg in &arguments.defaults {
|
||||
add_if_boolean(
|
||||
checker,
|
||||
arg,
|
||||
|
||||
@@ -7,7 +7,6 @@ mod tests {
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::{flake8_bugbear, Settings};
|
||||
@@ -26,7 +25,7 @@ mod tests {
|
||||
},
|
||||
..Settings::for_rules(vec![CheckCode::B008])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
@@ -17,8 +17,7 @@ fn is_abc_class(
|
||||
.node
|
||||
.arg
|
||||
.as_ref()
|
||||
.map(|a| a == "metaclass")
|
||||
.unwrap_or(false)
|
||||
.map_or(false, |a| a == "metaclass")
|
||||
&& match_module_member(
|
||||
&keyword.node.value,
|
||||
"abc",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Stmt, StmtKind};
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
@@ -8,16 +8,16 @@ use crate::code_gen::SourceGenerator;
|
||||
|
||||
fn assertion_error(msg: Option<&Expr>) -> Stmt {
|
||||
Stmt::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
StmtKind::Raise {
|
||||
exc: Some(Box::new(Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
ExprKind::Call {
|
||||
func: Box::new(Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
ExprKind::Name {
|
||||
id: "AssertionError".to_string(),
|
||||
ctx: ExprContext::Load,
|
||||
@@ -46,14 +46,13 @@ pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option
|
||||
let mut check = Check::new(CheckKind::DoNotAssertFalse, Range::from_located(test));
|
||||
if checker.patch(check.kind.code()) {
|
||||
let mut generator = SourceGenerator::new();
|
||||
if let Ok(()) = generator.unparse_stmt(&assertion_error(msg)) {
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
stmt.location,
|
||||
stmt.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
generator.unparse_stmt(&assertion_error(msg));
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
stmt.location,
|
||||
stmt.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use itertools::Itertools;
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Stmt};
|
||||
use rustpython_ast::{
|
||||
Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Location, Stmt,
|
||||
};
|
||||
|
||||
use crate::ast::helpers;
|
||||
use crate::ast::types::Range;
|
||||
@@ -12,8 +14,8 @@ use crate::code_gen::SourceGenerator;
|
||||
|
||||
fn type_pattern(elts: Vec<&Expr>) -> Expr {
|
||||
Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
ExprKind::Tuple {
|
||||
elts: elts.into_iter().cloned().collect(),
|
||||
ctx: ExprContext::Load,
|
||||
@@ -26,9 +28,9 @@ fn duplicate_handler_exceptions<'a>(
|
||||
expr: &'a Expr,
|
||||
elts: &'a [Expr],
|
||||
) -> BTreeSet<Vec<&'a str>> {
|
||||
let mut seen: BTreeSet<Vec<&str>> = Default::default();
|
||||
let mut duplicates: BTreeSet<Vec<&str>> = Default::default();
|
||||
let mut unique_elts: Vec<&Expr> = Default::default();
|
||||
let mut seen: BTreeSet<Vec<&str>> = BTreeSet::default();
|
||||
let mut duplicates: BTreeSet<Vec<&str>> = BTreeSet::default();
|
||||
let mut unique_elts: Vec<&Expr> = Vec::default();
|
||||
for type_ in elts {
|
||||
let call_path = helpers::collect_call_paths(type_);
|
||||
if !call_path.is_empty() {
|
||||
@@ -57,14 +59,13 @@ fn duplicate_handler_exceptions<'a>(
|
||||
if checker.patch(check.kind.code()) {
|
||||
// TODO(charlie): If we have a single element, remove the tuple.
|
||||
let mut generator = SourceGenerator::new();
|
||||
if let Ok(()) = generator.unparse_expr(&type_pattern(unique_elts), 0) {
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
generator.unparse_expr(&type_pattern(unique_elts), 0);
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
@@ -75,8 +76,8 @@ fn duplicate_handler_exceptions<'a>(
|
||||
}
|
||||
|
||||
pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Excepthandler]) {
|
||||
let mut seen: BTreeSet<Vec<&str>> = Default::default();
|
||||
let mut duplicates: BTreeSet<Vec<&str>> = Default::default();
|
||||
let mut seen: BTreeSet<Vec<&str>> = BTreeSet::default();
|
||||
let mut duplicates: BTreeSet<Vec<&str>> = BTreeSet::default();
|
||||
for handler in handlers {
|
||||
match &handler.node {
|
||||
ExcepthandlerKind::ExceptHandler { type_, .. } => {
|
||||
|
||||
@@ -60,9 +60,9 @@ where
|
||||
self.checks.push((
|
||||
CheckKind::FunctionCallArgumentDefault(compose_call_path(expr)),
|
||||
Range::from_located(expr),
|
||||
))
|
||||
));
|
||||
}
|
||||
visitor::walk_expr(self, expr)
|
||||
visitor::walk_expr(self, expr);
|
||||
}
|
||||
ExprKind::Lambda { .. } => {}
|
||||
_ => visitor::walk_expr(self, expr),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind};
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
@@ -10,8 +10,8 @@ use crate::python::keyword::KWLIST;
|
||||
|
||||
fn attribute(value: &Expr, attr: &str) -> Expr {
|
||||
Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
ExprKind::Attribute {
|
||||
value: Box::new(value.clone()),
|
||||
attr: attr.to_string(),
|
||||
@@ -35,14 +35,13 @@ pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
|
||||
Check::new(CheckKind::GetAttrWithConstant, Range::from_located(expr));
|
||||
if checker.patch(check.kind.code()) {
|
||||
let mut generator = SourceGenerator::new();
|
||||
if let Ok(()) = generator.unparse_expr(&attribute(obj, value), 0) {
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
generator.unparse_expr(&attribute(obj, value), 0);
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
|
||||
@@ -31,7 +31,7 @@ where
|
||||
}
|
||||
ExprKind::Lambda { args, body } => {
|
||||
visitor::walk_expr(self, body);
|
||||
for arg in args.args.iter() {
|
||||
for arg in &args.args {
|
||||
self.names.remove(arg.node.arg.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ pub use getattr_with_constant::getattr_with_constant;
|
||||
pub use jump_statement_in_finally::jump_statement_in_finally;
|
||||
pub use loop_variable_overrides_iterator::loop_variable_overrides_iterator;
|
||||
pub use mutable_argument_default::mutable_argument_default;
|
||||
pub use raise_without_from_inside_except::raise_without_from_inside_except;
|
||||
pub use redundant_tuple_in_exception_handler::redundant_tuple_in_exception_handler;
|
||||
pub use setattr_with_constant::setattr_with_constant;
|
||||
pub use star_arg_unpacking_after_keyword_arg::star_arg_unpacking_after_keyword_arg;
|
||||
@@ -35,6 +36,7 @@ mod getattr_with_constant;
|
||||
mod jump_statement_in_finally;
|
||||
mod loop_variable_overrides_iterator;
|
||||
mod mutable_argument_default;
|
||||
mod raise_without_from_inside_except;
|
||||
mod redundant_tuple_in_exception_handler;
|
||||
mod setattr_with_constant;
|
||||
mod star_arg_unpacking_after_keyword_arg;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
use rustpython_ast::{ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::visitor::Visitor;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::python::string::is_lower;
|
||||
|
||||
struct RaiseVisitor {
|
||||
checks: Vec<Check>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for RaiseVisitor {
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
match &stmt.node {
|
||||
StmtKind::Raise { exc, cause } => {
|
||||
if cause.is_none() {
|
||||
if let Some(exc) = exc {
|
||||
match &exc.node {
|
||||
ExprKind::Name { id, .. } if is_lower(id) => {}
|
||||
_ => {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::RaiseWithoutFromInsideExcept,
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::ClassDef { .. }
|
||||
| StmtKind::FunctionDef { .. }
|
||||
| StmtKind::AsyncFunctionDef { .. }
|
||||
| StmtKind::Try { .. } => {}
|
||||
StmtKind::If { body, .. }
|
||||
| StmtKind::While { body, .. }
|
||||
| StmtKind::With { body, .. }
|
||||
| StmtKind::AsyncWith { body, .. }
|
||||
| StmtKind::For { body, .. }
|
||||
| StmtKind::AsyncFor { body, .. } => {
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raise_without_from_inside_except(checker: &mut Checker, body: &[Stmt]) {
|
||||
let mut visitor = RaiseVisitor { checks: vec![] };
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
}
|
||||
checker.add_checks(visitor.checks.into_iter());
|
||||
}
|
||||
@@ -64,22 +64,21 @@ pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[E
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
let mut generator = SourceGenerator::new();
|
||||
if let Ok(()) = generator.unparse_expr(elt, 0) {
|
||||
if let Ok(content) = generator.generate() {
|
||||
match match_tuple_range(handler, checker.locator) {
|
||||
Ok(range) => {
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
range.location,
|
||||
range.end_location,
|
||||
));
|
||||
}
|
||||
Err(e) => error!("Failed to locate parentheses: {}", e),
|
||||
generator.unparse_expr(elt, 0);
|
||||
if let Ok(content) = generator.generate() {
|
||||
match match_tuple_range(handler, checker.locator) {
|
||||
Ok(range) => {
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
range.location,
|
||||
range.end_location,
|
||||
));
|
||||
}
|
||||
Err(e) => error!("Failed to locate parentheses: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
checker.add_check(check)
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Stmt, StmtKind};
|
||||
use anyhow::Result;
|
||||
use log::error;
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
@@ -8,14 +10,14 @@ use crate::code_gen::SourceGenerator;
|
||||
use crate::python::identifiers::IDENTIFIER_REGEX;
|
||||
use crate::python::keyword::KWLIST;
|
||||
|
||||
fn assignment(obj: &Expr, name: &str, value: &Expr) -> Option<String> {
|
||||
fn assignment(obj: &Expr, name: &str, value: &Expr) -> Result<String> {
|
||||
let stmt = Stmt::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
StmtKind::Assign {
|
||||
targets: vec![Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
ExprKind::Attribute {
|
||||
value: Box::new(obj.clone()),
|
||||
attr: name.to_string(),
|
||||
@@ -27,10 +29,8 @@ fn assignment(obj: &Expr, name: &str, value: &Expr) -> Option<String> {
|
||||
},
|
||||
);
|
||||
let mut generator = SourceGenerator::new();
|
||||
match generator.unparse_stmt(&stmt) {
|
||||
Ok(()) => generator.generate().ok(),
|
||||
Err(_) => None,
|
||||
}
|
||||
generator.unparse_stmt(&stmt);
|
||||
generator.generate().map_err(std::convert::Into::into)
|
||||
}
|
||||
|
||||
/// B010
|
||||
@@ -47,13 +47,14 @@ pub fn setattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
|
||||
let mut check =
|
||||
Check::new(CheckKind::SetAttrWithConstant, Range::from_located(expr));
|
||||
if checker.patch(check.kind.code()) {
|
||||
if let Some(content) = assignment(obj, name, value) {
|
||||
check.amend(Fix::replacement(
|
||||
match assignment(obj, name, value) {
|
||||
Ok(content) => check.amend(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
)),
|
||||
Err(e) => error!("Failed to fix invalid comparison: {}", e),
|
||||
};
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ pub fn unary_prefix_increment(checker: &mut Checker, expr: &Expr, op: &Unaryop,
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::UnaryPrefixIncrement,
|
||||
Range::from_located(expr),
|
||||
))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ struct NameFinder<'a> {
|
||||
impl NameFinder<'_> {
|
||||
fn new() -> Self {
|
||||
NameFinder {
|
||||
names: Default::default(),
|
||||
names: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ pub fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, body:
|
||||
format!("_{name}"),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ShadowingType {
|
||||
Variable,
|
||||
Argument,
|
||||
|
||||
@@ -390,11 +390,6 @@ pub fn unnecessary_double_cast_or_process(
|
||||
args: &[Expr],
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let outer = function_name(func)?;
|
||||
if !["list", "tuple", "set", "reversed", "sorted"].contains(&outer) {
|
||||
return None;
|
||||
}
|
||||
|
||||
fn new_check(inner: &str, outer: &str, location: Range) -> Check {
|
||||
Check::new(
|
||||
CheckKind::UnnecessaryDoubleCastOrProcess(inner.to_string(), outer.to_string()),
|
||||
@@ -402,6 +397,11 @@ pub fn unnecessary_double_cast_or_process(
|
||||
)
|
||||
}
|
||||
|
||||
let outer = function_name(func)?;
|
||||
if !["list", "tuple", "set", "reversed", "sorted"].contains(&outer) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let ExprKind::Call { func, .. } = &args.first()?.node {
|
||||
let inner = function_name(func)?;
|
||||
// Ex) set(tuple(...))
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use anyhow::Result;
|
||||
use libcst_native::{
|
||||
Arg, AssignEqual, Call, Codegen, Dict, DictComp, DictElement, Element, Expr, Expression,
|
||||
LeftCurlyBrace, LeftParen, LeftSquareBracket, List, ListComp, Name, ParenthesizableWhitespace,
|
||||
RightCurlyBrace, RightParen, RightSquareBracket, Set, SetComp, SimpleString, SimpleWhitespace,
|
||||
Tuple,
|
||||
Arg, AssignEqual, Call, Codegen, CodegenState, Dict, DictComp, DictElement, Element, Expr,
|
||||
Expression, LeftCurlyBrace, LeftParen, LeftSquareBracket, List, ListComp, Name,
|
||||
ParenthesizableWhitespace, RightCurlyBrace, RightParen, RightSquareBracket, Set, SetComp,
|
||||
SimpleString, SimpleWhitespace, Tuple,
|
||||
};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
@@ -60,7 +60,7 @@ pub fn fix_unnecessary_generator_list(
|
||||
rpar: generator_exp.rpar.clone(),
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
let mut state = CodegenState::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
@@ -103,7 +103,7 @@ pub fn fix_unnecessary_generator_set(
|
||||
rpar: generator_exp.rpar.clone(),
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
let mut state = CodegenState::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
@@ -163,13 +163,13 @@ pub fn fix_unnecessary_generator_dict(
|
||||
rbrace: RightCurlyBrace {
|
||||
whitespace_before: arg.whitespace_after_arg.clone(),
|
||||
},
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
whitespace_before_colon: Default::default(),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
whitespace_before_colon: ParenthesizableWhitespace::default(),
|
||||
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" ")),
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
let mut state = CodegenState::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
@@ -211,7 +211,7 @@ pub fn fix_unnecessary_list_comprehension_set(
|
||||
rpar: list_comp.rpar.clone(),
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
let mut state = CodegenState::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
@@ -269,7 +269,7 @@ pub fn fix_unnecessary_list_comprehension_dict(
|
||||
rpar: list_comp.rpar.clone(),
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
let mut state = CodegenState::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
@@ -312,12 +312,12 @@ pub fn fix_unnecessary_literal_set(
|
||||
rbrace: RightCurlyBrace {
|
||||
whitespace_before: arg.whitespace_after_arg.clone(),
|
||||
},
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
}
|
||||
|
||||
let mut state = Default::default();
|
||||
let mut state = CodegenState::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
@@ -363,7 +363,7 @@ pub fn fix_unnecessary_literal_dict(
|
||||
key: key.clone(),
|
||||
value: value.clone(),
|
||||
comma: comma.clone(),
|
||||
whitespace_before_colon: Default::default(),
|
||||
whitespace_before_colon: ParenthesizableWhitespace::default(),
|
||||
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(
|
||||
SimpleWhitespace(" "),
|
||||
),
|
||||
@@ -385,11 +385,11 @@ pub fn fix_unnecessary_literal_dict(
|
||||
rbrace: RightCurlyBrace {
|
||||
whitespace_before: arg.whitespace_after_arg.clone(),
|
||||
},
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
let mut state = CodegenState::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
@@ -422,28 +422,28 @@ pub fn fix_unnecessary_collection_call(
|
||||
match name.value {
|
||||
"tuple" => {
|
||||
body.value = Expression::Tuple(Box::new(Tuple {
|
||||
elements: Default::default(),
|
||||
lpar: vec![Default::default()],
|
||||
rpar: vec![Default::default()],
|
||||
elements: vec![],
|
||||
lpar: vec![LeftParen::default()],
|
||||
rpar: vec![RightParen::default()],
|
||||
}));
|
||||
}
|
||||
"list" => {
|
||||
body.value = Expression::List(Box::new(List {
|
||||
elements: Default::default(),
|
||||
lbracket: Default::default(),
|
||||
rbracket: Default::default(),
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
elements: vec![],
|
||||
lbracket: LeftSquareBracket::default(),
|
||||
rbracket: RightSquareBracket::default(),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
}
|
||||
"dict" => {
|
||||
if call.args.is_empty() {
|
||||
body.value = Expression::Dict(Box::new(Dict {
|
||||
elements: Default::default(),
|
||||
lbrace: Default::default(),
|
||||
rbrace: Default::default(),
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
elements: vec![],
|
||||
lbrace: LeftCurlyBrace::default(),
|
||||
rbrace: RightCurlyBrace::default(),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
} else {
|
||||
// Quote each argument.
|
||||
@@ -465,12 +465,12 @@ pub fn fix_unnecessary_collection_call(
|
||||
.map(|(i, arg)| DictElement::Simple {
|
||||
key: Expression::SimpleString(Box::new(SimpleString {
|
||||
value: &arena[i],
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
})),
|
||||
value: arg.value.clone(),
|
||||
comma: arg.comma.clone(),
|
||||
whitespace_before_colon: Default::default(),
|
||||
whitespace_before_colon: ParenthesizableWhitespace::default(),
|
||||
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(
|
||||
SimpleWhitespace(" "),
|
||||
),
|
||||
@@ -490,8 +490,8 @@ pub fn fix_unnecessary_collection_call(
|
||||
.whitespace_after_arg
|
||||
.clone(),
|
||||
},
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -502,7 +502,7 @@ pub fn fix_unnecessary_collection_call(
|
||||
}
|
||||
};
|
||||
|
||||
let mut state = Default::default();
|
||||
let mut state = CodegenState::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
@@ -558,7 +558,7 @@ pub fn fix_unnecessary_literal_within_tuple_call(
|
||||
}],
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
let mut state = CodegenState::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
@@ -612,11 +612,11 @@ pub fn fix_unnecessary_literal_within_list_call(
|
||||
rbracket: RightSquareBracket {
|
||||
whitespace_before: whitespace_before.clone(),
|
||||
},
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
let mut state = CodegenState::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
@@ -640,7 +640,7 @@ pub fn fix_unnecessary_list_call(
|
||||
|
||||
body.value = arg.value.clone();
|
||||
|
||||
let mut state = Default::default();
|
||||
let mut state = CodegenState::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
@@ -695,22 +695,22 @@ pub fn fix_unnecessary_call_around_sorted(
|
||||
args.push(Arg {
|
||||
value: Expression::Name(Box::new(Name {
|
||||
value: "True",
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
})),
|
||||
keyword: Some(Name {
|
||||
value: "reverse",
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}),
|
||||
equal: Some(AssignEqual {
|
||||
whitespace_before: Default::default(),
|
||||
whitespace_after: Default::default(),
|
||||
whitespace_before: ParenthesizableWhitespace::default(),
|
||||
whitespace_after: ParenthesizableWhitespace::default(),
|
||||
}),
|
||||
comma: Default::default(),
|
||||
star: Default::default(),
|
||||
whitespace_after_star: Default::default(),
|
||||
whitespace_after_arg: Default::default(),
|
||||
comma: None,
|
||||
star: "",
|
||||
whitespace_after_star: ParenthesizableWhitespace::default(),
|
||||
whitespace_after_arg: ParenthesizableWhitespace::default(),
|
||||
});
|
||||
args
|
||||
};
|
||||
@@ -722,11 +722,11 @@ pub fn fix_unnecessary_call_around_sorted(
|
||||
rpar: inner_call.rpar.clone(),
|
||||
whitespace_after_func: inner_call.whitespace_after_func.clone(),
|
||||
whitespace_before_args: inner_call.whitespace_before_args.clone(),
|
||||
}))
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let mut state = Default::default();
|
||||
let mut state = CodegenState::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
@@ -750,45 +750,45 @@ pub fn fix_unnecessary_comprehension(
|
||||
body.value = Expression::Call(Box::new(Call {
|
||||
func: Box::new(Expression::Name(Box::new(Name {
|
||||
value: "list",
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}))),
|
||||
args: vec![Arg {
|
||||
value: inner.for_in.iter.clone(),
|
||||
keyword: Default::default(),
|
||||
equal: Default::default(),
|
||||
comma: Default::default(),
|
||||
star: Default::default(),
|
||||
whitespace_after_star: Default::default(),
|
||||
whitespace_after_arg: Default::default(),
|
||||
keyword: None,
|
||||
equal: None,
|
||||
comma: None,
|
||||
star: "",
|
||||
whitespace_after_star: ParenthesizableWhitespace::default(),
|
||||
whitespace_after_arg: ParenthesizableWhitespace::default(),
|
||||
}],
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
whitespace_after_func: Default::default(),
|
||||
whitespace_before_args: Default::default(),
|
||||
}))
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
whitespace_after_func: ParenthesizableWhitespace::default(),
|
||||
whitespace_before_args: ParenthesizableWhitespace::default(),
|
||||
}));
|
||||
}
|
||||
Expression::SetComp(inner) => {
|
||||
body.value = Expression::Call(Box::new(Call {
|
||||
func: Box::new(Expression::Name(Box::new(Name {
|
||||
value: "set",
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}))),
|
||||
args: vec![Arg {
|
||||
value: inner.for_in.iter.clone(),
|
||||
keyword: Default::default(),
|
||||
equal: Default::default(),
|
||||
comma: Default::default(),
|
||||
star: Default::default(),
|
||||
whitespace_after_star: Default::default(),
|
||||
whitespace_after_arg: Default::default(),
|
||||
keyword: None,
|
||||
equal: None,
|
||||
comma: None,
|
||||
star: "",
|
||||
whitespace_after_star: ParenthesizableWhitespace::default(),
|
||||
whitespace_after_arg: ParenthesizableWhitespace::default(),
|
||||
}],
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
whitespace_after_func: Default::default(),
|
||||
whitespace_before_args: Default::default(),
|
||||
}))
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
whitespace_after_func: ParenthesizableWhitespace::default(),
|
||||
whitespace_before_args: ParenthesizableWhitespace::default(),
|
||||
}));
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
@@ -797,7 +797,7 @@ pub fn fix_unnecessary_comprehension(
|
||||
}
|
||||
}
|
||||
|
||||
let mut state = Default::default();
|
||||
let mut state = CodegenState::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
|
||||
@@ -35,7 +35,7 @@ pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
|
||||
if fix.patch.content.is_empty() || fix.patch.content == "pass" {
|
||||
checker.deletions.insert(context.defined_by);
|
||||
}
|
||||
check.amend(fix)
|
||||
check.amend(fix);
|
||||
}
|
||||
Err(e) => error!("Failed to remove print call: {}", e),
|
||||
}
|
||||
|
||||
@@ -74,13 +74,13 @@ pub fn quotes(
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(Check::new(
|
||||
Some(Check::new(
|
||||
CheckKind::BadQuotesDocstring(settings.docstring_quotes.clone()),
|
||||
Range {
|
||||
location: start,
|
||||
end_location: end,
|
||||
},
|
||||
));
|
||||
))
|
||||
} else if is_multiline {
|
||||
// If our string is or contains a known good string, ignore it.
|
||||
if raw_text.contains(good_multiline(&settings.multiline_quotes)) {
|
||||
@@ -92,13 +92,13 @@ pub fn quotes(
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(Check::new(
|
||||
Some(Check::new(
|
||||
CheckKind::BadQuotesMultilineString(settings.multiline_quotes.clone()),
|
||||
Range {
|
||||
location: start,
|
||||
end_location: end,
|
||||
},
|
||||
));
|
||||
))
|
||||
} else {
|
||||
let string_contents = &raw_text[1..raw_text.len() - 1];
|
||||
|
||||
@@ -131,7 +131,7 @@ pub fn quotes(
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::CheckCode;
|
||||
use crate::flake8_quotes::settings::Quote;
|
||||
use crate::linter::test_path;
|
||||
@@ -39,7 +38,7 @@ mod tests {
|
||||
CheckCode::Q003,
|
||||
])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
@@ -71,7 +70,7 @@ mod tests {
|
||||
CheckCode::Q003,
|
||||
])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
@@ -108,7 +107,7 @@ mod tests {
|
||||
CheckCode::Q003,
|
||||
])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
@@ -145,7 +144,7 @@ mod tests {
|
||||
CheckCode::Q003,
|
||||
])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
@@ -7,7 +7,6 @@ mod tests {
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::CheckCode;
|
||||
use crate::flake8_tidy_imports::settings::Strictness;
|
||||
use crate::linter::test_path;
|
||||
@@ -23,7 +22,7 @@ mod tests {
|
||||
},
|
||||
..Settings::for_rules(vec![CheckCode::I252])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
@@ -40,7 +39,7 @@ mod tests {
|
||||
},
|
||||
..Settings::for_rules(vec![CheckCode::I252])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
|
||||
159
src/fs.rs
159
src/fs.rs
@@ -1,17 +1,16 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Read};
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use globset::GlobMatcher;
|
||||
use log::debug;
|
||||
use path_absolutize::{path_dedot, Absolutize};
|
||||
use rustc_hash::FxHashSet;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore};
|
||||
|
||||
/// Extract the absolute path and basename (as strings) from a Path.
|
||||
fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
|
||||
@@ -26,32 +25,8 @@ fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
|
||||
Ok((file_path, file_basename))
|
||||
}
|
||||
|
||||
fn is_excluded<'a, T>(file_path: &str, file_basename: &str, exclude: T) -> bool
|
||||
where
|
||||
T: Iterator<Item = &'a FilePattern>,
|
||||
{
|
||||
for pattern in exclude {
|
||||
match pattern {
|
||||
FilePattern::Simple(basename) => {
|
||||
if *basename == file_basename {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
FilePattern::Complex(absolute, basename) => {
|
||||
if absolute.matches(file_path) {
|
||||
return true;
|
||||
}
|
||||
if basename
|
||||
.as_ref()
|
||||
.map(|pattern| pattern.matches(file_basename))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
false
|
||||
fn is_excluded(file_path: &str, file_basename: &str, exclude: &globset::GlobSet) -> bool {
|
||||
exclude.is_match(file_path) || exclude.is_match(file_basename)
|
||||
}
|
||||
|
||||
fn is_included(path: &Path) -> bool {
|
||||
@@ -61,18 +36,12 @@ fn is_included(path: &Path) -> bool {
|
||||
|
||||
pub fn iter_python_files<'a>(
|
||||
path: &'a Path,
|
||||
exclude: &'a [FilePattern],
|
||||
extend_exclude: &'a [FilePattern],
|
||||
exclude: &'a globset::GlobSet,
|
||||
extend_exclude: &'a globset::GlobSet,
|
||||
) -> impl Iterator<Item = Result<DirEntry, walkdir::Error>> + 'a {
|
||||
// Run some checks over the provided patterns, to enable optimizations below.
|
||||
let has_exclude = !exclude.is_empty();
|
||||
let has_extend_exclude = !extend_exclude.is_empty();
|
||||
let exclude_simple = exclude
|
||||
.iter()
|
||||
.all(|pattern| matches!(pattern, FilePattern::Simple(_)));
|
||||
let extend_exclude_simple = extend_exclude
|
||||
.iter()
|
||||
.all(|pattern| matches!(pattern, FilePattern::Simple(_)));
|
||||
|
||||
WalkDir::new(normalize_path(path))
|
||||
.into_iter()
|
||||
@@ -84,17 +53,11 @@ pub fn iter_python_files<'a>(
|
||||
let path = entry.path();
|
||||
match extract_path_names(path) {
|
||||
Ok((file_path, file_basename)) => {
|
||||
let file_type = entry.file_type();
|
||||
|
||||
if has_exclude
|
||||
&& (!exclude_simple || file_type.is_dir())
|
||||
&& is_excluded(file_path, file_basename, exclude.iter())
|
||||
{
|
||||
if has_exclude && is_excluded(file_path, file_basename, exclude) {
|
||||
debug!("Ignored path via `exclude`: {:?}", path);
|
||||
false
|
||||
} else if has_extend_exclude
|
||||
&& (!extend_exclude_simple || file_type.is_dir())
|
||||
&& is_excluded(file_path, file_basename, extend_exclude.iter())
|
||||
&& is_excluded(file_path, file_basename, extend_exclude)
|
||||
{
|
||||
debug!("Ignored path via `extend-exclude`: {:?}", path);
|
||||
false
|
||||
@@ -102,8 +65,8 @@ pub fn iter_python_files<'a>(
|
||||
true
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
debug!("Ignored path due to error in parsing: {:?}", path);
|
||||
Err(e) => {
|
||||
debug!("Ignored path due to error in parsing: {:?}: {}", path, e);
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -120,19 +83,15 @@ pub fn iter_python_files<'a>(
|
||||
/// Create tree set with codes matching the pattern/code pairs.
|
||||
pub(crate) fn ignores_from_path<'a>(
|
||||
path: &Path,
|
||||
pattern_code_pairs: &'a [PerFileIgnore],
|
||||
) -> Result<FxHashSet<&'a CheckCode>> {
|
||||
pattern_code_pairs: &'a [(GlobMatcher, GlobMatcher, BTreeSet<CheckCode>)],
|
||||
) -> Result<BTreeSet<&'a CheckCode>> {
|
||||
let (file_path, file_basename) = extract_path_names(path)?;
|
||||
Ok(pattern_code_pairs
|
||||
.iter()
|
||||
.filter(|pattern_code_pair| {
|
||||
is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
[&pattern_code_pair.pattern].into_iter(),
|
||||
)
|
||||
.filter(|(absolute, basename, _)| {
|
||||
basename.is_match(file_basename) || absolute.is_match(file_path)
|
||||
})
|
||||
.flat_map(|pattern_code_pair| &pattern_code_pair.codes)
|
||||
.flat_map(|(_, _, codes)| codes)
|
||||
.collect())
|
||||
}
|
||||
|
||||
@@ -155,7 +114,7 @@ pub(crate) fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
|
||||
|
||||
/// Convert an absolute path to be relative to the current working directory.
|
||||
pub(crate) fn relativize_path(path: &Path) -> Cow<str> {
|
||||
if let Ok(path) = path.strip_prefix(path_dedot::CWD.deref()) {
|
||||
if let Ok(path) = path.strip_prefix(&*path_dedot::CWD) {
|
||||
return path.to_string_lossy();
|
||||
}
|
||||
path.to_string_lossy()
|
||||
@@ -172,9 +131,10 @@ pub(crate) fn read_file(path: &Path) -> Result<String> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use globset::GlobSet;
|
||||
use path_absolutize::Absolutize;
|
||||
|
||||
use crate::fs::{extract_path_names, is_excluded, is_included};
|
||||
@@ -195,73 +155,86 @@ mod tests {
|
||||
assert!(!is_included(&path));
|
||||
}
|
||||
|
||||
fn make_exclusion(file_pattern: FilePattern, project_root: Option<&PathBuf>) -> GlobSet {
|
||||
let mut builder = globset::GlobSetBuilder::new();
|
||||
file_pattern.add_to(&mut builder, project_root).unwrap();
|
||||
builder.build().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclusions() -> Result<()> {
|
||||
let project_root = Path::new("/tmp/");
|
||||
|
||||
let path = Path::new("foo").absolutize_from(project_root).unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"foo",
|
||||
Some(&project_root.to_path_buf()),
|
||||
)?];
|
||||
let exclude = FilePattern::User("foo".to_string());
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
assert!(is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude, Some(&project_root.to_path_buf()))
|
||||
));
|
||||
|
||||
let path = Path::new("foo/bar").absolutize_from(project_root).unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"bar",
|
||||
Some(&project_root.to_path_buf()),
|
||||
)?];
|
||||
let exclude = FilePattern::User("bar".to_string());
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
assert!(is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude, Some(&project_root.to_path_buf()))
|
||||
));
|
||||
|
||||
let path = Path::new("foo/bar/baz.py")
|
||||
.absolutize_from(project_root)
|
||||
.unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"baz.py",
|
||||
Some(&project_root.to_path_buf()),
|
||||
)?];
|
||||
let exclude = FilePattern::User("baz.py".to_string());
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
assert!(is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude, Some(&project_root.to_path_buf()))
|
||||
));
|
||||
|
||||
let path = Path::new("foo/bar").absolutize_from(project_root).unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"foo/bar",
|
||||
Some(&project_root.to_path_buf()),
|
||||
)?];
|
||||
let exclude = FilePattern::User("foo/bar".to_string());
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
assert!(is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude, Some(&project_root.to_path_buf()))
|
||||
));
|
||||
|
||||
let path = Path::new("foo/bar/baz.py")
|
||||
.absolutize_from(project_root)
|
||||
.unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"foo/bar/baz.py",
|
||||
Some(&project_root.to_path_buf()),
|
||||
)?];
|
||||
let exclude = FilePattern::User("foo/bar/baz.py".to_string());
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
assert!(is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude, Some(&project_root.to_path_buf()))
|
||||
));
|
||||
|
||||
let path = Path::new("foo/bar/baz.py")
|
||||
.absolutize_from(project_root)
|
||||
.unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"foo/bar/*.py",
|
||||
Some(&project_root.to_path_buf()),
|
||||
)?];
|
||||
let exclude = FilePattern::User("foo/bar/*.py".to_string());
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
assert!(is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude, Some(&project_root.to_path_buf()))
|
||||
));
|
||||
|
||||
let path = Path::new("foo/bar/baz.py")
|
||||
.absolutize_from(project_root)
|
||||
.unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"baz",
|
||||
Some(&project_root.to_path_buf()),
|
||||
)?];
|
||||
let exclude = FilePattern::User("baz".to_string());
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(!is_excluded(file_path, file_basename, exclude.iter()));
|
||||
assert!(!is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude, Some(&project_root.to_path_buf()))
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ pub fn categorize(
|
||||
known_third_party: &BTreeSet<String>,
|
||||
extra_standard_library: &BTreeSet<String>,
|
||||
) -> ImportType {
|
||||
if level.map(|level| *level > 0).unwrap_or(false) {
|
||||
if level.map_or(false, |level| *level > 0) {
|
||||
ImportType::LocalFolder
|
||||
} else if known_first_party.contains(module_base) {
|
||||
ImportType::FirstParty
|
||||
|
||||
@@ -128,7 +128,7 @@ fn annotate_imports<'a>(
|
||||
asname: alias.node.asname.as_ref(),
|
||||
atop: alias_atop,
|
||||
inline: alias_inline,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
annotated.push(AnnotatedImport::ImportFrom {
|
||||
@@ -146,7 +146,7 @@ fn annotate_imports<'a>(
|
||||
}
|
||||
|
||||
fn normalize_imports(imports: Vec<AnnotatedImport>) -> ImportBlock {
|
||||
let mut block: ImportBlock = Default::default();
|
||||
let mut block = ImportBlock::default();
|
||||
for import in imports {
|
||||
match import {
|
||||
AnnotatedImport::Import {
|
||||
@@ -204,7 +204,7 @@ fn normalize_imports(imports: Vec<AnnotatedImport>) -> ImportBlock {
|
||||
entry.inline.push(comment.value);
|
||||
}
|
||||
} else {
|
||||
let entry = &mut block
|
||||
let entry = block
|
||||
.import_from_as
|
||||
.entry((
|
||||
ImportFromData { module, level },
|
||||
@@ -274,7 +274,7 @@ fn categorize_imports<'a>(
|
||||
known_third_party: &BTreeSet<String>,
|
||||
extra_standard_library: &BTreeSet<String>,
|
||||
) -> BTreeMap<ImportType, ImportBlock<'a>> {
|
||||
let mut block_by_type: BTreeMap<ImportType, ImportBlock> = Default::default();
|
||||
let mut block_by_type: BTreeMap<ImportType, ImportBlock> = BTreeMap::default();
|
||||
// Categorize `StmtKind::Import`.
|
||||
for (alias, comments) in block.import {
|
||||
let import_type = categorize(
|
||||
@@ -327,7 +327,7 @@ fn categorize_imports<'a>(
|
||||
}
|
||||
|
||||
fn sort_imports(block: ImportBlock) -> OrderedImportBlock {
|
||||
let mut ordered: OrderedImportBlock = Default::default();
|
||||
let mut ordered = OrderedImportBlock::default();
|
||||
|
||||
// Sort `StmtKind::Import`.
|
||||
ordered.import.extend(
|
||||
@@ -354,12 +354,12 @@ fn sort_imports(block: ImportBlock) -> OrderedImportBlock {
|
||||
(
|
||||
CommentSet {
|
||||
atop: comments.atop,
|
||||
inline: Default::default(),
|
||||
inline: vec![],
|
||||
},
|
||||
FxHashMap::from_iter([(
|
||||
alias,
|
||||
CommentSet {
|
||||
atop: Default::default(),
|
||||
atop: vec![],
|
||||
inline: comments.inline,
|
||||
},
|
||||
)]),
|
||||
@@ -428,22 +428,22 @@ pub fn format_imports(
|
||||
let import_block = sort_imports(import_block);
|
||||
|
||||
// Add a blank line between every section.
|
||||
if !is_first_block {
|
||||
output.append("\n");
|
||||
} else {
|
||||
if is_first_block {
|
||||
is_first_block = false;
|
||||
} else {
|
||||
output.append("\n");
|
||||
}
|
||||
|
||||
let mut is_first_statement = true;
|
||||
|
||||
// Format `StmtKind::Import` statements.
|
||||
for (alias, comments) in import_block.import.iter() {
|
||||
for (alias, comments) in &import_block.import {
|
||||
output.append(&format::format_import(alias, comments, is_first_statement));
|
||||
is_first_statement = false;
|
||||
}
|
||||
|
||||
// Format `StmtKind::ImportFrom` statements.
|
||||
for (import_from, comments, aliases) in import_block.import_from.iter() {
|
||||
for (import_from, comments, aliases) in &import_block.import_from {
|
||||
output.append(&format::format_import_from(
|
||||
import_from,
|
||||
comments,
|
||||
@@ -464,7 +464,6 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::Settings;
|
||||
@@ -501,7 +500,7 @@ mod tests {
|
||||
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
|
||||
..Settings::for_rule(CheckCode::I001)
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
|
||||
@@ -4,7 +4,7 @@ use textwrap::{dedent, indent};
|
||||
use crate::ast::helpers::{match_leading_content, match_trailing_content};
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::whitespace::leading_space;
|
||||
use crate::autofix::{fixer, Fix};
|
||||
use crate::autofix::Fix;
|
||||
use crate::checks::CheckKind;
|
||||
use crate::isort::{comments, format_imports};
|
||||
use crate::{Check, Settings, SourceCodeLocator};
|
||||
@@ -30,13 +30,13 @@ fn extract_indentation(body: &[&Stmt], locator: &SourceCodeLocator) -> String {
|
||||
|
||||
/// I001
|
||||
pub fn check_imports(
|
||||
body: Vec<&Stmt>,
|
||||
body: &[&Stmt],
|
||||
locator: &SourceCodeLocator,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
autofix: bool,
|
||||
) -> Option<Check> {
|
||||
let range = extract_range(&body);
|
||||
let indentation = extract_indentation(&body, locator);
|
||||
let range = extract_range(body);
|
||||
let indentation = extract_indentation(body, locator);
|
||||
|
||||
// Extract comments. Take care to grab any inline comments from the last line.
|
||||
let comments = comments::collect_comments(
|
||||
@@ -53,7 +53,7 @@ pub fn check_imports(
|
||||
|
||||
// Generate the sorted import block.
|
||||
let expected = format_imports(
|
||||
&body,
|
||||
body,
|
||||
comments,
|
||||
settings.line_length - indentation.len(),
|
||||
&settings.src,
|
||||
@@ -64,7 +64,7 @@ pub fn check_imports(
|
||||
|
||||
if has_leading_content || has_trailing_content {
|
||||
let mut check = Check::new(CheckKind::UnsortedImports, range);
|
||||
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
|
||||
if autofix && settings.fixable.contains(check.kind.code()) {
|
||||
let mut content = String::new();
|
||||
if has_leading_content {
|
||||
content.push('\n');
|
||||
@@ -90,9 +90,11 @@ pub fn check_imports(
|
||||
end_location: Location::new(range.end_location.row() + 1, 0),
|
||||
};
|
||||
let actual = dedent(&locator.slice_source_code_range(&range));
|
||||
if actual != expected {
|
||||
if actual == expected {
|
||||
None
|
||||
} else {
|
||||
let mut check = Check::new(CheckKind::UnsortedImports, range);
|
||||
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
|
||||
if autofix && settings.fixable.contains(check.kind.code()) {
|
||||
check.amend(Fix::replacement(
|
||||
indent(&expected, &indentation),
|
||||
range.location,
|
||||
@@ -100,8 +102,6 @@ pub fn check_imports(
|
||||
));
|
||||
}
|
||||
Some(check)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 8
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 6
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 26
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 5
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 15
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 5
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 3
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ expression: checks
|
||||
end_location:
|
||||
row: 3
|
||||
column: 0
|
||||
applied: false
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 5
|
||||
@@ -35,5 +34,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 7
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 13
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 5
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 12
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ expression: checks
|
||||
end_location:
|
||||
row: 4
|
||||
column: 0
|
||||
applied: false
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 5
|
||||
@@ -35,5 +34,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 7
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 3
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 6
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 4
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 5
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 5
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 11
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,5 +18,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 27
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ expression: checks
|
||||
end_location:
|
||||
row: 3
|
||||
column: 0
|
||||
applied: false
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 5
|
||||
@@ -35,5 +34,4 @@ expression: checks
|
||||
end_location:
|
||||
row: 7
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// See: https://github.com/PyCQA/isort/blob/12cc5fbd67eebf92eb2213b03c07b138ae1fb448/isort/sorting.py#L13
|
||||
/// See: <https://github.com/PyCQA/isort/blob/12cc5fbd67eebf92eb2213b03c07b138ae1fb448/isort/sorting.py#L13>
|
||||
use crate::python::string;
|
||||
|
||||
#[derive(PartialOrd, Ord, PartialEq, Eq)]
|
||||
@@ -23,12 +23,7 @@ pub fn member_key<'a>(
|
||||
if name.len() > 1 && string::is_upper(name) {
|
||||
// Ex) `CONSTANT`
|
||||
Prefix::Constants
|
||||
} else if name
|
||||
.chars()
|
||||
.next()
|
||||
.map(|char| char.is_uppercase())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
} else if name.chars().next().map_or(false, char::is_uppercase) {
|
||||
// Ex) `Class`
|
||||
Prefix::Classes
|
||||
} else {
|
||||
|
||||
@@ -142,7 +142,7 @@ where
|
||||
finalbody,
|
||||
} => {
|
||||
for excepthandler in handlers {
|
||||
self.visit_excepthandler(excepthandler)
|
||||
self.visit_excepthandler(excepthandler);
|
||||
}
|
||||
|
||||
for stmt in body {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Extract docstrings via tokenization.
|
||||
//!
|
||||
//! See: https://github.com/zheller/flake8-quotes/blob/ef0d9a90249a080e460b70ab62bf4b65e5aa5816/flake8_quotes/docstring_detection.py#L29
|
||||
//! See: <https://github.com/zheller/flake8-quotes/blob/ef0d9a90249a080e460b70ab62bf4b65e5aa5816/flake8_quotes/docstring_detection.py#L29>
|
||||
//!
|
||||
//! TODO(charlie): Consolidate with the existing AST-based docstring extraction.
|
||||
|
||||
|
||||
28
src/lib.rs
28
src/lib.rs
@@ -1,15 +1,26 @@
|
||||
#![allow(clippy::collapsible_if, clippy::collapsible_else_if)]
|
||||
#![allow(
|
||||
clippy::collapsible_else_if,
|
||||
clippy::collapsible_if,
|
||||
clippy::implicit_hasher,
|
||||
clippy::match_same_arms,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::must_use_candidate,
|
||||
clippy::similar_names,
|
||||
clippy::too_many_lines
|
||||
)]
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::debug;
|
||||
use rustpython_helpers::tokenize;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use settings::{pyproject, Settings};
|
||||
|
||||
use crate::autofix::fixer::Mode;
|
||||
use crate::checks::Check;
|
||||
use crate::linter::{check_path, tokenize};
|
||||
use crate::linter::check_path;
|
||||
use crate::settings::configuration::Configuration;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
@@ -24,6 +35,7 @@ pub mod checks;
|
||||
pub mod checks_gen;
|
||||
pub mod cli;
|
||||
pub mod code_gen;
|
||||
pub mod commands;
|
||||
mod cst;
|
||||
mod directives;
|
||||
mod docstrings;
|
||||
@@ -54,10 +66,12 @@ mod pyflakes;
|
||||
mod python;
|
||||
mod pyupgrade;
|
||||
mod rules;
|
||||
mod rustpython_helpers;
|
||||
pub mod settings;
|
||||
pub mod source_code_locator;
|
||||
#[cfg(feature = "update-informer")]
|
||||
pub mod updates;
|
||||
mod vendored;
|
||||
pub mod visibility;
|
||||
|
||||
/// Run Ruff over Python source code directly.
|
||||
@@ -74,10 +88,10 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
None => debug!("Unable to find pyproject.toml; using default settings..."),
|
||||
};
|
||||
|
||||
let settings = Settings::from_configuration(Configuration::from_pyproject(
|
||||
pyproject.as_ref(),
|
||||
let settings = Settings::from_configuration(
|
||||
Configuration::from_pyproject(pyproject.as_ref(), project_root.as_ref())?,
|
||||
project_root.as_ref(),
|
||||
)?);
|
||||
)?;
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(contents);
|
||||
@@ -100,7 +114,7 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
&locator,
|
||||
&directives,
|
||||
&settings,
|
||||
&if autofix { Mode::Generate } else { Mode::None },
|
||||
autofix,
|
||||
)?;
|
||||
|
||||
Ok(checks)
|
||||
|
||||
326
src/linter.rs
326
src/linter.rs
@@ -1,16 +1,13 @@
|
||||
use std::fs::write;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::ops::AddAssign;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use log::debug;
|
||||
use rustpython_ast::{Mod, Suite};
|
||||
use rustpython_parser::error::ParseError;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use rustpython_parser::parser::Mode;
|
||||
use rustpython_parser::{lexer, parser};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::fixer;
|
||||
@@ -26,32 +23,29 @@ use crate::message::{Message, Source};
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::settings::Settings;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::{cache, directives, fs};
|
||||
use crate::{cache, directives, fs, rustpython_helpers};
|
||||
|
||||
/// Collect tokens up to and including the first error.
|
||||
pub(crate) fn tokenize(contents: &str) -> Vec<LexResult> {
|
||||
let mut tokens: Vec<LexResult> = vec![];
|
||||
for tok in lexer::make_tokenizer(contents) {
|
||||
let is_err = tok.is_err();
|
||||
tokens.push(tok);
|
||||
if is_err {
|
||||
break;
|
||||
}
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Diagnostics {
|
||||
pub messages: Vec<Message>,
|
||||
pub fixed: usize,
|
||||
}
|
||||
|
||||
impl Diagnostics {
|
||||
pub fn new(messages: Vec<Message>) -> Self {
|
||||
Self { messages, fixed: 0 }
|
||||
}
|
||||
tokens
|
||||
}
|
||||
|
||||
/// Parse a full Python program from its tokens.
|
||||
pub(crate) fn parse_program_tokens(
|
||||
lxr: Vec<LexResult>,
|
||||
source_path: &str,
|
||||
) -> Result<Suite, ParseError> {
|
||||
parser::parse_tokens(lxr, Mode::Module, source_path).map(|top| match top {
|
||||
Mod::Module { body, .. } => body,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
impl AddAssign for Diagnostics {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
self.messages.extend(other.messages);
|
||||
self.fixed += other.fixed;
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a list of `Check` violations from the source code contents at the
|
||||
/// given `Path`.
|
||||
pub(crate) fn check_path(
|
||||
path: &Path,
|
||||
contents: &str,
|
||||
@@ -59,7 +53,7 @@ pub(crate) fn check_path(
|
||||
locator: &SourceCodeLocator,
|
||||
directives: &Directives,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
autofix: bool,
|
||||
) -> Result<Vec<Check>> {
|
||||
// Aggregate all checks.
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
@@ -70,7 +64,7 @@ pub(crate) fn check_path(
|
||||
.iter()
|
||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::Tokens));
|
||||
if use_tokens {
|
||||
check_tokens(&mut checks, locator, &tokens, settings, autofix);
|
||||
checks.extend(check_tokens(locator, &tokens, settings, autofix));
|
||||
}
|
||||
|
||||
// Run the AST-based checks.
|
||||
@@ -83,7 +77,7 @@ pub(crate) fn check_path(
|
||||
.iter()
|
||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::Imports));
|
||||
if use_ast || use_imports {
|
||||
match parse_program_tokens(tokens, "<filename>") {
|
||||
match rustpython_helpers::parse_program_tokens(tokens, "<filename>") {
|
||||
Ok(python_ast) => {
|
||||
if use_ast {
|
||||
checks.extend(check_ast(&python_ast, locator, settings, autofix, path));
|
||||
@@ -106,7 +100,7 @@ pub(crate) fn check_path(
|
||||
location: parse_error.location,
|
||||
end_location: parse_error.location,
|
||||
},
|
||||
))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,132 +129,108 @@ pub(crate) fn check_path(
|
||||
Ok(checks)
|
||||
}
|
||||
|
||||
pub fn lint_stdin(
|
||||
path: &Path,
|
||||
stdin: &str,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Result<Vec<Message>> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(stdin);
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
let locator = SourceCodeLocator::new(stdin);
|
||||
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
directives::Flags::from_settings(settings),
|
||||
);
|
||||
|
||||
// Generate checks.
|
||||
let mut checks = check_path(
|
||||
path,
|
||||
stdin,
|
||||
tokens,
|
||||
&locator,
|
||||
&directives,
|
||||
settings,
|
||||
autofix,
|
||||
)?;
|
||||
|
||||
// Apply autofix, write results to stdout.
|
||||
if matches!(autofix, fixer::Mode::Apply) {
|
||||
match fix_file(&mut checks, &locator) {
|
||||
None => io::stdout().write_all(stdin.as_bytes()),
|
||||
Some(contents) => io::stdout().write_all(contents.as_bytes()),
|
||||
}?;
|
||||
}
|
||||
|
||||
// Convert to messages.
|
||||
Ok(checks
|
||||
.into_iter()
|
||||
.map(|check| {
|
||||
let filename = path.to_string_lossy().to_string();
|
||||
let source = if settings.show_source {
|
||||
Some(Source::from_check(&check, &locator))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Message::from_check(check, filename, source)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
const MAX_ITERATIONS: usize = 100;
|
||||
|
||||
/// Lint the source code at the given `Path`.
|
||||
pub fn lint_path(
|
||||
path: &Path,
|
||||
settings: &Settings,
|
||||
mode: &cache::Mode,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Result<Vec<Message>> {
|
||||
) -> Result<Diagnostics> {
|
||||
let metadata = path.metadata()?;
|
||||
|
||||
// Check the cache.
|
||||
if let Some(messages) = cache::get(path, &metadata, settings, autofix, mode) {
|
||||
debug!("Cache hit for: {}", path.to_string_lossy());
|
||||
return Ok(messages);
|
||||
return Ok(Diagnostics::new(messages));
|
||||
}
|
||||
|
||||
// Read the file from disk.
|
||||
let contents = fs::read_file(path)?;
|
||||
let mut contents = fs::read_file(path)?;
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
// Track the number of fixed errors across iterations.
|
||||
let mut fixed = 0;
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
// As an escape hatch, bail after 100 iterations.
|
||||
let mut iterations = 0;
|
||||
|
||||
// Determine the noqa and isort exclusions.
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
directives::Flags::from_settings(settings),
|
||||
);
|
||||
// Continuously autofix until the source code stabilizes.
|
||||
let messages = loop {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
|
||||
// Generate checks.
|
||||
let mut checks = check_path(
|
||||
path,
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&directives,
|
||||
settings,
|
||||
autofix,
|
||||
)?;
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
|
||||
// Apply autofix.
|
||||
if matches!(autofix, fixer::Mode::Apply) {
|
||||
if let Some(fixed_contents) = fix_file(&mut checks, &locator) {
|
||||
write(path, fixed_contents.as_ref())?;
|
||||
// Determine the noqa and isort exclusions.
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
directives::Flags::from_settings(settings),
|
||||
);
|
||||
|
||||
// Generate checks.
|
||||
let checks = check_path(
|
||||
path,
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&directives,
|
||||
settings,
|
||||
autofix.into(),
|
||||
)?;
|
||||
|
||||
// Apply autofix.
|
||||
if matches!(autofix, fixer::Mode::Apply) && iterations < MAX_ITERATIONS {
|
||||
if let Some((fixed_contents, applied)) = fix_file(&checks, &locator) {
|
||||
// Count the number of fixed errors.
|
||||
fixed += applied;
|
||||
|
||||
// Store the fixed contents.
|
||||
contents = fixed_contents.to_string();
|
||||
|
||||
// Increment the iteration count.
|
||||
iterations += 1;
|
||||
|
||||
// Re-run the linter pass (by avoiding the break).
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to messages.
|
||||
let filename = path.to_string_lossy().to_string();
|
||||
break checks
|
||||
.into_iter()
|
||||
.map(|check| {
|
||||
let source = if settings.show_source {
|
||||
Some(Source::from_check(&check, &locator))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Message::from_check(check, filename.clone(), source)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
};
|
||||
|
||||
// Convert to messages.
|
||||
let messages: Vec<Message> = checks
|
||||
.into_iter()
|
||||
.map(|check| {
|
||||
let filename = path.to_string_lossy().to_string();
|
||||
let source = if settings.show_source {
|
||||
Some(Source::from_check(&check, &locator))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Message::from_check(check, filename, source)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Re-populate the cache.
|
||||
cache::set(path, &metadata, settings, autofix, &messages, mode);
|
||||
|
||||
Ok(messages)
|
||||
// If we applied any fixes, write the contents back to disk.
|
||||
if fixed > 0 {
|
||||
write(path, &contents)?;
|
||||
}
|
||||
|
||||
Ok(Diagnostics { messages, fixed })
|
||||
}
|
||||
|
||||
/// Add any missing `#noqa` pragmas to the source code at the given `Path`.
|
||||
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> = tokenize(&contents);
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
@@ -280,32 +250,115 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
|
||||
&locator,
|
||||
&directives,
|
||||
settings,
|
||||
&fixer::Mode::None,
|
||||
false,
|
||||
)?;
|
||||
|
||||
add_noqa(&checks, &contents, &directives.noqa_line_for, path)
|
||||
}
|
||||
|
||||
/// Apply autoformatting to the source code at the given `Path`.
|
||||
pub fn autoformat_path(path: &Path) -> Result<()> {
|
||||
// Read the file from disk.
|
||||
let contents = fs::read_file(path)?;
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
|
||||
// Generate the AST.
|
||||
let python_ast = parse_program_tokens(tokens, "<filename>")?;
|
||||
let mut generator: SourceGenerator = Default::default();
|
||||
generator.unparse_suite(&python_ast)?;
|
||||
let python_ast = rustpython_helpers::parse_program_tokens(tokens, "<filename>")?;
|
||||
let mut generator = SourceGenerator::default();
|
||||
generator.unparse_suite(&python_ast);
|
||||
write(path, generator.generate()?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate a list of `Check` violations from source code content derived from
|
||||
/// stdin.
|
||||
pub fn lint_stdin(
|
||||
path: &Path,
|
||||
stdin: &str,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Result<Diagnostics> {
|
||||
// Read the file from disk.
|
||||
let mut contents = stdin.to_string();
|
||||
|
||||
// Track the number of fixed errors across iterations.
|
||||
let mut fixed = 0;
|
||||
|
||||
// As an escape hatch, bail after 100 iterations.
|
||||
let mut iterations = 0;
|
||||
|
||||
let messages = loop {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
directives::Flags::from_settings(settings),
|
||||
);
|
||||
|
||||
// Generate checks.
|
||||
let checks = check_path(
|
||||
path,
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&directives,
|
||||
settings,
|
||||
autofix.into(),
|
||||
)?;
|
||||
|
||||
// Apply autofix.
|
||||
if matches!(autofix, fixer::Mode::Apply) && iterations < MAX_ITERATIONS {
|
||||
if let Some((fixed_contents, applied)) = fix_file(&checks, &locator) {
|
||||
// Count the number of fixed errors.
|
||||
fixed += applied;
|
||||
|
||||
// Store the fixed contents.
|
||||
contents = fixed_contents.to_string();
|
||||
|
||||
// Increment the iteration count.
|
||||
iterations += 1;
|
||||
|
||||
// Re-run the linter pass (by avoiding the break).
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to messages.
|
||||
let filename = path.to_string_lossy().to_string();
|
||||
break checks
|
||||
.into_iter()
|
||||
.map(|check| {
|
||||
let source = if settings.show_source {
|
||||
Some(Source::from_check(&check, &locator))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Message::from_check(check, filename.clone(), source)
|
||||
})
|
||||
.collect();
|
||||
};
|
||||
|
||||
// Write the fixed contents to stdout.
|
||||
if matches!(autofix, fixer::Mode::Apply) {
|
||||
io::stdout().write_all(contents.as_bytes())?;
|
||||
}
|
||||
|
||||
Ok(Diagnostics { messages, fixed })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test_path(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> Result<Vec<Check>> {
|
||||
pub fn test_path(path: &Path, settings: &Settings, autofix: bool) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
@@ -332,7 +385,6 @@ mod tests {
|
||||
use regex::Regex;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::settings;
|
||||
@@ -365,6 +417,7 @@ mod tests {
|
||||
#[test_case(CheckCode::B025, Path::new("B025.py"); "B025")]
|
||||
#[test_case(CheckCode::B026, Path::new("B026.py"); "B026")]
|
||||
#[test_case(CheckCode::B027, Path::new("B027.py"); "B027")]
|
||||
#[test_case(CheckCode::B904, Path::new("B904.py"); "B904")]
|
||||
#[test_case(CheckCode::BLE001, Path::new("BLE.py"); "BLE001")]
|
||||
#[test_case(CheckCode::C400, Path::new("C400.py"); "C400")]
|
||||
#[test_case(CheckCode::C401, Path::new("C401.py"); "C401")]
|
||||
@@ -453,6 +506,7 @@ mod tests {
|
||||
#[test_case(CheckCode::F405, Path::new("F405.py"); "F405")]
|
||||
#[test_case(CheckCode::F406, Path::new("F406.py"); "F406")]
|
||||
#[test_case(CheckCode::F407, Path::new("F407.py"); "F407")]
|
||||
#[test_case(CheckCode::F521, Path::new("F521.py"); "F521")]
|
||||
#[test_case(CheckCode::F541, Path::new("F541.py"); "F541")]
|
||||
#[test_case(CheckCode::F601, Path::new("F601.py"); "F601")]
|
||||
#[test_case(CheckCode::F602, Path::new("F602.py"); "F602")]
|
||||
@@ -552,7 +606,7 @@ mod tests {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures").join(path).as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
@@ -567,7 +621,7 @@ mod tests {
|
||||
dummy_variable_rgx: Regex::new(r"^z$").unwrap(),
|
||||
..settings::Settings::for_rule(CheckCode::F841)
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
@@ -579,7 +633,7 @@ mod tests {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/M001.py"),
|
||||
&settings::Settings::for_rules(vec![CheckCode::M001, CheckCode::E501, CheckCode::F841]),
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
@@ -591,7 +645,7 @@ mod tests {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/__init__.py"),
|
||||
&settings::Settings::for_rules(vec![CheckCode::F821, CheckCode::F822]),
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
@@ -603,7 +657,7 @@ mod tests {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/future_annotations.py"),
|
||||
&settings::Settings::for_rules(vec![CheckCode::F401, CheckCode::F821]),
|
||||
&fixer::Mode::Generate,
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
|
||||
@@ -53,12 +53,12 @@ pub fn set_up_logging(level: &LogLevel) -> Result<()> {
|
||||
record.target(),
|
||||
record.level(),
|
||||
message
|
||||
))
|
||||
));
|
||||
})
|
||||
.level(level.level_filter())
|
||||
.chain(std::io::stdout())
|
||||
.apply()
|
||||
.map_err(|e| e.into())
|
||||
.apply()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
180
src/main.rs
180
src/main.rs
@@ -1,23 +1,34 @@
|
||||
#![allow(
|
||||
clippy::collapsible_else_if,
|
||||
clippy::collapsible_if,
|
||||
clippy::implicit_hasher,
|
||||
clippy::match_same_arms,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::must_use_candidate,
|
||||
clippy::similar_names,
|
||||
clippy::too_many_lines
|
||||
)]
|
||||
|
||||
use std::io::{self, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::time::Instant;
|
||||
|
||||
use ::ruff::cache;
|
||||
use ::ruff::checks::{CheckCode, CheckKind};
|
||||
use ::ruff::cli::{collect_per_file_ignores, extract_log_level, Cli};
|
||||
use ::ruff::fs::iter_python_files;
|
||||
use ::ruff::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin};
|
||||
use ::ruff::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin, Diagnostics};
|
||||
use ::ruff::logging::{set_up_logging, LogLevel};
|
||||
use ::ruff::message::Message;
|
||||
use ::ruff::printer::{Printer, SerializationFormat};
|
||||
use ::ruff::settings::configuration::Configuration;
|
||||
use ::ruff::settings::types::FilePattern;
|
||||
use ::ruff::settings::user::UserConfiguration;
|
||||
use ::ruff::settings::{pyproject, Settings};
|
||||
#[cfg(feature = "update-informer")]
|
||||
use ::ruff::updates;
|
||||
use ::ruff::{cache, commands};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use colored::Colorize;
|
||||
@@ -25,12 +36,13 @@ use log::{debug, error};
|
||||
use notify::{raw_watcher, RecursiveMode, Watcher};
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rayon::prelude::*;
|
||||
use rustpython_ast::Location;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
/// Shim that calls par_iter except for wasm because there's no wasm support in
|
||||
/// rayon yet (there is a shim to be used for the web, but it requires js
|
||||
/// cooperation) Unfortunately, ParallelIterator does not implement Iterator so
|
||||
/// the signatures diverge
|
||||
/// Shim that calls `par_iter` except for wasm because there's no wasm support
|
||||
/// in rayon yet (there is a shim to be used for the web, but it requires js
|
||||
/// cooperation) Unfortunately, `ParallelIterator` does not implement `Iterator`
|
||||
/// so the signatures diverge
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
fn par_iter<T: Sync>(iterable: &Vec<T>) -> impl ParallelIterator<Item = &T> {
|
||||
iterable.par_iter()
|
||||
@@ -41,48 +53,20 @@ fn par_iter<T: Sync>(iterable: &Vec<T>) -> impl Iterator<Item = &T> {
|
||||
iterable.iter()
|
||||
}
|
||||
|
||||
fn show_settings(
|
||||
configuration: Configuration,
|
||||
project_root: Option<PathBuf>,
|
||||
pyproject: Option<PathBuf>,
|
||||
) {
|
||||
println!(
|
||||
"{:#?}",
|
||||
UserConfiguration::from_configuration(configuration, project_root, pyproject)
|
||||
);
|
||||
}
|
||||
|
||||
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 read_from_stdin() -> Result<String> {
|
||||
let mut buffer = String::new();
|
||||
io::stdin().lock().read_to_string(&mut buffer)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
fn run_once_stdin(settings: &Settings, filename: &Path, autofix: bool) -> Result<Vec<Message>> {
|
||||
fn run_once_stdin(settings: &Settings, filename: &Path, autofix: bool) -> Result<Diagnostics> {
|
||||
let stdin = read_from_stdin()?;
|
||||
let mut messages = lint_stdin(filename, &stdin, settings, &autofix.into())?;
|
||||
messages.sort_unstable();
|
||||
Ok(messages)
|
||||
let mut diagnostics = lint_stdin(filename, &stdin, settings, &autofix.into())?;
|
||||
diagnostics.messages.sort_unstable();
|
||||
Ok(diagnostics)
|
||||
}
|
||||
|
||||
fn run_once(
|
||||
files: &[PathBuf],
|
||||
settings: &Settings,
|
||||
cache: bool,
|
||||
autofix: bool,
|
||||
) -> Result<Vec<Message>> {
|
||||
fn run_once(files: &[PathBuf], settings: &Settings, cache: bool, autofix: bool) -> Diagnostics {
|
||||
// Collect all the files to check.
|
||||
let start = Instant::now();
|
||||
let paths: Vec<Result<DirEntry, walkdir::Error>> = files
|
||||
@@ -93,7 +77,7 @@ fn run_once(
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
let start = Instant::now();
|
||||
let mut messages: Vec<Message> = par_iter(&paths)
|
||||
let mut diagnostics: Diagnostics = par_iter(&paths)
|
||||
.map(|entry| {
|
||||
match entry {
|
||||
Ok(entry) => {
|
||||
@@ -110,63 +94,67 @@ fn run_once(
|
||||
.unwrap_or_else(|(path, message)| {
|
||||
if let Some(path) = path {
|
||||
if settings.enabled.contains(&CheckCode::E902) {
|
||||
vec![Message {
|
||||
Diagnostics::new(vec![Message {
|
||||
kind: CheckKind::IOError(message),
|
||||
fixed: false,
|
||||
location: Default::default(),
|
||||
end_location: Default::default(),
|
||||
location: Location::default(),
|
||||
end_location: Location::default(),
|
||||
filename: path.to_string_lossy().to_string(),
|
||||
source: None,
|
||||
}]
|
||||
}])
|
||||
} else {
|
||||
error!("Failed to check {}: {message}", path.to_string_lossy());
|
||||
vec![]
|
||||
Diagnostics::default()
|
||||
}
|
||||
} else {
|
||||
error!("{message}");
|
||||
vec![]
|
||||
Diagnostics::default()
|
||||
}
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
.reduce(Diagnostics::default, |mut acc, item| {
|
||||
acc += item;
|
||||
acc
|
||||
});
|
||||
|
||||
messages.sort_unstable();
|
||||
diagnostics.messages.sort_unstable();
|
||||
let duration = start.elapsed();
|
||||
debug!("Checked files in: {:?}", duration);
|
||||
|
||||
Ok(messages)
|
||||
diagnostics
|
||||
}
|
||||
|
||||
fn add_noqa(files: &[PathBuf], settings: &Settings) -> Result<usize> {
|
||||
fn add_noqa(files: &[PathBuf], settings: &Settings) -> usize {
|
||||
// Collect all the files to check.
|
||||
let start = Instant::now();
|
||||
let paths: Vec<Result<DirEntry, walkdir::Error>> = files
|
||||
let paths: Vec<DirEntry> = files
|
||||
.iter()
|
||||
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
|
||||
.flatten()
|
||||
.collect();
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
let start = Instant::now();
|
||||
let modifications: usize = par_iter(&paths)
|
||||
.map(|entry| match entry {
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
add_noqa_to_path(path, settings)
|
||||
.filter_map(|entry| {
|
||||
let path = entry.path();
|
||||
match add_noqa_to_path(path, settings) {
|
||||
Ok(count) => Some(count),
|
||||
Err(e) => {
|
||||
error!("Failed to add noqa to {}: {e}", path.to_string_lossy());
|
||||
None
|
||||
}
|
||||
}
|
||||
Err(_) => Ok(0),
|
||||
})
|
||||
.flatten()
|
||||
.sum();
|
||||
|
||||
let duration = start.elapsed();
|
||||
debug!("Added noqa to files in: {:?}", duration);
|
||||
|
||||
Ok(modifications)
|
||||
modifications
|
||||
}
|
||||
|
||||
fn autoformat(files: &[PathBuf], settings: &Settings) -> Result<usize> {
|
||||
fn autoformat(files: &[PathBuf], settings: &Settings) -> usize {
|
||||
// Collect all the files to format.
|
||||
let start = Instant::now();
|
||||
let paths: Vec<DirEntry> = files
|
||||
@@ -179,17 +167,22 @@ fn autoformat(files: &[PathBuf], settings: &Settings) -> Result<usize> {
|
||||
|
||||
let start = Instant::now();
|
||||
let modifications = par_iter(&paths)
|
||||
.map(|entry| {
|
||||
.filter_map(|entry| {
|
||||
let path = entry.path();
|
||||
autoformat_path(path)
|
||||
match autoformat_path(path) {
|
||||
Ok(()) => Some(()),
|
||||
Err(e) => {
|
||||
error!("Failed to autoformat {}: {e}", path.to_string_lossy());
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.count();
|
||||
|
||||
let duration = start.elapsed();
|
||||
debug!("Auto-formatted files in: {:?}", duration);
|
||||
|
||||
Ok(modifications)
|
||||
modifications
|
||||
}
|
||||
|
||||
fn inner_main() -> Result<ExitCode> {
|
||||
@@ -215,28 +208,16 @@ fn inner_main() -> Result<ExitCode> {
|
||||
};
|
||||
|
||||
// Reconcile configuration from pyproject.toml and command-line arguments.
|
||||
let exclude: Vec<FilePattern> = cli
|
||||
.exclude
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, project_root.as_ref()))
|
||||
.collect::<Result<_>>()?;
|
||||
let extend_exclude: Vec<FilePattern> = cli
|
||||
.extend_exclude
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, project_root.as_ref()))
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
let mut configuration =
|
||||
Configuration::from_pyproject(pyproject.as_ref(), project_root.as_ref())?;
|
||||
if !exclude.is_empty() {
|
||||
configuration.exclude = exclude;
|
||||
if !cli.exclude.is_empty() {
|
||||
configuration.exclude = cli.exclude;
|
||||
}
|
||||
if !extend_exclude.is_empty() {
|
||||
configuration.extend_exclude = extend_exclude;
|
||||
if !cli.extend_exclude.is_empty() {
|
||||
configuration.extend_exclude = cli.extend_exclude;
|
||||
}
|
||||
if !cli.per_file_ignores.is_empty() {
|
||||
configuration.per_file_ignores =
|
||||
collect_per_file_ignores(cli.per_file_ignores, project_root.as_ref())?;
|
||||
configuration.per_file_ignores = collect_per_file_ignores(cli.per_file_ignores);
|
||||
}
|
||||
if !cli.select.is_empty() {
|
||||
configuration.select = cli.select;
|
||||
@@ -275,21 +256,26 @@ fn inner_main() -> Result<ExitCode> {
|
||||
configuration.show_source = true;
|
||||
}
|
||||
|
||||
if let Some(code) = cli.explain {
|
||||
commands::explain(&code, cli.format)?;
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
if cli.show_settings && cli.show_files {
|
||||
eprintln!("Error: specify --show-settings or show-files (not both).");
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
if cli.show_settings {
|
||||
show_settings(configuration, project_root, pyproject);
|
||||
commands::show_settings(&configuration, project_root.as_ref(), pyproject.as_ref());
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
// Extract settings for internal use.
|
||||
let fix_enabled: bool = configuration.fix;
|
||||
let settings = Settings::from_configuration(configuration);
|
||||
let settings = Settings::from_configuration(configuration, project_root.as_ref())?;
|
||||
|
||||
if cli.show_files {
|
||||
show_files(&cli.files, &settings);
|
||||
commands::show_files(&cli.files, &settings);
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
@@ -322,7 +308,7 @@ fn inner_main() -> Result<ExitCode> {
|
||||
printer.clear_screen()?;
|
||||
printer.write_to_user("Starting linter in watch mode...\n");
|
||||
|
||||
let messages = run_once(&cli.files, &settings, cache_enabled, false)?;
|
||||
let messages = run_once(&cli.files, &settings, cache_enabled, false);
|
||||
printer.write_continuously(&messages)?;
|
||||
|
||||
// Configure the file watcher.
|
||||
@@ -340,7 +326,7 @@ fn inner_main() -> Result<ExitCode> {
|
||||
printer.clear_screen()?;
|
||||
printer.write_to_user("File change detected...\n");
|
||||
|
||||
let messages = run_once(&cli.files, &settings, cache_enabled, false)?;
|
||||
let messages = run_once(&cli.files, &settings, cache_enabled, false);
|
||||
printer.write_continuously(&messages)?;
|
||||
}
|
||||
}
|
||||
@@ -349,12 +335,12 @@ fn inner_main() -> Result<ExitCode> {
|
||||
}
|
||||
}
|
||||
} else if cli.add_noqa {
|
||||
let modifications = add_noqa(&cli.files, &settings)?;
|
||||
let modifications = add_noqa(&cli.files, &settings);
|
||||
if modifications > 0 && log_level >= LogLevel::Default {
|
||||
println!("Added {modifications} noqa directives.");
|
||||
}
|
||||
} else if cli.autoformat {
|
||||
let modifications = autoformat(&cli.files, &settings)?;
|
||||
let modifications = autoformat(&cli.files, &settings);
|
||||
if modifications > 0 && log_level >= LogLevel::Default {
|
||||
println!("Formatted {modifications} files.");
|
||||
}
|
||||
@@ -362,28 +348,28 @@ fn inner_main() -> Result<ExitCode> {
|
||||
let is_stdin = cli.files == vec![PathBuf::from("-")];
|
||||
|
||||
// Generate lint violations.
|
||||
let messages = if is_stdin {
|
||||
let diagnostics = if is_stdin {
|
||||
let filename = cli.stdin_filename.unwrap_or_else(|| "-".to_string());
|
||||
let path = Path::new(&filename);
|
||||
run_once_stdin(&settings, path, fix_enabled)?
|
||||
} else {
|
||||
run_once(&cli.files, &settings, cache_enabled, fix_enabled)?
|
||||
run_once(&cli.files, &settings, cache_enabled, fix_enabled)
|
||||
};
|
||||
|
||||
// Always try to print violations (the printer itself may suppress output),
|
||||
// unless we're writing fixes via stdin (in which case, the transformed
|
||||
// source code goes to stdout).
|
||||
if !(is_stdin && fix_enabled) {
|
||||
printer.write_once(&messages)?;
|
||||
printer.write_once(&diagnostics)?;
|
||||
}
|
||||
|
||||
// Check for updates if we're in a non-silent log level.
|
||||
#[cfg(feature = "update-informer")]
|
||||
if !is_stdin && log_level >= LogLevel::Default && atty::is(atty::Stream::Stdout) {
|
||||
let _ = updates::check_for_updates();
|
||||
drop(updates::check_for_updates());
|
||||
}
|
||||
|
||||
if messages.iter().any(|message| !message.fixed) && !cli.exit_zero {
|
||||
if !diagnostics.messages.is_empty() && !cli.exit_zero {
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
}
|
||||
@@ -395,7 +381,7 @@ fn main() -> ExitCode {
|
||||
match inner_main() {
|
||||
Ok(code) => code,
|
||||
Err(err) => {
|
||||
eprintln!("{} {:?}", "error".red().bold(), err);
|
||||
eprintln!("{} {err:?}", "error".red().bold());
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user