From c515a1b31afe0b5d2ffdea0fb94876132288d802 Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Tue, 14 Mar 2023 02:23:00 +0800 Subject: [PATCH 01/49] PYI011: allow `math` constants in defaults (#3484) --- .../test/fixtures/flake8_pyi/PYI011.pyi | 40 +++++++ .../rules/flake8_pyi/rules/simple_defaults.rs | 28 ++++- ..._flake8_pyi__tests__PYI011_PYI011.pyi.snap | 104 ++++++++++++++++++ 3 files changed, 170 insertions(+), 2 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI011.pyi b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI011.pyi index ed40594b546c3..8678a0958ae43 100644 --- a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI011.pyi +++ b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI011.pyi @@ -61,3 +61,43 @@ def f22( x: complex = -42.5j # Error PYI011 Only simple default values allowed for typed arguments + 4.3j, ) -> None: ... +def f23( + x: bool = True, # OK +) -> None: ... +def f24( + x: float = 3.14, # OK +) -> None: ... +def f25( + x: float = -3.14, # OK +) -> None: ... +def f26( + x: complex = -3.14j, # OK +) -> None: ... +def f27( + x: complex = -3 - 3.14j, # OK +) -> None: ... +def f28( + x: float = math.tau, # OK +) -> None: ... +def f29( + x: float = math.inf, # OK +) -> None: ... +def f30( + x: float = -math.inf, # OK +) -> None: ... +def f31( + x: float = inf, # Error PYI011 Only simple default values allowed for typed arguments +) -> None: ... +def f32( + x: float = np.inf, # Error PYI011 Only simple default values allowed for typed arguments +) -> None: ... +def f33( + x: float = math.nan, # OK +) -> None: ... +def f34( + x: float = -math.nan, # Error PYI011 Only simple default values allowed for typed arguments +) -> None: ... +def f35( + x: complex = math.inf # Error PYI011 Only simple default values allowed for typed arguments + + 1j, +) -> None: ... diff --git a/crates/ruff/src/rules/flake8_pyi/rules/simple_defaults.rs b/crates/ruff/src/rules/flake8_pyi/rules/simple_defaults.rs index ca8affdc63cb9..8bafc22bdbadd 100644 --- a/crates/ruff/src/rules/flake8_pyi/rules/simple_defaults.rs +++ b/crates/ruff/src/rules/flake8_pyi/rules/simple_defaults.rs @@ -28,6 +28,14 @@ impl Violation for ArgumentSimpleDefaults { } } +const ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS: &[&[&str]] = &[ + &["math", "inf"], + &["math", "nan"], + &["math", "e"], + &["math", "pi"], + &["math", "tau"], +]; + const ALLOWED_ATTRIBUTES_IN_DEFAULTS: &[&[&str]] = &[ &["sys", "stdin"], &["sys", "stdout"], @@ -99,6 +107,21 @@ fn is_valid_default_value_with_annotation(default: &Expr, checker: &Checker) -> return checker.locator.slice(operand).len() <= 10; } } + // Ex) `-math.inf`, `-math.pi`, etc. + if let ExprKind::Attribute { .. } = &operand.node { + if checker + .ctx + .resolve_call_path(default) + .map_or(false, |call_path| { + ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS.iter().any(|target| { + // reject `-math.nan` + call_path.as_slice() == *target && *target != ["math", "nan"] + }) + }) + { + return true; + } + } } ExprKind::BinOp { left, @@ -134,14 +157,15 @@ fn is_valid_default_value_with_annotation(default: &Expr, checker: &Checker) -> } } } - // Ex) `sys.stdin`, etc. + // Ex) `math.inf`, `sys.stdin`, etc. ExprKind::Attribute { .. } => { if checker .ctx .resolve_call_path(default) .map_or(false, |call_path| { - ALLOWED_ATTRIBUTES_IN_DEFAULTS + ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS .iter() + .chain(ALLOWED_ATTRIBUTES_IN_DEFAULTS.iter()) .any(|target| call_path.as_slice() == *target) }) { diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI011_PYI011.pyi.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI011_PYI011.pyi.snap index d85bee351443c..484cf00790753 100644 --- a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI011_PYI011.pyi.snap +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI011_PYI011.pyi.snap @@ -145,4 +145,108 @@ expression: diagnostics column: 10 fix: ~ parent: ~ +- kind: + name: TypedArgumentSimpleDefaults + body: Only simple default values allowed for typed arguments + suggestion: ~ + fixable: false + location: + row: 80 + column: 15 + end_location: + row: 80 + column: 23 + fix: ~ + parent: ~ +- kind: + name: TypedArgumentSimpleDefaults + body: Only simple default values allowed for typed arguments + suggestion: ~ + fixable: false + location: + row: 83 + column: 15 + end_location: + row: 83 + column: 23 + fix: ~ + parent: ~ +- kind: + name: TypedArgumentSimpleDefaults + body: Only simple default values allowed for typed arguments + suggestion: ~ + fixable: false + location: + row: 86 + column: 15 + end_location: + row: 86 + column: 24 + fix: ~ + parent: ~ +- kind: + name: TypedArgumentSimpleDefaults + body: Only simple default values allowed for typed arguments + suggestion: ~ + fixable: false + location: + row: 89 + column: 15 + end_location: + row: 89 + column: 18 + fix: ~ + parent: ~ +- kind: + name: TypedArgumentSimpleDefaults + body: Only simple default values allowed for typed arguments + suggestion: ~ + fixable: false + location: + row: 92 + column: 15 + end_location: + row: 92 + column: 21 + fix: ~ + parent: ~ +- kind: + name: TypedArgumentSimpleDefaults + body: Only simple default values allowed for typed arguments + suggestion: ~ + fixable: false + location: + row: 95 + column: 15 + end_location: + row: 95 + column: 23 + fix: ~ + parent: ~ +- kind: + name: TypedArgumentSimpleDefaults + body: Only simple default values allowed for typed arguments + suggestion: ~ + fixable: false + location: + row: 98 + column: 15 + end_location: + row: 98 + column: 24 + fix: ~ + parent: ~ +- kind: + name: TypedArgumentSimpleDefaults + body: Only simple default values allowed for typed arguments + suggestion: ~ + fixable: false + location: + row: 101 + column: 17 + end_location: + row: 102 + column: 8 + fix: ~ + parent: ~ From a8c1915e2efbcd0a4bd798ed3e81cdc18f8e1890 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 13 Mar 2023 15:52:05 -0400 Subject: [PATCH 02/49] Remove erroneous C4-to-C40 redirect (#3488) --- crates/ruff/src/rule_redirects.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/ruff/src/rule_redirects.rs b/crates/ruff/src/rule_redirects.rs index 8a8c214965156..a7fce22eecc60 100644 --- a/crates/ruff/src/rule_redirects.rs +++ b/crates/ruff/src/rule_redirects.rs @@ -18,7 +18,6 @@ static REDIRECTS: Lazy> = Lazy::new(|| { // The following are here because we don't yet have the many-to-one mapping enabled. ("SIM111", "SIM110"), // The following are deprecated. - ("C4", "C40"), ("C9", "C90"), ("T1", "T10"), ("T2", "T20"), From a6e998d6391605407c8cce65a127671c98211e3e Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 13 Mar 2023 16:48:43 -0400 Subject: [PATCH 03/49] Remove Wasm-specific Rayon workarounds (#3490) --- crates/ruff_cli/src/commands/add_noqa.rs | 4 ++-- crates/ruff_cli/src/commands/run.rs | 4 ++-- crates/ruff_cli/src/iterators.rs | 16 ---------------- crates/ruff_cli/src/lib.rs | 1 - 4 files changed, 4 insertions(+), 21 deletions(-) delete mode 100644 crates/ruff_cli/src/iterators.rs diff --git a/crates/ruff_cli/src/commands/add_noqa.rs b/crates/ruff_cli/src/commands/add_noqa.rs index b3d2b0af6f1b7..f56670d7bace0 100644 --- a/crates/ruff_cli/src/commands/add_noqa.rs +++ b/crates/ruff_cli/src/commands/add_noqa.rs @@ -11,7 +11,6 @@ use ruff::resolver::PyprojectDiscovery; use ruff::{packaging, resolver, warn_user_once}; use crate::args::Overrides; -use crate::iterators::par_iter; /// Add `noqa` directives to a collection of files. pub fn add_noqa( @@ -42,7 +41,8 @@ pub fn add_noqa( ); let start = Instant::now(); - let modifications: usize = par_iter(&paths) + let modifications: usize = paths + .par_iter() .flatten() .filter_map(|entry| { let path = entry.path(); diff --git a/crates/ruff_cli/src/commands/run.rs b/crates/ruff_cli/src/commands/run.rs index cb54a59b4f965..308744fb2c16b 100644 --- a/crates/ruff_cli/src/commands/run.rs +++ b/crates/ruff_cli/src/commands/run.rs @@ -19,7 +19,6 @@ use ruff_diagnostics::Diagnostic; use crate::args::Overrides; use crate::cache; use crate::diagnostics::{lint_path, Diagnostics}; -use crate::iterators::par_iter; /// Run the linter over a collection of files. pub fn run( @@ -76,7 +75,8 @@ pub fn run( ); let start = Instant::now(); - let mut diagnostics: Diagnostics = par_iter(&paths) + let mut diagnostics: Diagnostics = paths + .par_iter() .map(|entry| { match entry { Ok(entry) => { diff --git a/crates/ruff_cli/src/iterators.rs b/crates/ruff_cli/src/iterators.rs deleted file mode 100644 index 80603b1637f93..0000000000000 --- a/crates/ruff_cli/src/iterators.rs +++ /dev/null @@ -1,16 +0,0 @@ -#[cfg(not(target_family = "wasm"))] -use rayon::prelude::*; - -/// 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"))] -pub fn par_iter(iterable: &[T]) -> impl ParallelIterator { - iterable.par_iter() -} - -#[cfg(target_family = "wasm")] -pub fn par_iter(iterable: &[T]) -> impl Iterator { - iterable.iter() -} diff --git a/crates/ruff_cli/src/lib.rs b/crates/ruff_cli/src/lib.rs index 86faf3cb8c1be..b95889c16c49c 100644 --- a/crates/ruff_cli/src/lib.rs +++ b/crates/ruff_cli/src/lib.rs @@ -18,7 +18,6 @@ pub mod args; mod cache; mod commands; mod diagnostics; -mod iterators; mod printer; mod resolve; From 1e5db58b7b8d86227a46e061994a728c12aa10c9 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 13 Mar 2023 17:13:47 -0400 Subject: [PATCH 04/49] Include individual path checks in --verbose logging (#3489) --- crates/ruff/src/resolver.rs | 2 +- crates/ruff_cli/src/commands/add_noqa.rs | 2 +- crates/ruff_cli/src/commands/run.rs | 5 +---- crates/ruff_cli/src/diagnostics.rs | 4 +++- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/ruff/src/resolver.rs b/crates/ruff/src/resolver.rs index 6a166c83abfe5..80aecd1699f50 100644 --- a/crates/ruff/src/resolver.rs +++ b/crates/ruff/src/resolver.rs @@ -128,7 +128,7 @@ pub fn resolve_configuration( // Resolve the current path. let options = pyproject::load_options(&path) - .map_err(|err| anyhow!("Failed to parse `{}`: {}", path.to_string_lossy(), err))?; + .map_err(|err| anyhow!("Failed to parse `{}`: {}", path.display(), err))?; let project_root = relativity.resolve(&path); let configuration = Configuration::from_options(options, &project_root)?; diff --git a/crates/ruff_cli/src/commands/add_noqa.rs b/crates/ruff_cli/src/commands/add_noqa.rs index f56670d7bace0..40aab27fc0ed8 100644 --- a/crates/ruff_cli/src/commands/add_noqa.rs +++ b/crates/ruff_cli/src/commands/add_noqa.rs @@ -54,7 +54,7 @@ pub fn add_noqa( match add_noqa_to_path(path, package, settings) { Ok(count) => Some(count), Err(e) => { - error!("Failed to add noqa to {}: {e}", path.to_string_lossy()); + error!("Failed to add noqa to {}: {e}", path.display()); None } } diff --git a/crates/ruff_cli/src/commands/run.rs b/crates/ruff_cli/src/commands/run.rs index 308744fb2c16b..3b79f7b22a825 100644 --- a/crates/ruff_cli/src/commands/run.rs +++ b/crates/ruff_cli/src/commands/run.rs @@ -44,10 +44,7 @@ pub fn run( if cache.into() { fn init_cache(path: &std::path::Path) { if let Err(e) = cache::init(path) { - error!( - "Failed to initialize cache at {}: {e:?}", - path.to_string_lossy() - ); + error!("Failed to initialize cache at {}: {e:?}", path.display()); } } diff --git a/crates/ruff_cli/src/diagnostics.rs b/crates/ruff_cli/src/diagnostics.rs index 2b81a3d77a655..573f1273cc2ff 100644 --- a/crates/ruff_cli/src/diagnostics.rs +++ b/crates/ruff_cli/src/diagnostics.rs @@ -74,7 +74,7 @@ pub fn lint_path( if let Some(messages) = cache::get(path, package.as_ref(), &metadata, settings, autofix.into()) { - debug!("Cache hit for: {}", path.to_string_lossy()); + debug!("Cache hit for: {}", path.display()); return Ok(Diagnostics::new(messages)); } Some(metadata) @@ -82,6 +82,8 @@ pub fn lint_path( None }; + debug!("Checking: {}", path.display()); + // Read the file from disk. let contents = std::fs::read_to_string(path)?; From 62ff3b62e3fcb3c37cc99fbe352334accc18931f Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 13 Mar 2023 18:14:39 -0400 Subject: [PATCH 05/49] Add `requires-python` inference to docs (#3495) --- crates/ruff/src/settings/options.rs | 8 +++++--- docs/tutorial.md | 6 ++++-- ruff.schema.json | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/ruff/src/settings/options.rs b/crates/ruff/src/settings/options.rs index 525980db7e5a4..ea199aa5bfe75 100644 --- a/crates/ruff/src/settings/options.rs +++ b/crates/ruff/src/settings/options.rs @@ -376,9 +376,11 @@ pub struct Options { "# )] /// The Python version to target, e.g., when considering automatic code - /// upgrades, like rewriting type annotations. Note that the target - /// version will _not_ be inferred from the _current_ Python version, - /// and instead must be specified explicitly (as seen below). + /// upgrades, like rewriting type annotations. + /// + /// If omitted, the target version will be inferred from the + /// `project.requires-python` field in the relevant `pyproject.toml` + /// (e.g., `requires-python = ">=3.8"`), if present. pub target_version: Option, #[option( default = r#"["TODO", "FIXME", "XXX"]"#, diff --git a/docs/tutorial.md b/docs/tutorial.md index 577ebfac60c2f..02959de35df51 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -93,11 +93,13 @@ For a full enumeration of the supported settings, see [_Settings_](settings.md). specifically, we'll want to make note of the minimum supported Python version: ```toml +[project] +# Support Python 3.10+. +requires-python = ">=3.10" + [tool.ruff] # Decrease the maximum line length to 79 characters. line-length = 79 -# Support Python 3.10+. -target-version = "py310" src = ["src"] ``` diff --git a/ruff.schema.json b/ruff.schema.json index df2bece8a9d13..eb617a6f594dd 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -461,7 +461,7 @@ } }, "target-version": { - "description": "The Python version to target, e.g., when considering automatic code upgrades, like rewriting type annotations. Note that the target version will _not_ be inferred from the _current_ Python version, and instead must be specified explicitly (as seen below).", + "description": "The Python version to target, e.g., when considering automatic code upgrades, like rewriting type annotations.\n\nIf omitted, the target version will be inferred from the `project.requires-python` field in the relevant `pyproject.toml` (e.g., `requires-python = \">=3.8\"`), if present.", "anyOf": [ { "$ref": "#/definitions/PythonVersion" From a82fe4a1397a1e271ead3b7662df0d467f7f4ba3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 14 Mar 2023 00:19:41 +0100 Subject: [PATCH 06/49] Fix lack of `not` in `PLC1901` error message (#3497) --- .../src/rules/pylint/rules/compare_to_empty_string.rs | 4 ++-- ...int__tests__PLC1901_compare_to_empty_string.py.snap | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/ruff/src/rules/pylint/rules/compare_to_empty_string.rs b/crates/ruff/src/rules/pylint/rules/compare_to_empty_string.rs index d287074298b9b..06e23b065f8e1 100644 --- a/crates/ruff/src/rules/pylint/rules/compare_to_empty_string.rs +++ b/crates/ruff/src/rules/pylint/rules/compare_to_empty_string.rs @@ -34,8 +34,8 @@ impl TryFrom<&Cmpop> for EmptyStringCmpop { impl EmptyStringCmpop { pub fn into_unary(self) -> &'static str { match self { - Self::Is | Self::Eq => "", - Self::IsNot | Self::NotEq => "not ", + Self::Is | Self::Eq => "not ", + Self::IsNot | Self::NotEq => "", } } } diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC1901_compare_to_empty_string.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC1901_compare_to_empty_string.py.snap index d26a45408f3d6..22a19ceab507f 100644 --- a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC1901_compare_to_empty_string.py.snap +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC1901_compare_to_empty_string.py.snap @@ -4,7 +4,7 @@ expression: diagnostics --- - kind: name: CompareToEmptyString - body: "`x is \"\"` can be simplified to `x` as an empty string is falsey" + body: "`x is \"\"` can be simplified to `not x` as an empty string is falsey" suggestion: ~ fixable: false location: @@ -17,7 +17,7 @@ expression: diagnostics parent: ~ - kind: name: CompareToEmptyString - body: "`x == \"\"` can be simplified to `x` as an empty string is falsey" + body: "`x == \"\"` can be simplified to `not x` as an empty string is falsey" suggestion: ~ fixable: false location: @@ -30,7 +30,7 @@ expression: diagnostics parent: ~ - kind: name: CompareToEmptyString - body: "`y is not \"\"` can be simplified to `not y` as an empty string is falsey" + body: "`y is not \"\"` can be simplified to `y` as an empty string is falsey" suggestion: ~ fixable: false location: @@ -43,7 +43,7 @@ expression: diagnostics parent: ~ - kind: name: CompareToEmptyString - body: "`y != \"\"` can be simplified to `not y` as an empty string is falsey" + body: "`y != \"\"` can be simplified to `y` as an empty string is falsey" suggestion: ~ fixable: false location: @@ -56,7 +56,7 @@ expression: diagnostics parent: ~ - kind: name: CompareToEmptyString - body: "`\"\" != z` can be simplified to `not z` as an empty string is falsey" + body: "`\"\" != z` can be simplified to `z` as an empty string is falsey" suggestion: ~ fixable: false location: From 3a7bdb39c99d1820289c28599e5c7d8397d0b32a Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Mon, 13 Mar 2023 22:37:12 -0400 Subject: [PATCH 07/49] Fix base ref determination for artifact download in ecosystem CI check (#3499) --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1c605a2c5a258..a2c0f990dce94 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -147,7 +147,7 @@ jobs: - uses: dawidd6/action-download-artifact@v2 with: name: ruff - branch: ${{ github.event.pull_request.base_ref }} + branch: ${{ github.event.pull_request.base.ref }} check_artifacts: true - name: Run ecosystem check run: | From d5700d7c6987a54843830b27833109b28ecd5953 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 14 Mar 2023 08:35:07 +0100 Subject: [PATCH 08/49] Add Micro Benchmark (#3466) --- .cargo/config.toml | 1 + Cargo.lock | 12 ++ Cargo.toml | 10 ++ crates/ruff/Cargo.toml | 12 +- crates/ruff_benchmark/Cargo.toml | 31 +++++ crates/ruff_benchmark/README.md | 87 ++++++++++++++ crates/ruff_benchmark/benches/linter.rs | 71 ++++++++++++ crates/ruff_benchmark/src/lib.rs | 144 ++++++++++++++++++++++++ 8 files changed, 362 insertions(+), 6 deletions(-) create mode 100644 crates/ruff_benchmark/Cargo.toml create mode 100644 crates/ruff_benchmark/README.md create mode 100644 crates/ruff_benchmark/benches/linter.rs create mode 100644 crates/ruff_benchmark/src/lib.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 5fef0dd0b68c3..27bc03c090fb7 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,6 @@ [alias] dev = "run --package ruff_dev --bin ruff_dev" +benchmark = "bench -p ruff_benchmark --" [target.'cfg(all())'] rustflags = [ diff --git a/Cargo.lock b/Cargo.lock index c4efad6169f5e..164cdfaca96e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2024,6 +2024,18 @@ dependencies = [ "toml", ] +[[package]] +name = "ruff_benchmark" +version = "0.0.0" +dependencies = [ + "criterion", + "mimalloc", + "ruff", + "tikv-jemallocator", + "ureq", + "url", +] + [[package]] name = "ruff_cache" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index d8723972f0eb3..76e3891cbff00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,10 @@ members = ["crates/*"] [workspace.package] edition = "2021" rust-version = "1.67" +homepage = "https://beta.ruff.rs/docs/" +documentation = "https://beta.ruff.rs/docs/" +repository = "https://github.com/charliermarsh/ruff" +authors = ["Charlie Marsh "] [workspace.dependencies] anyhow = { version = "1.0.69" } @@ -59,3 +63,9 @@ opt-level = 3 # https://github.com/bytecodealliance/wasm-tools/blob/b5c3d98e40590512a3b12470ef358d5c7b983b15/crates/wasmparser/src/limits.rs#L29 [profile.dev.package.rustpython-parser] opt-level = 1 + +# Use the `--profile release-debug` flag to show symbols in release mode. +# e.g. `cargo build --profile release-debug` +[profile.release-debug] +inherits = "release" +debug = 1 diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index 7414731c9aa54..bc67ee1b5bb55 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "ruff" version = "0.0.255" -authors = ["Charlie Marsh "] -edition = { workspace = true } -rust-version = { workspace = true } -documentation = "https://github.com/charliermarsh/ruff" -homepage = "https://github.com/charliermarsh/ruff" -repository = "https://github.com/charliermarsh/ruff" +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +documentation.workspace = true +homepage.workspace = true +repository.workspace = true readme = "README.md" license = "MIT" diff --git a/crates/ruff_benchmark/Cargo.toml b/crates/ruff_benchmark/Cargo.toml new file mode 100644 index 0000000000000..0d4899b11e1e4 --- /dev/null +++ b/crates/ruff_benchmark/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "ruff_benchmark" +version = "0.0.0" +publish = false +edition.workspace = true +authors.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true +description = "Ruff Micro-benchmarks" + +[lib] +bench = false + +[[bench]] +name = "linter" +harness = false + +[dependencies] +ruff.path = "../ruff" +url = "2.3.1" +ureq = "2.6.2" + +[dev-dependencies] +criterion = { version = "0.4.0"} + +[target.'cfg(target_os = "windows")'.dependencies] +mimalloc = "0.1.34" + +[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dependencies] +tikv-jemallocator = "0.5.0" diff --git a/crates/ruff_benchmark/README.md b/crates/ruff_benchmark/README.md new file mode 100644 index 0000000000000..292d0dac3b80d --- /dev/null +++ b/crates/ruff_benchmark/README.md @@ -0,0 +1,87 @@ +## Ruff Micro-benchmarks + +Benchmarks for the different Ruff-tools. + +### Run Benchmark + +You can run the benchmarks with + +```shell +cargo benchmark +``` + +### Benchmark driven Development + +You can use `--save-baseline=` to store an initial baseline benchmark (e.g. on `main`) and then use +`--benchmark=` to compare against that benchmark. Criterion will print a message telling you if the benchmark improved/regressed compared to that baseline. + +```shell +# Run once on your "baseline" code +cargo benchmark --save-baseline=main + +# Then iterate with +cargo benchmark --baseline=main +``` + +### PR Summary +You can use `--save-baseline` and `critcmp` to get a pretty comparison between two recordings. +This is useful to illustrate the improvements of a PR. + +```shell +# On main +cargo benchmark --save-baseline=main + +# After applying your changes +cargo benchmark --save-baseline=pr + +critcmp main pr +``` + +You must install [`critcmp`](https://github.com/BurntSushi/critcmp) for the comparison. + +```bash +cargo install critcmp +``` + +### Tips + +* Use `cargo benchmark ` to only run specific benchmarks. For example: `cargo benchmark linter/pydantic` to only run the pydantic tests. +* Use `cargo benchmark --quiet` for a more cleaned up output (without statistical relevance) +* Use `cargo benchmark --quick` to get faster results (more prone to noise) + +## Profiling + +### Linux + +Install `perf` and build `ruff_benchmark` with the `release-debug` profile and then run it with perf + +```shell +cargo bench -p ruff_benchmark --no-run --profile=release-debug && perf record -g -F 9999 cargo bench -p ruff_benchmark --profile=release-debug -- --profile-time=1 +``` + +Then convert the recorded profile + +```shell +perf script -F +pid > /tmp/test.perf +``` + +You can now view the converted file with [firefox profiler](https://profiler.firefox.com/) + +You can find a more in-depth guide [here](https://profiler.firefox.com/docs/#/./guide-perf-profiling) + +### Mac + +Install [`cargo-instruments`](https://crates.io/crates/cargo-instruments): + +```shell +cargo install cargo-instruments +``` + +Then run the profiler with + +```shell +cargo instruments -t time --bench linter --profile release-debug -p ruff_benchmark -- --profile-time=1 +``` + +* `-t`: Specifies what to profile. Useful options are `time` to profile the wall time and `alloc` for profiling the allocations. +* You may want to pass an additional filter to run a single test file diff --git a/crates/ruff_benchmark/benches/linter.rs b/crates/ruff_benchmark/benches/linter.rs new file mode 100644 index 0000000000000..c0777b6e6cdf0 --- /dev/null +++ b/crates/ruff_benchmark/benches/linter.rs @@ -0,0 +1,71 @@ +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use ruff::linter::lint_only; +use ruff::settings::{flags, Settings}; +use ruff_benchmark::{TestCase, TestCaseSpeed, TestFile, TestFileDownloadError}; +use std::time::Duration; + +#[cfg(target_os = "windows")] +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + +#[cfg(all( + not(target_os = "windows"), + not(target_os = "openbsd"), + any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "powerpc64" + ) +))] +#[global_allocator] +static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + +fn create_test_cases() -> Result, TestFileDownloadError> { + Ok(vec![ + TestCase::fast(TestFile::try_download("numpy/globals.py", "https://github.com/numpy/numpy/blob/89d64415e349ca75a25250f22b874aa16e5c0973/numpy/_globals.py")?), + TestCase::normal(TestFile::try_download( + "pydantic/types.py", + "https://raw.githubusercontent.com/pydantic/pydantic/main/pydantic/types.py", + )?), + TestCase::normal(TestFile::try_download("numpy/ctypeslib.py", "https://github.com/numpy/numpy/blob/main/numpy/ctypeslib.py")?), + TestCase::slow(TestFile::try_download( + "large/dataset.py", + "https://raw.githubusercontent.com/DHI/mikeio/b7d26418f4db2909b0aa965253dbe83194d7bb5b/tests/test_dataset.py", + )?), + ]) +} + +fn benchmark_linter(criterion: &mut Criterion) { + let test_cases = create_test_cases().unwrap(); + let mut group = criterion.benchmark_group("linter"); + + for case in test_cases { + group.throughput(Throughput::Bytes(case.code().len() as u64)); + group.measurement_time(match case.speed() { + TestCaseSpeed::Fast => Duration::from_secs(10), + TestCaseSpeed::Normal => Duration::from_secs(20), + TestCaseSpeed::Slow => Duration::from_secs(30), + }); + group.bench_with_input( + BenchmarkId::from_parameter(case.name()), + &case, + |b, case| { + b.iter(|| { + lint_only( + case.code(), + &case.path(), + None, + &black_box(Settings::default()), + flags::Noqa::Enabled, + flags::Autofix::Enabled, + ) + }); + }, + ); + } + + group.finish(); +} + +criterion_group!(benches, benchmark_linter); +criterion_main!(benches); diff --git a/crates/ruff_benchmark/src/lib.rs b/crates/ruff_benchmark/src/lib.rs new file mode 100644 index 0000000000000..af7eb91ae961f --- /dev/null +++ b/crates/ruff_benchmark/src/lib.rs @@ -0,0 +1,144 @@ +use std::fmt::{Display, Formatter}; +use std::path::{Path, PathBuf}; +use url::Url; + +/// Relative size of a test case. Benchmarks can use it to configure the time for how long a benchmark should run to get stable results. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub enum TestCaseSpeed { + /// A test case that is fast to run + Fast, + + /// A normal test case + Normal, + + /// A slow test case + Slow, +} + +#[derive(Debug, Clone)] +pub struct TestCase { + file: TestFile, + speed: TestCaseSpeed, +} + +impl TestCase { + pub fn fast(file: TestFile) -> Self { + Self { + file, + speed: TestCaseSpeed::Fast, + } + } + + pub fn normal(file: TestFile) -> Self { + Self { + file, + speed: TestCaseSpeed::Normal, + } + } + + pub fn slow(file: TestFile) -> Self { + Self { + file, + speed: TestCaseSpeed::Slow, + } + } +} + +impl TestCase { + pub fn code(&self) -> &str { + &self.file.code + } + + pub fn name(&self) -> &str { + &self.file.name + } + + pub fn speed(&self) -> TestCaseSpeed { + self.speed + } + + pub fn path(&self) -> PathBuf { + Path::new("target").join(self.name()) + } +} + +#[derive(Debug, Clone)] +pub struct TestFile { + name: String, + code: String, +} + +impl TestFile { + pub fn new(name: String, code: String) -> Self { + Self { name, code } + } + + #[allow(clippy::print_stderr)] + pub fn try_download(name: &str, url: &str) -> Result { + let url = Url::parse(url)?; + + let cached_filename = Path::new("target").join(name); + + if let Ok(content) = std::fs::read_to_string(&cached_filename) { + Ok(TestFile::new(name.to_string(), content)) + } else { + // File not yet cached, download and cache it in the target directory + let response = ureq::get(url.as_str()).call()?; + + let content = response.into_string()?; + + // SAFETY: There's always the `target` directory + let parent = cached_filename.parent().unwrap(); + if let Err(error) = std::fs::create_dir_all(parent) { + eprintln!("Failed to crate the directory for the test case {name}: {error}"); + } else if let Err(error) = std::fs::write(cached_filename, &content) { + eprintln!("Failed to cache test case file downloaded from {url}: {error}"); + } + + Ok(TestFile::new(name.to_string(), content)) + } + } +} + +#[derive(Debug)] +pub enum TestFileDownloadError { + UrlParse(url::ParseError), + Request(Box), + Download(std::io::Error), +} + +impl From for TestFileDownloadError { + fn from(value: url::ParseError) -> Self { + Self::UrlParse(value) + } +} + +impl From for TestFileDownloadError { + fn from(value: ureq::Error) -> Self { + Self::Request(Box::new(value)) + } +} + +impl From for TestFileDownloadError { + fn from(value: std::io::Error) -> Self { + Self::Download(value) + } +} + +impl Display for TestFileDownloadError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TestFileDownloadError::UrlParse(inner) => { + write!(f, "Failed to parse url: {inner}") + } + TestFileDownloadError::Request(inner) => { + write!(f, "Failed to download file: {inner}") + } + TestFileDownloadError::Download(inner) => { + write!(f, "Failed to download file: {inner}") + } + } + } +} + +impl std::error::Error for TestFileDownloadError {} From 78c2b0ac47044e0267366f1873a8d4feeb4fe468 Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Tue, 14 Mar 2023 22:31:26 +0800 Subject: [PATCH 09/49] ci: add dependabot integration for GitHub Actions (#3504) --- .github/dependabot.yml | 11 +++++++++++ .github/workflows/ci.yaml | 12 ++++++------ .github/workflows/docs.yaml | 4 ++-- .github/workflows/flake8-to-ruff.yaml | 4 ++-- .github/workflows/playground.yaml | 2 +- .github/workflows/ruff.yaml | 4 ++-- .pre-commit-config.yaml | 2 +- 7 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000000..67412d272c78f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "12:00" + timezone: "America/New_York" + commit-message: + prefix: "ci(deps)" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a2c0f990dce94..1c48a0c44abcf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -34,7 +34,7 @@ jobs: - name: "Install Rust toolchain" run: | rustup component add clippy - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo clippy --workspace --all-targets --all-features -- -D warnings cargo-clippy-wasm: @@ -46,7 +46,7 @@ jobs: run: | rustup component add clippy rustup target add wasm32-unknown-unknown - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features -- -D warnings cargo-test: @@ -59,9 +59,9 @@ jobs: - uses: actions/checkout@v3 - name: "Install Rust toolchain" run: rustup show - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo install cargo-insta - - run: pip install black[d]==22.12.0 + - run: pip install black[d]==23.1.0 - name: "Run tests (Ubuntu)" if: ${{ matrix.os == 'ubuntu-latest' }} run: | @@ -98,7 +98,7 @@ jobs: cache: "npm" cache-dependency-path: playground/package-lock.json - uses: jetli/wasm-pack-action@v0.4.0 - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - name: "Run wasm-pack" run: | cd crates/ruff_wasm @@ -111,7 +111,7 @@ jobs: - uses: actions/checkout@v3 - name: "Install Rust toolchain" run: rustup show - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: ./scripts/add_rule.py --name DoTheThing --code PLC999 --linter pylint - run: cargo check - run: | diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 9bdcf80fc585d..8baae43bcbd34 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-python@v4 - name: "Install Rust toolchain" run: rustup show - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - name: "Install dependencies" run: | pip install -r docs/requirements.txt @@ -26,7 +26,7 @@ jobs: mkdocs build --strict - name: "Deploy to Cloudflare Pages" if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }} - uses: cloudflare/wrangler-action@2.0.0 + uses: cloudflare/wrangler-action@2 with: apiToken: ${{ secrets.CF_API_TOKEN }} accountId: ${{ secrets.CF_ACCOUNT_ID }} diff --git a/.github/workflows/flake8-to-ruff.yaml b/.github/workflows/flake8-to-ruff.yaml index 7c45b904f0833..c207e1884ac48 100644 --- a/.github/workflows/flake8-to-ruff.yaml +++ b/.github/workflows/flake8-to-ruff.yaml @@ -133,7 +133,7 @@ jobs: target: ${{ matrix.target }} manylinux: auto args: --no-default-features --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml - - uses: uraimo/run-on-arch-action@v2.5.0 + - uses: uraimo/run-on-arch-action@v2 if: matrix.target != 'ppc64' name: Install built wheel with: @@ -206,7 +206,7 @@ jobs: target: ${{ matrix.platform.target }} manylinux: musllinux_1_2 args: --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml - - uses: uraimo/run-on-arch-action@master + - uses: uraimo/run-on-arch-action@v2 name: Install built wheel with: arch: ${{ matrix.platform.arch }} diff --git a/.github/workflows/playground.yaml b/.github/workflows/playground.yaml index 40060bcbb7185..6da8fc5ad2d19 100644 --- a/.github/workflows/playground.yaml +++ b/.github/workflows/playground.yaml @@ -40,7 +40,7 @@ jobs: working-directory: playground - name: "Deploy to Cloudflare Pages" if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }} - uses: cloudflare/wrangler-action@2.0.0 + uses: cloudflare/wrangler-action@2 with: apiToken: ${{ secrets.CF_API_TOKEN }} accountId: ${{ secrets.CF_ACCOUNT_ID }} diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index 80e17677657a9..4d31e24540d8c 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -208,7 +208,7 @@ jobs: target: ${{ matrix.platform.target }} manylinux: auto args: --release --out dist - - uses: uraimo/run-on-arch-action@v2.5.0 + - uses: uraimo/run-on-arch-action@v2 if: matrix.platform.arch != 'ppc64' name: Install built wheel with: @@ -309,7 +309,7 @@ jobs: target: ${{ matrix.platform.target }} manylinux: musllinux_1_2 args: --release --out dist - - uses: uraimo/run-on-arch-action@master + - uses: uraimo/run-on-arch-action@v2 name: Install built wheel with: arch: ${{ matrix.platform.arch }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cb649883eb0b5..eda576b5eb928 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ fail_fast: true repos: - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.10.1 + rev: v0.12.1 hooks: - id: validate-pyproject From 106a93eab073a3864e81bcefab1854e5cf7864df Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 14 Mar 2023 11:02:05 -0400 Subject: [PATCH 10/49] Make Clap an optional feature for ruff crate (#3498) --- Cargo.lock | 15 ++++++++++-- crates/ruff/Cargo.toml | 3 +-- crates/ruff/src/rule_selector.rs | 5 ++-- crates/ruff/src/settings/types.rs | 38 +++++++------------------------ crates/ruff_cli/Cargo.toml | 4 ++-- crates/ruff_cli/src/args.rs | 2 +- docs/configuration.md | 2 +- 7 files changed, 28 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 164cdfaca96e1..94078d0797bb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -313,13 +313,14 @@ dependencies = [ [[package]] name = "clap_complete_command" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4160b4a4f72ef58bd766bad27c09e6ef1cc9d82a22f6a0f55d152985a4a48e31" +checksum = "183495371ea78d4c9ff638bfc6497d46fed2396e4f9c50aebc1278a4a9919a3d" dependencies = [ "clap 4.1.8", "clap_complete", "clap_complete_fig", + "clap_complete_nushell", ] [[package]] @@ -332,6 +333,16 @@ dependencies = [ "clap_complete", ] +[[package]] +name = "clap_complete_nushell" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fa41f5e6aa83bd151b70fd0ceaee703d68cd669522795dc812df9edad1252c" +dependencies = [ + "clap 4.1.8", + "clap_complete", +] + [[package]] name = "clap_derive" version = "4.1.8" diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index bc67ee1b5bb55..3325a0d532e68 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -25,7 +25,7 @@ anyhow = { workspace = true } bisection = { version = "0.1.0" } bitflags = { workspace = true } chrono = { workspace = true } -clap = { workspace = true, features = ["derive", "env", "string"] } +clap = { workspace = true, features = ["derive", "string"], optional = true } colored = { workspace = true } dirs = { version = "4.0.0" } fern = { version = "0.6.1" } @@ -72,7 +72,6 @@ insta = { workspace = true, features = ["yaml", "redactions"] } pretty_assertions = "1.3.0" test-case = { workspace = true } - [features] default = [] logical_lines = [] diff --git a/crates/ruff/src/rule_selector.rs b/crates/ruff/src/rule_selector.rs index 9a01f03ed0803..55f34add8e6ea 100644 --- a/crates/ruff/src/rule_selector.rs +++ b/crates/ruff/src/rule_selector.rs @@ -277,6 +277,7 @@ pub(crate) enum Specificity { Code5Chars, } +#[cfg(feature = "clap")] mod clap_completion { use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory}; use strum::IntoEnumIterator; @@ -316,9 +317,7 @@ mod clap_completion { .map_err(|e| clap::Error::raw(clap::error::ErrorKind::InvalidValue, e)) } - fn possible_values( - &self, - ) -> Option + '_>> { + fn possible_values(&self) -> Option + '_>> { Some(Box::new( std::iter::once(PossibleValue::new("ALL").help("all rules")).chain( Linter::iter() diff --git a/crates/ruff/src/settings/types.rs b/crates/ruff/src/settings/types.rs index 18e0168d32e45..a487a2549643f 100644 --- a/crates/ruff/src/settings/types.rs +++ b/crates/ruff/src/settings/types.rs @@ -4,21 +4,21 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::string::ToString; -use anyhow::{anyhow, bail, Result}; -use clap::ValueEnum; +use anyhow::{bail, Result}; use globset::{Glob, GlobSet, GlobSetBuilder}; use pep440_rs::{Version as Pep440Version, VersionSpecifiers}; -use ruff_cache::{CacheKey, CacheKeyHasher}; -use ruff_macros::CacheKey; use rustc_hash::FxHashSet; use schemars::JsonSchema; use serde::{de, Deserialize, Deserializer, Serialize}; use strum::IntoEnumIterator; use strum_macros::EnumIter; +use ruff_cache::{CacheKey, CacheKeyHasher}; +use ruff_macros::CacheKey; + +use crate::fs; use crate::registry::Rule; use crate::rule_selector::RuleSelector; -use crate::{fs, warn_user_once}; #[derive( Clone, @@ -34,6 +34,7 @@ use crate::{fs, warn_user_once}; CacheKey, EnumIter, )] +#[cfg_attr(feature = "clap", derive(clap::ValueEnum))] #[serde(rename_all = "lowercase")] pub enum PythonVersion { Py37, @@ -43,28 +44,6 @@ pub enum PythonVersion { Py311, } -impl FromStr for PythonVersion { - type Err = anyhow::Error; - - fn from_str(string: &str) -> Result { - match string { - "py33" | "py34" | "py35" | "py36" => { - warn_user_once!( - "Specified a version below the minimum supported Python version. Defaulting \ - to Python 3.7." - ); - Ok(Self::Py37) - } - "py37" => Ok(Self::Py37), - "py38" => Ok(Self::Py38), - "py39" => Ok(Self::Py39), - "py310" => Ok(Self::Py310), - "py311" => Ok(Self::Py311), - _ => Err(anyhow!("Unknown version: {string} (try: \"py37\")")), - } - } -} - impl From for Pep440Version { fn from(version: PythonVersion) -> Self { let (major, minor) = version.as_tuple(); @@ -240,9 +219,8 @@ impl FromStr for PatternPrefixPair { } } -#[derive( - Clone, Copy, ValueEnum, PartialEq, Eq, Serialize, Deserialize, Debug, JsonSchema, Hash, -)] +#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug, JsonSchema, Hash)] +#[cfg_attr(feature = "clap", derive(clap::ValueEnum))] #[serde(rename_all = "kebab-case")] pub enum SerializationFormat { Text, diff --git a/crates/ruff_cli/Cargo.toml b/crates/ruff_cli/Cargo.toml index 53b82af2769b1..6571be0314cc1 100644 --- a/crates/ruff_cli/Cargo.toml +++ b/crates/ruff_cli/Cargo.toml @@ -22,7 +22,7 @@ name = "ruff" doc = false [dependencies] -ruff = { path = "../ruff" } +ruff = { path = "../ruff", features = ["clap"] } ruff_cache = { path = "../ruff_cache" } ruff_diagnostics = { path = "../ruff_diagnostics" } @@ -34,7 +34,7 @@ bitflags = { workspace = true } cachedir = { version = "0.3.0" } chrono = { workspace = true } clap = { workspace = true, features = ["derive", "env"] } -clap_complete_command = { version = "0.4.0" } +clap_complete_command = { version = "0.5.1" } clearscreen = { version = "2.0.0" } colored = { workspace = true } filetime = { workspace = true } diff --git a/crates/ruff_cli/src/args.rs b/crates/ruff_cli/src/args.rs index 335a39393725f..9103ccd245d46 100644 --- a/crates/ruff_cli/src/args.rs +++ b/crates/ruff_cli/src/args.rs @@ -99,7 +99,7 @@ pub struct CheckArgs { #[arg(long, value_enum, env = "RUFF_FORMAT")] pub format: Option, /// The minimum Python version that should be supported. - #[arg(long)] + #[arg(long, value_enum)] pub target_version: Option, /// Path to the `pyproject.toml` or `ruff.toml` file to use for /// configuration. diff --git a/docs/configuration.md b/docs/configuration.md index 19c562e79512a..5710074bb5c2d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -211,7 +211,7 @@ Options: --format Output serialization format for violations [env: RUFF_FORMAT=] [possible values: text, json, junit, grouped, github, gitlab, pylint, azure] --target-version - The minimum Python version that should be supported + The minimum Python version that should be supported [possible values: py37, py38, py39, py310, py311] --config Path to the `pyproject.toml` or `ruff.toml` file to use for configuration --statistics From 8c7317eb8d5c10f111763fa784361fa02f9f1f9c Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Tue, 14 Mar 2023 23:16:09 +0800 Subject: [PATCH 11/49] ci: fix missing short tag for cloudflare/wrangler-action (#3513) --- .github/workflows/docs.yaml | 2 +- .github/workflows/playground.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 8baae43bcbd34..8722d251007cc 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -26,7 +26,7 @@ jobs: mkdocs build --strict - name: "Deploy to Cloudflare Pages" if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }} - uses: cloudflare/wrangler-action@2 + uses: cloudflare/wrangler-action@2.0.0 with: apiToken: ${{ secrets.CF_API_TOKEN }} accountId: ${{ secrets.CF_ACCOUNT_ID }} diff --git a/.github/workflows/playground.yaml b/.github/workflows/playground.yaml index 6da8fc5ad2d19..40060bcbb7185 100644 --- a/.github/workflows/playground.yaml +++ b/.github/workflows/playground.yaml @@ -40,7 +40,7 @@ jobs: working-directory: playground - name: "Deploy to Cloudflare Pages" if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }} - uses: cloudflare/wrangler-action@2 + uses: cloudflare/wrangler-action@2.0.0 with: apiToken: ${{ secrets.CF_API_TOKEN }} accountId: ${{ secrets.CF_ACCOUNT_ID }} From 1eff3dffa5e07be2bf0fc125b8d766cbd49bebae Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 14 Mar 2023 11:22:14 -0400 Subject: [PATCH 12/49] Ensure that redirect warnings appear exactly once per code (#3500) --- crates/ruff/src/logging.rs | 23 +++++++++++++++++++++++ crates/ruff/src/settings/mod.rs | 33 ++++++++++++++++----------------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/crates/ruff/src/logging.rs b/crates/ruff/src/logging.rs index f6c6b37f8d3b5..9a797e3c2f720 100644 --- a/crates/ruff/src/logging.rs +++ b/crates/ruff/src/logging.rs @@ -1,8 +1,31 @@ +use std::sync::Mutex; + use anyhow::Result; use colored::Colorize; use fern; use log::Level; +use once_cell::sync::Lazy; + +pub(crate) static WARNINGS: Lazy>> = Lazy::new(Mutex::default); + +/// Warn a user once, with uniqueness determined by the given ID. +#[macro_export] +macro_rules! warn_user_once_by_id { + ($id:expr, $($arg:tt)*) => { + use colored::Colorize; + use log::warn; + + if let Ok(mut states) = $crate::logging::WARNINGS.lock() { + if !states.contains(&$id) { + let message = format!("{}", format_args!($($arg)*)); + warn!("{}", message.bold()); + states.push($id); + } + } + }; +} +/// Warn a user once, with uniqueness determined by the calling location itself. #[macro_export] macro_rules! warn_user_once { ($($arg:tt)*) => { diff --git a/crates/ruff/src/settings/mod.rs b/crates/ruff/src/settings/mod.rs index 6a19f8faa2247..5091d73b18b6a 100644 --- a/crates/ruff/src/settings/mod.rs +++ b/crates/ruff/src/settings/mod.rs @@ -7,11 +7,12 @@ use std::path::{Path, PathBuf}; use anyhow::{anyhow, Result}; use globset::{Glob, GlobMatcher}; use regex::Regex; -use ruff_cache::cache_dir; use rustc_hash::{FxHashMap, FxHashSet}; use strum::IntoEnumIterator; -use self::rule_table::RuleTable; +use ruff_cache::cache_dir; +use ruff_macros::CacheKey; + use crate::registry::{Rule, RuleNamespace, INCOMPATIBLE_CODES}; use crate::rule_selector::{RuleSelector, Specificity}; use crate::rules::{ @@ -22,8 +23,9 @@ use crate::rules::{ }; use crate::settings::configuration::Configuration; use crate::settings::types::{FilePatternSet, PerFileIgnore, PythonVersion, SerializationFormat}; -use crate::warn_user_once; -use ruff_macros::CacheKey; +use crate::warn_user_once_by_id; + +use self::rule_table::RuleTable; pub mod configuration; pub mod defaults; @@ -361,7 +363,8 @@ impl From<&Configuration> for RuleTable { for (from, target) in redirects { // TODO(martin): This belongs into the ruff_cli crate. - crate::warn_user!( + warn_user_once_by_id!( + from, "`{from}` has been remapped to `{}{}`.", target.linter().common_prefix(), target.short_code() @@ -389,16 +392,11 @@ impl From<&Configuration> for RuleTable { // Validate that we didn't enable any incompatible rules. Use this awkward // approach to give each pair it's own `warn_user_once`. - let [pair1, pair2] = INCOMPATIBLE_CODES; - let (preferred, expendable, message) = pair1; - if rules.enabled(preferred) && rules.enabled(expendable) { - warn_user_once!("{}", message); - rules.disable(expendable); - } - let (preferred, expendable, message) = pair2; - if rules.enabled(preferred) && rules.enabled(expendable) { - warn_user_once!("{}", message); - rules.disable(expendable); + for (preferred, expendable, message) in INCOMPATIBLE_CODES { + if rules.enabled(preferred) && rules.enabled(expendable) { + warn_user_once_by_id!(expendable.as_ref(), "{}", message); + rules.disable(expendable); + } } rules @@ -428,12 +426,13 @@ pub fn resolve_per_file_ignores( mod tests { use rustc_hash::FxHashSet; - use super::configuration::RuleSelection; use crate::codes::{self, Pycodestyle}; use crate::registry::Rule; use crate::settings::configuration::Configuration; use crate::settings::rule_table::RuleTable; + use super::configuration::RuleSelection; + #[allow(clippy::needless_pass_by_value)] fn resolve_rules(selections: impl IntoIterator) -> FxHashSet { RuleTable::from(&Configuration { @@ -471,7 +470,7 @@ mod tests { let actual = resolve_rules([RuleSelection { select: Some(vec![Pycodestyle::W.into()]), - ignore: vec![codes::Pycodestyle::W292.into()], + ignore: vec![Pycodestyle::W292.into()], ..RuleSelection::default() }]); let expected = FxHashSet::from_iter([ From 1b738f88c4d0074edc94489392112ad4f7bb44fb Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 14 Mar 2023 13:46:34 -0400 Subject: [PATCH 13/49] Allow f-strings and concatenations in os.getenv (#3516) --- .../fixtures/pylint/invalid_envvar_default.py | 4 ++ .../fixtures/pylint/invalid_envvar_value.py | 3 + .../pylint/rules/invalid_envvar_default.rs | 13 +++- .../pylint/rules/invalid_envvar_value.rs | 61 +++++++++++++------ ...ests__PLE1507_invalid_envvar_value.py.snap | 13 ++++ ...ts__PLW1508_invalid_envvar_default.py.snap | 13 ++++ 6 files changed, 86 insertions(+), 21 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/pylint/invalid_envvar_default.py b/crates/ruff/resources/test/fixtures/pylint/invalid_envvar_default.py index c557b7683781a..739e029976ab1 100644 --- a/crates/ruff/resources/test/fixtures/pylint/invalid_envvar_default.py +++ b/crates/ruff/resources/test/fixtures/pylint/invalid_envvar_default.py @@ -5,4 +5,8 @@ dictVarBad = os.getenv("AAA", {"a", 7}) # [invalid-envvar-default] print(os.getenv("TEST", False)) # [invalid-envvar-default] os.getenv("AA", "GOOD") +os.getenv("AA", f"GOOD") +os.getenv("AA", "GOOD" + "BAD") +os.getenv("AA", "GOOD" + 1) os.getenv("B", Z) + diff --git a/crates/ruff/resources/test/fixtures/pylint/invalid_envvar_value.py b/crates/ruff/resources/test/fixtures/pylint/invalid_envvar_value.py index ac5208513072a..23574d7b773c6 100644 --- a/crates/ruff/resources/test/fixtures/pylint/invalid_envvar_value.py +++ b/crates/ruff/resources/test/fixtures/pylint/invalid_envvar_value.py @@ -7,6 +7,9 @@ os.getenv(key=11) # [invalid-envvar-value] os.getenv(["hello"]) # [invalid-envvar-value] os.getenv(key="foo", default="bar") +os.getenv(key=f"foo", default="bar") +os.getenv(key="foo" + "bar", default=1) +os.getenv(key=1 + "bar", default=1) # [invalid-envvar-value] AA = "aa" os.getenv(AA) diff --git a/crates/ruff/src/rules/pylint/rules/invalid_envvar_default.rs b/crates/ruff/src/rules/pylint/rules/invalid_envvar_default.rs index 069a49ff242cf..d10b87fcb6c19 100644 --- a/crates/ruff/src/rules/pylint/rules/invalid_envvar_default.rs +++ b/crates/ruff/src/rules/pylint/rules/invalid_envvar_default.rs @@ -1,4 +1,4 @@ -use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword}; +use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword, Operator}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; @@ -49,13 +49,22 @@ fn is_valid_default(expr: &Expr) -> bool { return true; } + if let ExprKind::BinOp { + left, + right, + op: Operator::Add, + } = &expr.node + { + return is_valid_default(left) && is_valid_default(right); + } + // Otherwise, the default must be a string or `None`. matches!( expr.node, ExprKind::Constant { value: Constant::Str { .. } | Constant::None { .. }, .. - } + } | ExprKind::JoinedStr { .. } ) } diff --git a/crates/ruff/src/rules/pylint/rules/invalid_envvar_value.rs b/crates/ruff/src/rules/pylint/rules/invalid_envvar_value.rs index 562ffa198733e..2b7ad2288aff0 100644 --- a/crates/ruff/src/rules/pylint/rules/invalid_envvar_value.rs +++ b/crates/ruff/src/rules/pylint/rules/invalid_envvar_value.rs @@ -1,4 +1,4 @@ -use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword}; +use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword, Operator}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; @@ -34,6 +34,37 @@ impl Violation for InvalidEnvvarValue { } } +fn is_valid_key(expr: &Expr) -> bool { + // We can't infer the types of these defaults, so assume they're valid. + if matches!( + expr.node, + ExprKind::Name { .. } + | ExprKind::Attribute { .. } + | ExprKind::Subscript { .. } + | ExprKind::Call { .. } + ) { + return true; + } + + if let ExprKind::BinOp { + left, + right, + op: Operator::Add, + } = &expr.node + { + return is_valid_key(left) && is_valid_key(right); + } + + // Otherwise, the default must be a string. + matches!( + expr.node, + ExprKind::Constant { + value: Constant::Str { .. }, + .. + } | ExprKind::JoinedStr { .. } + ) +} + /// PLE1507 pub fn invalid_envvar_value( checker: &mut Checker, @@ -46,28 +77,20 @@ pub fn invalid_envvar_value( .resolve_call_path(func) .map_or(false, |call_path| call_path.as_slice() == ["os", "getenv"]) { - // Get the first argument for `getenv` - if let Some(expr) = args.get(0).or_else(|| { + // Find the `key` argument, if it exists. + let Some(expr) = args.get(0).or_else(|| { keywords .iter() .find(|keyword| keyword.node.arg.as_ref().map_or(false, |arg| arg == "key")) .map(|keyword| &keyword.node.value) - }) { - // Ignoring types that are inferred, only do direct constants - if !matches!( - expr.node, - ExprKind::Constant { - value: Constant::Str { .. }, - .. - } | ExprKind::Name { .. } - | ExprKind::Attribute { .. } - | ExprKind::Subscript { .. } - | ExprKind::Call { .. } - ) { - checker - .diagnostics - .push(Diagnostic::new(InvalidEnvvarValue, Range::from(expr))); - } + }) else { + return; + }; + + if !is_valid_key(expr) { + checker + .diagnostics + .push(Diagnostic::new(InvalidEnvvarValue, Range::from(expr))); } } } diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE1507_invalid_envvar_value.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE1507_invalid_envvar_value.py.snap index 1e3ad3ece73eb..632fa3450696b 100644 --- a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE1507_invalid_envvar_value.py.snap +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE1507_invalid_envvar_value.py.snap @@ -41,4 +41,17 @@ expression: diagnostics column: 19 fix: ~ parent: ~ +- kind: + name: InvalidEnvvarValue + body: "Invalid type for initial `os.getenv` argument; expected `str`" + suggestion: ~ + fixable: false + location: + row: 12 + column: 14 + end_location: + row: 12 + column: 23 + fix: ~ + parent: ~ diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW1508_invalid_envvar_default.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW1508_invalid_envvar_default.py.snap index ea6f86774e620..2561e3db083b7 100644 --- a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW1508_invalid_envvar_default.py.snap +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW1508_invalid_envvar_default.py.snap @@ -41,4 +41,17 @@ expression: diagnostics column: 29 fix: ~ parent: ~ +- kind: + name: InvalidEnvvarDefault + body: "Invalid type for environment variable default; expected `str` or `None`" + suggestion: ~ + fixable: false + location: + row: 10 + column: 16 + end_location: + row: 10 + column: 26 + fix: ~ + parent: ~ From c50d6da8b4ad5ad4eaee1b0b60f9ac062c057bc7 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 14 Mar 2023 14:27:21 -0400 Subject: [PATCH 14/49] Allow string percent formatting in os.getenv (#3518) --- .../test/fixtures/pylint/invalid_envvar_default.py | 2 +- .../src/rules/pylint/rules/invalid_envvar_default.rs | 11 +++++++++++ .../src/rules/pylint/rules/invalid_envvar_value.rs | 11 +++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/crates/ruff/resources/test/fixtures/pylint/invalid_envvar_default.py b/crates/ruff/resources/test/fixtures/pylint/invalid_envvar_default.py index 739e029976ab1..c07de79871fda 100644 --- a/crates/ruff/resources/test/fixtures/pylint/invalid_envvar_default.py +++ b/crates/ruff/resources/test/fixtures/pylint/invalid_envvar_default.py @@ -8,5 +8,5 @@ os.getenv("AA", f"GOOD") os.getenv("AA", "GOOD" + "BAD") os.getenv("AA", "GOOD" + 1) +os.getenv("AA", "GOOD %s" % "BAD") os.getenv("B", Z) - diff --git a/crates/ruff/src/rules/pylint/rules/invalid_envvar_default.rs b/crates/ruff/src/rules/pylint/rules/invalid_envvar_default.rs index d10b87fcb6c19..22d4aa137938a 100644 --- a/crates/ruff/src/rules/pylint/rules/invalid_envvar_default.rs +++ b/crates/ruff/src/rules/pylint/rules/invalid_envvar_default.rs @@ -49,6 +49,7 @@ fn is_valid_default(expr: &Expr) -> bool { return true; } + // Allow string concatenation. if let ExprKind::BinOp { left, right, @@ -58,6 +59,16 @@ fn is_valid_default(expr: &Expr) -> bool { return is_valid_default(left) && is_valid_default(right); } + // Allow string formatting. + if let ExprKind::BinOp { + left, + op: Operator::Mod, + .. + } = &expr.node + { + return is_valid_default(left); + } + // Otherwise, the default must be a string or `None`. matches!( expr.node, diff --git a/crates/ruff/src/rules/pylint/rules/invalid_envvar_value.rs b/crates/ruff/src/rules/pylint/rules/invalid_envvar_value.rs index 2b7ad2288aff0..6c8bc3b084a46 100644 --- a/crates/ruff/src/rules/pylint/rules/invalid_envvar_value.rs +++ b/crates/ruff/src/rules/pylint/rules/invalid_envvar_value.rs @@ -46,6 +46,7 @@ fn is_valid_key(expr: &Expr) -> bool { return true; } + // Allow string concatenation. if let ExprKind::BinOp { left, right, @@ -55,6 +56,16 @@ fn is_valid_key(expr: &Expr) -> bool { return is_valid_key(left) && is_valid_key(right); } + // Allow string formatting. + if let ExprKind::BinOp { + left, + op: Operator::Mod, + .. + } = &expr.node + { + return is_valid_key(left); + } + // Otherwise, the default must be a string. matches!( expr.node, From 432059de35e3fc7f20140686b7df6ee770acc0f2 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 14 Mar 2023 14:34:28 -0400 Subject: [PATCH 15/49] Allow `# ruff:` prefix for isort action comments (#3493) --- .../isort/{skip_file.py => isort_skip_file.py} | 0 .../test/fixtures/isort/ruff_skip_file.py | 10 ++++++++++ .../ruff/resources/test/fixtures/isort/skip.py | 8 ++++++++ crates/ruff/src/directives.rs | 14 ++++++++++---- crates/ruff/src/rules/isort/mod.rs | 3 ++- ..._rules__isort__tests__isort_skip_file.py.snap | 6 ++++++ ...__rules__isort__tests__ruff_skip_file.py.snap | 6 ++++++ .../ruff__rules__isort__tests__skip.py.snap | 16 ++++++++-------- .../ruff__rules__isort__tests__skip_file.py.snap | 6 ------ docs/configuration.md | 9 +++++++-- 10 files changed, 57 insertions(+), 21 deletions(-) rename crates/ruff/resources/test/fixtures/isort/{skip_file.py => isort_skip_file.py} (100%) create mode 100644 crates/ruff/resources/test/fixtures/isort/ruff_skip_file.py create mode 100644 crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__isort_skip_file.py.snap create mode 100644 crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__ruff_skip_file.py.snap delete mode 100644 crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__skip_file.py.snap diff --git a/crates/ruff/resources/test/fixtures/isort/skip_file.py b/crates/ruff/resources/test/fixtures/isort/isort_skip_file.py similarity index 100% rename from crates/ruff/resources/test/fixtures/isort/skip_file.py rename to crates/ruff/resources/test/fixtures/isort/isort_skip_file.py diff --git a/crates/ruff/resources/test/fixtures/isort/ruff_skip_file.py b/crates/ruff/resources/test/fixtures/isort/ruff_skip_file.py new file mode 100644 index 0000000000000..dd310782ad187 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/isort/ruff_skip_file.py @@ -0,0 +1,10 @@ +# ruff: isort: skip_file +import e +import f + +# isort: split + +import a +import b +import c +import d diff --git a/crates/ruff/resources/test/fixtures/isort/skip.py b/crates/ruff/resources/test/fixtures/isort/skip.py index dbc836c64317a..5cf668879bc99 100644 --- a/crates/ruff/resources/test/fixtures/isort/skip.py +++ b/crates/ruff/resources/test/fixtures/isort/skip.py @@ -6,6 +6,14 @@ def f(): # isort: on +def f(): + # ruff: isort: off + import sys + import os + import collections + # ruff: isort: on + + def f(): import sys import os # isort: skip diff --git a/crates/ruff/src/directives.rs b/crates/ruff/src/directives.rs index ad3ab6ec1101b..b773ef227f4a6 100644 --- a/crates/ruff/src/directives.rs +++ b/crates/ruff/src/directives.rs @@ -107,15 +107,21 @@ pub fn extract_isort_directives(lxr: &[LexResult]) -> IsortDirectives { // omit a space after the colon. The remaining action comments are // required to include the space, and must appear on their own lines. let comment_text = comment_text.trim_end(); - if comment_text == "# isort: split" { + if matches!(comment_text, "# isort: split" | "# ruff: isort: split") { splits.push(start.row()); - } else if comment_text == "# isort: skip_file" || comment_text == "# isort:skip_file" { + } else if matches!( + comment_text, + "# isort: skip_file" + | "# isort:skip_file" + | "# ruff: isort: skip_file" + | "# ruff: isort:skip_file" + ) { return IsortDirectives { skip_file: true, ..IsortDirectives::default() }; } else if off.is_some() { - if comment_text == "# isort: on" { + if comment_text == "# isort: on" || comment_text == "# ruff: isort: on" { if let Some(start) = off { for row in start.row() + 1..=end.row() { exclusions.insert(row); @@ -126,7 +132,7 @@ pub fn extract_isort_directives(lxr: &[LexResult]) -> IsortDirectives { } else { if comment_text.contains("isort: skip") || comment_text.contains("isort:skip") { exclusions.insert(start.row()); - } else if comment_text == "# isort: off" { + } else if comment_text == "# isort: off" || comment_text == "# ruff: isort: off" { off = Some(start); } } diff --git a/crates/ruff/src/rules/isort/mod.rs b/crates/ruff/src/rules/isort/mod.rs index d34cc0520acd8..bbbc0f7a6e629 100644 --- a/crates/ruff/src/rules/isort/mod.rs +++ b/crates/ruff/src/rules/isort/mod.rs @@ -373,6 +373,7 @@ mod tests { #[test_case(Path::new("inline_comments.py"))] #[test_case(Path::new("insert_empty_lines.py"))] #[test_case(Path::new("insert_empty_lines.pyi"))] + #[test_case(Path::new("isort_skip_file.py"))] #[test_case(Path::new("leading_prefix.py"))] #[test_case(Path::new("magic_trailing_comma.py"))] #[test_case(Path::new("natural_order.py"))] @@ -391,12 +392,12 @@ mod tests { #[test_case(Path::new("preserve_tabs_2.py"))] #[test_case(Path::new("relative_imports_order.py"))] #[test_case(Path::new("reorder_within_section.py"))] + #[test_case(Path::new("ruff_skip_file.py"))] #[test_case(Path::new("separate_first_party_imports.py"))] #[test_case(Path::new("separate_future_imports.py"))] #[test_case(Path::new("separate_local_folder_imports.py"))] #[test_case(Path::new("separate_third_party_imports.py"))] #[test_case(Path::new("skip.py"))] - #[test_case(Path::new("skip_file.py"))] #[test_case(Path::new("sort_similar_imports.py"))] #[test_case(Path::new("split.py"))] #[test_case(Path::new("star_before_others.py"))] diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__isort_skip_file.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__isort_skip_file.py.snap new file mode 100644 index 0000000000000..caa019cc6cd90 --- /dev/null +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__isort_skip_file.py.snap @@ -0,0 +1,6 @@ +--- +source: crates/ruff/src/rules/isort/mod.rs +expression: diagnostics +--- +[] + diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__ruff_skip_file.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__ruff_skip_file.py.snap new file mode 100644 index 0000000000000..caa019cc6cd90 --- /dev/null +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__ruff_skip_file.py.snap @@ -0,0 +1,6 @@ +--- +source: crates/ruff/src/rules/isort/mod.rs +expression: diagnostics +--- +[] + diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__skip.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__skip.py.snap index 6660926e5383d..98ba47763bc6f 100644 --- a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__skip.py.snap +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__skip.py.snap @@ -8,18 +8,18 @@ expression: diagnostics suggestion: Organize imports fixable: true location: - row: 12 + row: 20 column: 0 end_location: - row: 14 + row: 22 column: 0 fix: content: " import abc\n import collections\n" location: - row: 12 + row: 20 column: 0 end_location: - row: 14 + row: 22 column: 0 parent: ~ - kind: @@ -28,18 +28,18 @@ expression: diagnostics suggestion: Organize imports fixable: true location: - row: 19 + row: 27 column: 0 end_location: - row: 21 + row: 29 column: 0 fix: content: " import abc\n import collections\n" location: - row: 19 + row: 27 column: 0 end_location: - row: 21 + row: 29 column: 0 parent: ~ diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__skip_file.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__skip_file.py.snap deleted file mode 100644 index 40d8799b1084f..0000000000000 --- a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__skip_file.py.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/rules/isort/mod.rs -expression: diagnostics ---- -[] - diff --git a/docs/configuration.md b/docs/configuration.md index 5710074bb5c2d..d855e443935a9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -418,12 +418,17 @@ automatically add `noqa` directives to all failing lines, with the appropriate r ### Action comments -Ruff respects `isort`'s [action comments](https://pycqa.github.io/isort/docs/configuration/action_comments.html) +Ruff respects isort's [action comments](https://pycqa.github.io/isort/docs/configuration/action_comments.html) (`# isort: skip_file`, `# isort: on`, `# isort: off`, `# isort: skip`, and `# isort: split`), which enable selectively enabling and disabling import sorting for blocks of code and other inline configuration. -See the [`isort` documentation](https://pycqa.github.io/isort/docs/configuration/action_comments.html) +Ruff will also respect variants of these action comments with a `# ruff:` prefix +(e.g., `# ruff: isort: skip_file`, `# ruff: isort: on`, and so on). These variants more clearly +convey that the action comment is intended for Ruff, but are functionally equivalent to the +isort variants. + +See the [isort documentation](https://pycqa.github.io/isort/docs/configuration/action_comments.html) for more. ## Exit codes From 344daebb1b136a9cf059518c09ef88e7940e2d28 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 14 Mar 2023 14:40:33 -0400 Subject: [PATCH 16/49] Refine complexity rules for try-except-else-finally (#3519) --- crates/ruff/src/rules/mccabe/rules.rs | 42 +++++++++++++++---- ...ules__mccabe__tests__max_complexity_0.snap | 2 +- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/crates/ruff/src/rules/mccabe/rules.rs b/crates/ruff/src/rules/mccabe/rules.rs index 79c7500e2afe3..71febbc60171a 100644 --- a/crates/ruff/src/rules/mccabe/rules.rs +++ b/crates/ruff/src/rules/mccabe/rules.rs @@ -1,4 +1,4 @@ -use rustpython_parser::ast::{ExcepthandlerKind, ExprKind, Stmt, StmtKind}; +use rustpython_parser::ast::{ExcepthandlerKind, Stmt, StmtKind}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; @@ -74,13 +74,10 @@ fn get_complexity_number(stmts: &[Stmt]) -> usize { complexity += get_complexity_number(body); complexity += get_complexity_number(orelse); } - StmtKind::While { test, body, orelse } => { + StmtKind::While { body, orelse, .. } => { complexity += 1; complexity += get_complexity_number(body); complexity += get_complexity_number(orelse); - if let ExprKind::BoolOp { .. } = &test.node { - complexity += 1; - } } StmtKind::Match { cases, .. } => { complexity += 1; @@ -100,8 +97,10 @@ fn get_complexity_number(stmts: &[Stmt]) -> usize { orelse, finalbody, } => { - complexity += 1; complexity += get_complexity_number(body); + if !orelse.is_empty() { + complexity += 1; + } complexity += get_complexity_number(orelse); complexity += get_complexity_number(finalbody); for handler in handlers { @@ -307,7 +306,7 @@ def nested_try_finally(): print(3) "#; let stmts = parser::parse_program(source, "")?; - assert_eq!(get_complexity_number(&stmts), 3); + assert_eq!(get_complexity_number(&stmts), 1); Ok(()) } @@ -374,4 +373,33 @@ class Class: assert_eq!(get_complexity_number(&stmts), 9); Ok(()) } + + #[test] + fn finally() -> Result<()> { + let source = r#" +def process_detect_lines(): + try: + pass + finally: + pass +"#; + let stmts = parser::parse_program(source, "")?; + assert_eq!(get_complexity_number(&stmts), 1); + Ok(()) + } + + #[test] + fn if_in_finally() -> Result<()> { + let source = r#" +def process_detect_lines(): + try: + pass + finally: + if res: + errors.append(f"Non-zero exit code {res}") +"#; + let stmts = parser::parse_program(source, "")?; + assert_eq!(get_complexity_number(&stmts), 2); + Ok(()) + } } diff --git a/crates/ruff/src/rules/mccabe/snapshots/ruff__rules__mccabe__tests__max_complexity_0.snap b/crates/ruff/src/rules/mccabe/snapshots/ruff__rules__mccabe__tests__max_complexity_0.snap index 1a7c576b7151a..632935557df75 100644 --- a/crates/ruff/src/rules/mccabe/snapshots/ruff__rules__mccabe__tests__max_complexity_0.snap +++ b/crates/ruff/src/rules/mccabe/snapshots/ruff__rules__mccabe__tests__max_complexity_0.snap @@ -160,7 +160,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`nested_try_finally` is too complex (3)" + body: "`nested_try_finally` is too complex (1)" suggestion: ~ fixable: false location: From 7e904111b1901db43150fad09ab43c92f31a4d26 Mon Sep 17 00:00:00 2001 From: Jonathan Plasse <13716151+JonathanPlasse@users.noreply.github.com> Date: Tue, 14 Mar 2023 19:43:09 +0100 Subject: [PATCH 17/49] Fix PYI011 and add auto-fix (#3492) --- .../test/fixtures/flake8_pyi/PYI011.pyi | 13 + .../rules/flake8_pyi/rules/simple_defaults.rs | 43 ++- ..._flake8_pyi__tests__PYI011_PYI011.pyi.snap | 321 +++++++++++------- 3 files changed, 242 insertions(+), 135 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI011.pyi b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI011.pyi index 8678a0958ae43..1a3c7b3c618b5 100644 --- a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI011.pyi +++ b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI011.pyi @@ -1,3 +1,10 @@ +import math +import os +import sys +from math import inf + +import numpy as np + def f12( x, y: str = os.pathsep, # Error PYI011 Only simple default values allowed for typed arguments @@ -101,3 +108,9 @@ def f35( x: complex = math.inf # Error PYI011 Only simple default values allowed for typed arguments + 1j, ) -> None: ... +def f36( + *, x: str = sys.version, # OK +) -> None: ... +def f37( + *, x: str = "" + "", # Error PYI011 Only simple default values allowed for typed arguments +) -> None: ... diff --git a/crates/ruff/src/rules/flake8_pyi/rules/simple_defaults.rs b/crates/ruff/src/rules/flake8_pyi/rules/simple_defaults.rs index 8bafc22bdbadd..ba4ef5b0625f2 100644 --- a/crates/ruff/src/rules/flake8_pyi/rules/simple_defaults.rs +++ b/crates/ruff/src/rules/flake8_pyi/rules/simple_defaults.rs @@ -1,20 +1,25 @@ use rustpython_parser::ast::{Arguments, Constant, Expr, ExprKind, Operator, Unaryop}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::types::Range; use crate::checkers::ast::Checker; +use crate::registry::AsRule; #[violation] pub struct TypedArgumentSimpleDefaults; /// PYI011 -impl Violation for TypedArgumentSimpleDefaults { +impl AlwaysAutofixableViolation for TypedArgumentSimpleDefaults { #[derive_message_formats] fn message(&self) -> String { format!("Only simple default values allowed for typed arguments") } + + fn autofix_title(&self) -> String { + "Replace default value by `...`".to_string() + } } #[violation] @@ -111,7 +116,7 @@ fn is_valid_default_value_with_annotation(default: &Expr, checker: &Checker) -> if let ExprKind::Attribute { .. } = &operand.node { if checker .ctx - .resolve_call_path(default) + .resolve_call_path(operand) .map_or(false, |call_path| { ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS.iter().any(|target| { // reject `-math.nan` @@ -188,10 +193,18 @@ pub fn typed_argument_simple_defaults(checker: &mut Checker, args: &Arguments) { { if arg.node.annotation.is_some() { if !is_valid_default_value_with_annotation(default, checker) { - checker.diagnostics.push(Diagnostic::new( - TypedArgumentSimpleDefaults, - Range::from(default), - )); + let mut diagnostic = + Diagnostic::new(TypedArgumentSimpleDefaults, Range::from(default)); + + if checker.patch(diagnostic.kind.rule()) { + diagnostic.amend(Fix::replacement( + "...".to_string(), + default.location, + default.end_location.unwrap(), + )); + } + + checker.diagnostics.push(diagnostic); } } } @@ -207,10 +220,18 @@ pub fn typed_argument_simple_defaults(checker: &mut Checker, args: &Arguments) { { if kwarg.node.annotation.is_some() { if !is_valid_default_value_with_annotation(default, checker) { - checker.diagnostics.push(Diagnostic::new( - TypedArgumentSimpleDefaults, - Range::from(default), - )); + let mut diagnostic = + Diagnostic::new(TypedArgumentSimpleDefaults, Range::from(default)); + + if checker.patch(diagnostic.kind.rule()) { + diagnostic.amend(Fix::replacement( + "...".to_string(), + default.location, + default.end_location.unwrap(), + )); + } + + checker.diagnostics.push(diagnostic); } } } diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI011_PYI011.pyi.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI011_PYI011.pyi.snap index 484cf00790753..81f1c12f5a221 100644 --- a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI011_PYI011.pyi.snap +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI011_PYI011.pyi.snap @@ -5,248 +5,321 @@ expression: diagnostics - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 3 + row: 10 column: 13 end_location: - row: 3 + row: 10 column: 23 - fix: ~ + fix: + content: "..." + location: + row: 10 + column: 13 + end_location: + row: 10 + column: 23 parent: ~ - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 9 + row: 16 column: 8 end_location: - row: 13 + row: 20 column: 5 - fix: ~ + fix: + content: "..." + location: + row: 16 + column: 8 + end_location: + row: 20 + column: 5 parent: ~ - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 18 + row: 25 column: 8 end_location: - row: 22 + row: 29 column: 5 - fix: ~ + fix: + content: "..." + location: + row: 25 + column: 8 + end_location: + row: 29 + column: 5 parent: ~ - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 27 + row: 34 column: 8 end_location: - row: 31 + row: 38 column: 5 - fix: ~ + fix: + content: "..." + location: + row: 34 + column: 8 + end_location: + row: 38 + column: 5 parent: ~ - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 36 + row: 43 column: 8 end_location: - row: 38 + row: 45 column: 5 - fix: ~ + fix: + content: "..." + location: + row: 43 + column: 8 + end_location: + row: 45 + column: 5 parent: ~ - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 41 + row: 48 column: 13 end_location: - row: 42 + row: 49 column: 11 - fix: ~ + fix: + content: "..." + location: + row: 48 + column: 13 + end_location: + row: 49 + column: 11 parent: ~ - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 45 + row: 52 column: 13 end_location: - row: 46 + row: 53 column: 12 - fix: ~ + fix: + content: "..." + location: + row: 52 + column: 13 + end_location: + row: 53 + column: 12 parent: ~ - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 49 + row: 56 column: 16 end_location: - row: 50 + row: 57 column: 7 - fix: ~ + fix: + content: "..." + location: + row: 56 + column: 16 + end_location: + row: 57 + column: 7 parent: ~ - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 53 + row: 60 column: 13 end_location: - row: 54 + row: 61 column: 7 - fix: ~ + fix: + content: "..." + location: + row: 60 + column: 13 + end_location: + row: 61 + column: 7 parent: ~ - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 57 + row: 64 column: 17 end_location: - row: 58 + row: 65 column: 8 - fix: ~ + fix: + content: "..." + location: + row: 64 + column: 17 + end_location: + row: 65 + column: 8 parent: ~ - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 61 + row: 68 column: 17 end_location: - row: 62 + row: 69 column: 10 - fix: ~ - parent: ~ -- kind: - name: TypedArgumentSimpleDefaults - body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false - location: - row: 80 - column: 15 - end_location: - row: 80 - column: 23 - fix: ~ - parent: ~ -- kind: - name: TypedArgumentSimpleDefaults - body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false - location: - row: 83 - column: 15 - end_location: - row: 83 - column: 23 - fix: ~ + fix: + content: "..." + location: + row: 68 + column: 17 + end_location: + row: 69 + column: 10 parent: ~ - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 86 + row: 96 column: 15 end_location: - row: 86 - column: 24 - fix: ~ - parent: ~ -- kind: - name: TypedArgumentSimpleDefaults - body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false - location: - row: 89 - column: 15 - end_location: - row: 89 + row: 96 column: 18 - fix: ~ + fix: + content: "..." + location: + row: 96 + column: 15 + end_location: + row: 96 + column: 18 parent: ~ - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 92 + row: 99 column: 15 end_location: - row: 92 + row: 99 column: 21 - fix: ~ + fix: + content: "..." + location: + row: 99 + column: 15 + end_location: + row: 99 + column: 21 parent: ~ - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 95 + row: 105 column: 15 end_location: - row: 95 - column: 23 - fix: ~ + row: 105 + column: 24 + fix: + content: "..." + location: + row: 105 + column: 15 + end_location: + row: 105 + column: 24 parent: ~ - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 98 - column: 15 + row: 108 + column: 17 end_location: - row: 98 - column: 24 - fix: ~ + row: 109 + column: 8 + fix: + content: "..." + location: + row: 108 + column: 17 + end_location: + row: 109 + column: 8 parent: ~ - kind: name: TypedArgumentSimpleDefaults body: Only simple default values allowed for typed arguments - suggestion: ~ - fixable: false + suggestion: "Replace default value by `...`" + fixable: true location: - row: 101 - column: 17 + row: 115 + column: 16 end_location: - row: 102 - column: 8 - fix: ~ + row: 115 + column: 23 + fix: + content: "..." + location: + row: 115 + column: 16 + end_location: + row: 115 + column: 23 parent: ~ From a36139ae218beb25a6a035fb89777b0e99d067b8 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 14 Mar 2023 14:48:12 -0400 Subject: [PATCH 18/49] Replicate inline comments when splitting single-line imports (#3521) --- .../resources/test/fixtures/isort/force_single_line.py | 9 +++++---- crates/ruff/src/rules/isort/mod.rs | 2 +- ...t__tests__force_single_line_force_single_line.py.snap | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/isort/force_single_line.py b/crates/ruff/resources/test/fixtures/isort/force_single_line.py index 3aadd3e43613f..b9bcf502d0f93 100644 --- a/crates/ruff/resources/test/fixtures/isort/force_single_line.py +++ b/crates/ruff/resources/test/fixtures/isort/force_single_line.py @@ -10,9 +10,10 @@ from foo import bar # comment 3 from foo2 import bar2 # comment 4 +from foo3 import bar3, baz3 # comment 5 -# comment 5 +# comment 6 from bar import ( - a, # comment 6 - b, # comment 7 -) \ No newline at end of file + a, # comment 7 + b, # comment 8 +) diff --git a/crates/ruff/src/rules/isort/mod.rs b/crates/ruff/src/rules/isort/mod.rs index bbbc0f7a6e629..5df3c20a1a648 100644 --- a/crates/ruff/src/rules/isort/mod.rs +++ b/crates/ruff/src/rules/isort/mod.rs @@ -94,7 +94,7 @@ fn force_single_line_imports<'a>( } else { CommentSet { atop: vec![], - inline: vec![], + inline: comment_set.inline.clone(), } }, TrailingComma::Absent, diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__force_single_line_force_single_line.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__force_single_line_force_single_line.py.snap index c2b87e78af6ad..276ca31a43dac 100644 --- a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__force_single_line_force_single_line.py.snap +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__force_single_line_force_single_line.py.snap @@ -11,15 +11,15 @@ expression: diagnostics row: 1 column: 0 end_location: - row: 19 + row: 20 column: 0 fix: - content: "import math\nimport sys\nfrom logging.handlers import FileHandler, StreamHandler\nfrom os import path, uname\n\n# comment 5\nfrom bar import a # comment 6\nfrom bar import b # comment 7\nfrom foo import bar # comment 3\nfrom foo2 import bar2 # comment 4\n\n# comment 1\n# comment 2\nfrom third_party import lib1\nfrom third_party import lib2\nfrom third_party import lib3\nfrom third_party import lib4\nfrom third_party import lib5\nfrom third_party import lib6\nfrom third_party import lib7\n" + content: "import math\nimport sys\nfrom logging.handlers import FileHandler, StreamHandler\nfrom os import path, uname\n\n# comment 6\nfrom bar import a # comment 7\nfrom bar import b # comment 8\nfrom foo import bar # comment 3\nfrom foo2 import bar2 # comment 4\nfrom foo3 import bar3 # comment 5\nfrom foo3 import baz3 # comment 5\n\n# comment 1\n# comment 2\nfrom third_party import lib1\nfrom third_party import lib2\nfrom third_party import lib3\nfrom third_party import lib4\nfrom third_party import lib5\nfrom third_party import lib6\nfrom third_party import lib7\n" location: row: 1 column: 0 end_location: - row: 19 + row: 20 column: 0 parent: ~ From 58353a4bf4789dd3bef200e94c38f948ecae8249 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 14 Mar 2023 18:02:15 -0400 Subject: [PATCH 19/49] Avoid PEP 604 panic with empty tuple (#3526) --- crates/ruff/resources/test/fixtures/pyupgrade/UP038.py | 1 + .../ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/crates/ruff/resources/test/fixtures/pyupgrade/UP038.py b/crates/ruff/resources/test/fixtures/pyupgrade/UP038.py index 5bfbf30b1707d..3b41c58831da9 100644 --- a/crates/ruff/resources/test/fixtures/pyupgrade/UP038.py +++ b/crates/ruff/resources/test/fixtures/pyupgrade/UP038.py @@ -5,3 +5,4 @@ issubclass("yes", int) # OK isinstance(1, int | float) # OK issubclass("yes", int | str) # OK +isinstance(1, ()) # OK diff --git a/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs b/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs index af2c2ec3bff12..15242a9af85ca 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs @@ -68,6 +68,7 @@ fn union(elts: &[Expr]) -> Expr { } } +/// UP038 pub fn use_pep604_isinstance(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) { if let ExprKind::Name { id, .. } = &func.node { let Some(kind) = CallKind::from_name(id) else { @@ -78,6 +79,9 @@ pub fn use_pep604_isinstance(checker: &mut Checker, expr: &Expr, func: &Expr, ar }; if let Some(types) = args.get(1) { if let ExprKind::Tuple { elts, .. } = &types.node { + if elts.is_empty() { + return; + } let mut diagnostic = Diagnostic::new(IsinstanceWithTuple { kind }, Range::from(expr)); if checker.patch(diagnostic.kind.rule()) { From 254586979727aeaaad694d4fe1a166e37b9674d0 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 14 Mar 2023 18:08:43 -0400 Subject: [PATCH 20/49] Avoid PEP 604 isinstance errors for starred tuples (#3527) --- crates/ruff/resources/test/fixtures/pyupgrade/UP038.py | 1 + .../src/rules/pyupgrade/rules/use_pep604_isinstance.rs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/crates/ruff/resources/test/fixtures/pyupgrade/UP038.py b/crates/ruff/resources/test/fixtures/pyupgrade/UP038.py index 3b41c58831da9..fd95f34d10b8a 100644 --- a/crates/ruff/resources/test/fixtures/pyupgrade/UP038.py +++ b/crates/ruff/resources/test/fixtures/pyupgrade/UP038.py @@ -6,3 +6,4 @@ isinstance(1, int | float) # OK issubclass("yes", int | str) # OK isinstance(1, ()) # OK +isinstance(1, (int, *(str, bytes))) # OK diff --git a/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs b/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs index 15242a9af85ca..2c49d28fb4636 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs @@ -79,9 +79,19 @@ pub fn use_pep604_isinstance(checker: &mut Checker, expr: &Expr, func: &Expr, ar }; if let Some(types) = args.get(1) { if let ExprKind::Tuple { elts, .. } = &types.node { + // Ex) `()` if elts.is_empty() { return; } + + // Ex) `(*args,)` + if elts + .iter() + .any(|elt| matches!(elt.node, ExprKind::Starred { .. })) + { + return; + } + let mut diagnostic = Diagnostic::new(IsinstanceWithTuple { kind }, Range::from(expr)); if checker.patch(diagnostic.kind.rule()) { From 57796c5e59e3e0950d4409d3e54e7ba5a04bdda1 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 14 Mar 2023 20:08:09 -0400 Subject: [PATCH 21/49] Add last remaining deprecated typing imports (#3529) --- .../test/fixtures/pyupgrade/UP035.py | 2 +- .../pyupgrade/rules/deprecated_import.rs | 2 ++ ...ff__rules__pyupgrade__tests__UP035.py.snap | 35 +++++++++++++------ 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/pyupgrade/UP035.py b/crates/ruff/resources/test/fixtures/pyupgrade/UP035.py index d26f99eb07a49..aefd7064d166c 100644 --- a/crates/ruff/resources/test/fixtures/pyupgrade/UP035.py +++ b/crates/ruff/resources/test/fixtures/pyupgrade/UP035.py @@ -41,7 +41,7 @@ Good, ) -from typing import Callable, Match, Pattern, List, OrderedDict, AbstractSet +from typing import Callable, Match, Pattern, List, OrderedDict, AbstractSet, ContextManager if True: from collections import ( Mapping, Counter) diff --git a/crates/ruff/src/rules/pyupgrade/rules/deprecated_import.rs b/crates/ruff/src/rules/pyupgrade/rules/deprecated_import.rs index 3e0a3e14ff46e..4fc881752bf47 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/deprecated_import.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/deprecated_import.rs @@ -212,11 +212,13 @@ const TYPING_TO_RENAME_PY39: &[(&str, &str)] = &[ "AsyncContextManager", "contextlib.AbstractAsyncContextManager", ), + ("ContextManager", "contextlib.AbstractContextManager"), ("AbstractSet", "collections.abc.Set"), ("Tuple", "tuple"), ("List", "list"), ("FrozenSet", "frozenset"), ("Dict", "dict"), + ("Type", "type"), ("Set", "set"), ("Deque", "collections.deque"), ("DefaultDict", "collections.defaultdict"), diff --git a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP035.py.snap b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP035.py.snap index 5c830bafe1b23..441079428fa40 100644 --- a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP035.py.snap +++ b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP035.py.snap @@ -292,15 +292,15 @@ expression: diagnostics column: 0 end_location: row: 44 - column: 75 + column: 91 fix: - content: "from typing import Match, Pattern, List, OrderedDict, AbstractSet\nfrom collections.abc import Callable" + content: "from typing import Match, Pattern, List, OrderedDict, AbstractSet, ContextManager\nfrom collections.abc import Callable" location: row: 44 column: 0 end_location: row: 44 - column: 75 + column: 91 parent: ~ - kind: name: DeprecatedImport @@ -312,15 +312,15 @@ expression: diagnostics column: 0 end_location: row: 44 - column: 75 + column: 91 fix: - content: "from typing import Callable, Match, Pattern, List, AbstractSet\nfrom collections import OrderedDict" + content: "from typing import Callable, Match, Pattern, List, AbstractSet, ContextManager\nfrom collections import OrderedDict" location: row: 44 column: 0 end_location: row: 44 - column: 75 + column: 91 parent: ~ - kind: name: DeprecatedImport @@ -332,15 +332,15 @@ expression: diagnostics column: 0 end_location: row: 44 - column: 75 + column: 91 fix: - content: "from typing import Callable, List, OrderedDict, AbstractSet\nfrom re import Match, Pattern" + content: "from typing import Callable, List, OrderedDict, AbstractSet, ContextManager\nfrom re import Match, Pattern" location: row: 44 column: 0 end_location: row: 44 - column: 75 + column: 91 parent: ~ - kind: name: DeprecatedImport @@ -352,7 +352,7 @@ expression: diagnostics column: 0 end_location: row: 44 - column: 75 + column: 91 fix: ~ parent: ~ - kind: @@ -365,7 +365,20 @@ expression: diagnostics column: 0 end_location: row: 44 - column: 75 + column: 91 + fix: ~ + parent: ~ +- kind: + name: DeprecatedImport + body: "`typing.ContextManager` is deprecated, use `contextlib.AbstractContextManager` instead" + suggestion: ~ + fixable: false + location: + row: 44 + column: 0 + end_location: + row: 44 + column: 91 fix: ~ parent: ~ - kind: From d188d242a00a7583fc98c6ca5fe2746c7a064cea Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 14 Mar 2023 22:26:01 -0400 Subject: [PATCH 22/49] Avoid tracking as-imports separately with force-single-line (#3530) --- .../resources/test/fixtures/isort/force_single_line.py | 5 +++++ crates/ruff/src/rules/isort/mod.rs | 2 +- crates/ruff/src/rules/isort/normalize.rs | 10 +++++++--- ..._tests__force_single_line_force_single_line.py.snap | 6 +++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/isort/force_single_line.py b/crates/ruff/resources/test/fixtures/isort/force_single_line.py index b9bcf502d0f93..4dc242baa6a1a 100644 --- a/crates/ruff/resources/test/fixtures/isort/force_single_line.py +++ b/crates/ruff/resources/test/fixtures/isort/force_single_line.py @@ -1,5 +1,10 @@ import sys, math from os import path, uname +from json import detect_encoding +from json import dump +from json import dumps as json_dumps +from json import load +from json import loads as json_loads from logging.handlers import StreamHandler, FileHandler # comment 1 diff --git a/crates/ruff/src/rules/isort/mod.rs b/crates/ruff/src/rules/isort/mod.rs index 5df3c20a1a648..411f91058e37b 100644 --- a/crates/ruff/src/rules/isort/mod.rs +++ b/crates/ruff/src/rules/isort/mod.rs @@ -143,7 +143,7 @@ pub fn format_imports( let block = annotate_imports(&block.imports, comments, locator, split_on_trailing_comma); // Normalize imports (i.e., deduplicate, aggregate `from` imports). - let block = normalize_imports(block, combine_as_imports); + let block = normalize_imports(block, combine_as_imports, force_single_line); let mut output = String::new(); diff --git a/crates/ruff/src/rules/isort/normalize.rs b/crates/ruff/src/rules/isort/normalize.rs index f3aa484c82c70..98f52111bebca 100644 --- a/crates/ruff/src/rules/isort/normalize.rs +++ b/crates/ruff/src/rules/isort/normalize.rs @@ -3,7 +3,11 @@ use crate::rules::isort::types::TrailingComma; use super::types::{AliasData, ImportBlock, ImportFromData}; use super::AnnotatedImport; -pub fn normalize_imports(imports: Vec, combine_as_imports: bool) -> ImportBlock { +pub fn normalize_imports( + imports: Vec, + combine_as_imports: bool, + force_single_line: bool, +) -> ImportBlock { let mut block = ImportBlock::default(); for import in imports { match import { @@ -55,7 +59,7 @@ pub fn normalize_imports(imports: Vec, combine_as_imports: bool .import_from_star .entry(ImportFromData { module, level }) .or_default() - } else if alias.asname.is_none() || combine_as_imports { + } else if alias.asname.is_none() || combine_as_imports || force_single_line { block .import_from .entry(ImportFromData { module, level }) @@ -89,7 +93,7 @@ pub fn normalize_imports(imports: Vec, combine_as_imports: bool .import_from_star .entry(ImportFromData { module, level }) .or_default() - } else if alias.asname.is_none() || combine_as_imports { + } else if alias.asname.is_none() || combine_as_imports || force_single_line { block .import_from .entry(ImportFromData { module, level }) diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__force_single_line_force_single_line.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__force_single_line_force_single_line.py.snap index 276ca31a43dac..d846e63bbadc9 100644 --- a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__force_single_line_force_single_line.py.snap +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__force_single_line_force_single_line.py.snap @@ -11,15 +11,15 @@ expression: diagnostics row: 1 column: 0 end_location: - row: 20 + row: 25 column: 0 fix: - content: "import math\nimport sys\nfrom logging.handlers import FileHandler, StreamHandler\nfrom os import path, uname\n\n# comment 6\nfrom bar import a # comment 7\nfrom bar import b # comment 8\nfrom foo import bar # comment 3\nfrom foo2 import bar2 # comment 4\nfrom foo3 import bar3 # comment 5\nfrom foo3 import baz3 # comment 5\n\n# comment 1\n# comment 2\nfrom third_party import lib1\nfrom third_party import lib2\nfrom third_party import lib3\nfrom third_party import lib4\nfrom third_party import lib5\nfrom third_party import lib6\nfrom third_party import lib7\n" + content: "import math\nimport sys\nfrom json import detect_encoding\nfrom json import dump\nfrom json import dumps as json_dumps\nfrom json import load\nfrom json import loads as json_loads\nfrom logging.handlers import FileHandler, StreamHandler\nfrom os import path, uname\n\n# comment 6\nfrom bar import a # comment 7\nfrom bar import b # comment 8\nfrom foo import bar # comment 3\nfrom foo2 import bar2 # comment 4\nfrom foo3 import bar3 # comment 5\nfrom foo3 import baz3 # comment 5\n\n# comment 1\n# comment 2\nfrom third_party import lib1\nfrom third_party import lib2\nfrom third_party import lib3\nfrom third_party import lib4\nfrom third_party import lib5\nfrom third_party import lib6\nfrom third_party import lib7\n" location: row: 1 column: 0 end_location: - row: 20 + row: 25 column: 0 parent: ~ From 12dfd57211ec11a593aa93057dc1c49221bf2b09 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 14 Mar 2023 22:52:21 -0400 Subject: [PATCH 23/49] Bump version to v0.0.256 (#3531) --- Cargo.lock | 6 +++--- README.md | 2 +- crates/flake8_to_ruff/Cargo.toml | 2 +- crates/ruff/Cargo.toml | 2 +- crates/ruff_cli/Cargo.toml | 2 +- docs/tutorial.md | 2 +- docs/usage.md | 4 ++-- pyproject.toml | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 94078d0797bb1..4a8e7bbc9bbd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -780,7 +780,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flake8-to-ruff" -version = "0.0.255" +version = "0.0.256" dependencies = [ "anyhow", "clap 4.1.8", @@ -1982,7 +1982,7 @@ dependencies = [ [[package]] name = "ruff" -version = "0.0.255" +version = "0.0.256" dependencies = [ "anyhow", "bisection", @@ -2060,7 +2060,7 @@ dependencies = [ [[package]] name = "ruff_cli" -version = "0.0.255" +version = "0.0.256" dependencies = [ "annotate-snippets 0.9.1", "anyhow", diff --git a/README.md b/README.md index b9d708e9324c2..6e765d21d3adb 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook: ```yaml - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.0.255' + rev: 'v0.0.256' hooks: - id: ruff ``` diff --git a/crates/flake8_to_ruff/Cargo.toml b/crates/flake8_to_ruff/Cargo.toml index 2f7168abbf23e..b98d7e3e2ebe0 100644 --- a/crates/flake8_to_ruff/Cargo.toml +++ b/crates/flake8_to_ruff/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flake8-to-ruff" -version = "0.0.255" +version = "0.0.256" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index 3325a0d532e68..69fbdb42e574c 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff" -version = "0.0.255" +version = "0.0.256" authors.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/crates/ruff_cli/Cargo.toml b/crates/ruff_cli/Cargo.toml index 6571be0314cc1..9dc3adb332f1d 100644 --- a/crates/ruff_cli/Cargo.toml +++ b/crates/ruff_cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_cli" -version = "0.0.255" +version = "0.0.256" authors = ["Charlie Marsh "] edition = { workspace = true } rust-version = { workspace = true } diff --git a/docs/tutorial.md b/docs/tutorial.md index 02959de35df51..85fb1eff4b035 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -242,7 +242,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be ```yaml - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.0.255' + rev: 'v0.0.256' hooks: - id: ruff ``` diff --git a/docs/usage.md b/docs/usage.md index bdd07e263ca1b..743f5fe69e8ea 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -20,7 +20,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook: ```yaml - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.0.255' + rev: 'v0.0.256' hooks: - id: ruff ``` @@ -30,7 +30,7 @@ Or, to enable autofix: ```yaml - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.0.255' + rev: 'v0.0.256' hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/pyproject.toml b/pyproject.toml index 65d46870a4583..8e4f0f5523189 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "maturin" [project] name = "ruff" -version = "0.0.255" +version = "0.0.256" description = "An extremely fast Python linter, written in Rust." authors = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }] maintainers = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }] From e636c5fcf07313ad83c1c75d5b1459b4999efd20 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 14 Mar 2023 23:17:19 -0400 Subject: [PATCH 24/49] Avoid unused argument violations in .pyi files (#3533) --- crates/ruff/src/checkers/ast/mod.rs | 44 ++++++++++++++++------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 907f4904ccaf7..d20b003b09aa9 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -4685,31 +4685,37 @@ impl<'a> Checker<'a> { while let Some((scopes, ..)) = self.deferred.assignments.pop() { let scope_index = scopes[scopes.len() - 1]; let parent_scope_index = scopes[scopes.len() - 2]; + + // pyflakes if self.settings.rules.enabled(&Rule::UnusedVariable) { pyflakes::rules::unused_variable(self, scope_index); } if self.settings.rules.enabled(&Rule::UnusedAnnotation) { pyflakes::rules::unused_annotation(self, scope_index); } - if self.settings.rules.enabled(&Rule::UnusedFunctionArgument) - || self.settings.rules.enabled(&Rule::UnusedMethodArgument) - || self - .settings - .rules - .enabled(&Rule::UnusedClassMethodArgument) - || self - .settings - .rules - .enabled(&Rule::UnusedStaticMethodArgument) - || self.settings.rules.enabled(&Rule::UnusedLambdaArgument) - { - self.diagnostics - .extend(flake8_unused_arguments::rules::unused_arguments( - self, - &self.ctx.scopes[parent_scope_index], - &self.ctx.scopes[scope_index], - &self.ctx.bindings, - )); + + if !self.is_stub { + // flake8-unused-arguments + if self.settings.rules.enabled(&Rule::UnusedFunctionArgument) + || self.settings.rules.enabled(&Rule::UnusedMethodArgument) + || self + .settings + .rules + .enabled(&Rule::UnusedClassMethodArgument) + || self + .settings + .rules + .enabled(&Rule::UnusedStaticMethodArgument) + || self.settings.rules.enabled(&Rule::UnusedLambdaArgument) + { + self.diagnostics + .extend(flake8_unused_arguments::rules::unused_arguments( + self, + &self.ctx.scopes[parent_scope_index], + &self.ctx.scopes[scope_index], + &self.ctx.bindings, + )); + } } } } From de1106b95a929958ea99fc0d3239bb57973fb51b Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 15 Mar 2023 09:34:53 +0100 Subject: [PATCH 25/49] Allow dispatching the PR comment job for testing (#3535) --- .../{ecosystem-comment.yaml => pr-comment.yaml} | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) rename .github/workflows/{ecosystem-comment.yaml => pr-comment.yaml} (75%) diff --git a/.github/workflows/ecosystem-comment.yaml b/.github/workflows/pr-comment.yaml similarity index 75% rename from .github/workflows/ecosystem-comment.yaml rename to .github/workflows/pr-comment.yaml index b29425614dc97..c9f652bdd0954 100644 --- a/.github/workflows/ecosystem-comment.yaml +++ b/.github/workflows/pr-comment.yaml @@ -1,7 +1,14 @@ +name: PR Check Comment + on: workflow_run: workflows: [CI] types: [completed] + workflow_dispatch: + inputs: + workflow_run_id: + description: The ecosystem workflow that triggers the workflow run + required: true permissions: pull-requests: write @@ -16,7 +23,7 @@ jobs: with: name: ecosystem-result workflow: ci.yaml - run_id: ${{ github.event.workflow_run.id }} + run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }} if_no_artifact_found: ignore - if: steps.download-result.outputs.found_artifact id: result From 9ae9cc9d2f5483e8db8611acd9df3342b635931d Mon Sep 17 00:00:00 2001 From: "Edgar R. M" Date: Wed, 15 Mar 2023 23:37:25 -0600 Subject: [PATCH 26/49] Use `value > max` style in pylint and mccabe messages (#3553) --- crates/ruff/src/rules/mccabe/rules.rs | 10 ++++- ...ules__mccabe__tests__max_complexity_0.snap | 44 +++++++++---------- ...ules__mccabe__tests__max_complexity_3.snap | 4 +- .../rules/pylint/rules/too_many_arguments.rs | 2 +- .../rules/pylint/rules/too_many_branches.rs | 2 +- .../rules/too_many_return_statements.rs | 2 +- .../rules/pylint/rules/too_many_statements.rs | 2 +- ...PLR0911_too_many_return_statements.py.snap | 2 +- ...__tests__PLR0912_too_many_branches.py.snap | 2 +- ..._tests__PLR0913_too_many_arguments.py.snap | 6 +-- ...tests__PLR0915_too_many_statements.py.snap | 2 +- .../ruff__rules__pylint__tests__max_args.snap | 4 +- ..._tests__max_args_with_dummy_variables.snap | 2 +- ...f__rules__pylint__tests__max_branches.snap | 4 +- ..._pylint__tests__max_return_statements.snap | 2 +- ..._rules__pylint__tests__max_statements.snap | 6 +-- 16 files changed, 51 insertions(+), 45 deletions(-) diff --git a/crates/ruff/src/rules/mccabe/rules.rs b/crates/ruff/src/rules/mccabe/rules.rs index 71febbc60171a..5b07789db63fa 100644 --- a/crates/ruff/src/rules/mccabe/rules.rs +++ b/crates/ruff/src/rules/mccabe/rules.rs @@ -50,13 +50,18 @@ use ruff_python_ast::source_code::Locator; pub struct ComplexStructure { pub name: String, pub complexity: usize, + pub max_complexity: usize, } impl Violation for ComplexStructure { #[derive_message_formats] fn message(&self) -> String { - let ComplexStructure { name, complexity } = self; - format!("`{name}` is too complex ({complexity})") + let ComplexStructure { + name, + complexity, + max_complexity, + } = self; + format!("`{name}` is too complex ({complexity} > {max_complexity})") } } @@ -135,6 +140,7 @@ pub fn function_is_too_complex( ComplexStructure { name: name.to_string(), complexity, + max_complexity, }, identifier_range(stmt, locator), )) diff --git a/crates/ruff/src/rules/mccabe/snapshots/ruff__rules__mccabe__tests__max_complexity_0.snap b/crates/ruff/src/rules/mccabe/snapshots/ruff__rules__mccabe__tests__max_complexity_0.snap index 632935557df75..9a1351878d41f 100644 --- a/crates/ruff/src/rules/mccabe/snapshots/ruff__rules__mccabe__tests__max_complexity_0.snap +++ b/crates/ruff/src/rules/mccabe/snapshots/ruff__rules__mccabe__tests__max_complexity_0.snap @@ -4,7 +4,7 @@ expression: diagnostics --- - kind: name: ComplexStructure - body: "`trivial` is too complex (1)" + body: "`trivial` is too complex (1 > 0)" suggestion: ~ fixable: false location: @@ -17,7 +17,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`expr_as_statement` is too complex (1)" + body: "`expr_as_statement` is too complex (1 > 0)" suggestion: ~ fixable: false location: @@ -30,7 +30,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`sequential` is too complex (1)" + body: "`sequential` is too complex (1 > 0)" suggestion: ~ fixable: false location: @@ -43,7 +43,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`if_elif_else_dead_path` is too complex (3)" + body: "`if_elif_else_dead_path` is too complex (3 > 0)" suggestion: ~ fixable: false location: @@ -56,7 +56,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`nested_ifs` is too complex (3)" + body: "`nested_ifs` is too complex (3 > 0)" suggestion: ~ fixable: false location: @@ -69,7 +69,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`for_loop` is too complex (2)" + body: "`for_loop` is too complex (2 > 0)" suggestion: ~ fixable: false location: @@ -82,7 +82,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`for_else` is too complex (2)" + body: "`for_else` is too complex (2 > 0)" suggestion: ~ fixable: false location: @@ -95,7 +95,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`recursive` is too complex (2)" + body: "`recursive` is too complex (2 > 0)" suggestion: ~ fixable: false location: @@ -108,7 +108,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`nested_functions` is too complex (3)" + body: "`nested_functions` is too complex (3 > 0)" suggestion: ~ fixable: false location: @@ -121,7 +121,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`a` is too complex (2)" + body: "`a` is too complex (2 > 0)" suggestion: ~ fixable: false location: @@ -134,7 +134,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`b` is too complex (1)" + body: "`b` is too complex (1 > 0)" suggestion: ~ fixable: false location: @@ -147,7 +147,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`try_else` is too complex (4)" + body: "`try_else` is too complex (4 > 0)" suggestion: ~ fixable: false location: @@ -160,7 +160,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`nested_try_finally` is too complex (1)" + body: "`nested_try_finally` is too complex (1 > 0)" suggestion: ~ fixable: false location: @@ -173,7 +173,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`foobar` is too complex (3)" + body: "`foobar` is too complex (3 > 0)" suggestion: ~ fixable: false location: @@ -186,7 +186,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`annotated_assign` is too complex (1)" + body: "`annotated_assign` is too complex (1 > 0)" suggestion: ~ fixable: false location: @@ -199,7 +199,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`handle` is too complex (9)" + body: "`handle` is too complex (9 > 0)" suggestion: ~ fixable: false location: @@ -212,7 +212,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`a` is too complex (1)" + body: "`a` is too complex (1 > 0)" suggestion: ~ fixable: false location: @@ -225,7 +225,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`b` is too complex (2)" + body: "`b` is too complex (2 > 0)" suggestion: ~ fixable: false location: @@ -238,7 +238,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`c` is too complex (1)" + body: "`c` is too complex (1 > 0)" suggestion: ~ fixable: false location: @@ -251,7 +251,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`error` is too complex (1)" + body: "`error` is too complex (1 > 0)" suggestion: ~ fixable: false location: @@ -264,7 +264,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`info` is too complex (1)" + body: "`info` is too complex (1 > 0)" suggestion: ~ fixable: false location: @@ -277,7 +277,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`exception` is too complex (1)" + body: "`exception` is too complex (1 > 0)" suggestion: ~ fixable: false location: diff --git a/crates/ruff/src/rules/mccabe/snapshots/ruff__rules__mccabe__tests__max_complexity_3.snap b/crates/ruff/src/rules/mccabe/snapshots/ruff__rules__mccabe__tests__max_complexity_3.snap index b00affeb57f46..5bcea57845be2 100644 --- a/crates/ruff/src/rules/mccabe/snapshots/ruff__rules__mccabe__tests__max_complexity_3.snap +++ b/crates/ruff/src/rules/mccabe/snapshots/ruff__rules__mccabe__tests__max_complexity_3.snap @@ -4,7 +4,7 @@ expression: diagnostics --- - kind: name: ComplexStructure - body: "`try_else` is too complex (4)" + body: "`try_else` is too complex (4 > 3)" suggestion: ~ fixable: false location: @@ -17,7 +17,7 @@ expression: diagnostics parent: ~ - kind: name: ComplexStructure - body: "`handle` is too complex (9)" + body: "`handle` is too complex (9 > 3)" suggestion: ~ fixable: false location: diff --git a/crates/ruff/src/rules/pylint/rules/too_many_arguments.rs b/crates/ruff/src/rules/pylint/rules/too_many_arguments.rs index a37eec9c032a8..e39a5d0bf0546 100644 --- a/crates/ruff/src/rules/pylint/rules/too_many_arguments.rs +++ b/crates/ruff/src/rules/pylint/rules/too_many_arguments.rs @@ -16,7 +16,7 @@ impl Violation for TooManyArguments { #[derive_message_formats] fn message(&self) -> String { let TooManyArguments { c_args, max_args } = self; - format!("Too many arguments to function call ({c_args}/{max_args})") + format!("Too many arguments to function call ({c_args} > {max_args})") } } diff --git a/crates/ruff/src/rules/pylint/rules/too_many_branches.rs b/crates/ruff/src/rules/pylint/rules/too_many_branches.rs index 51e847e35b178..86d31810dee61 100644 --- a/crates/ruff/src/rules/pylint/rules/too_many_branches.rs +++ b/crates/ruff/src/rules/pylint/rules/too_many_branches.rs @@ -18,7 +18,7 @@ impl Violation for TooManyBranches { branches, max_branches, } = self; - format!("Too many branches ({branches}/{max_branches})") + format!("Too many branches ({branches} > {max_branches})") } } diff --git a/crates/ruff/src/rules/pylint/rules/too_many_return_statements.rs b/crates/ruff/src/rules/pylint/rules/too_many_return_statements.rs index 9ddbad22c5869..687225e6e7803 100644 --- a/crates/ruff/src/rules/pylint/rules/too_many_return_statements.rs +++ b/crates/ruff/src/rules/pylint/rules/too_many_return_statements.rs @@ -19,7 +19,7 @@ impl Violation for TooManyReturnStatements { returns, max_returns, } = self; - format!("Too many return statements ({returns}/{max_returns})") + format!("Too many return statements ({returns} > {max_returns})") } } diff --git a/crates/ruff/src/rules/pylint/rules/too_many_statements.rs b/crates/ruff/src/rules/pylint/rules/too_many_statements.rs index 981e4abb9c1c5..8a07fe92b0ffa 100644 --- a/crates/ruff/src/rules/pylint/rules/too_many_statements.rs +++ b/crates/ruff/src/rules/pylint/rules/too_many_statements.rs @@ -18,7 +18,7 @@ impl Violation for TooManyStatements { statements, max_statements, } = self; - format!("Too many statements ({statements}/{max_statements})") + format!("Too many statements ({statements} > {max_statements})") } } diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0911_too_many_return_statements.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0911_too_many_return_statements.py.snap index f0b94fef5d1a1..b5380f2bc8af1 100644 --- a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0911_too_many_return_statements.py.snap +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0911_too_many_return_statements.py.snap @@ -4,7 +4,7 @@ expression: diagnostics --- - kind: name: TooManyReturnStatements - body: Too many return statements (11/6) + body: Too many return statements (11 > 6) suggestion: ~ fixable: false location: diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0912_too_many_branches.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0912_too_many_branches.py.snap index ae130f88def9c..d5f086b68fc74 100644 --- a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0912_too_many_branches.py.snap +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0912_too_many_branches.py.snap @@ -4,7 +4,7 @@ expression: diagnostics --- - kind: name: TooManyBranches - body: Too many branches (13/12) + body: Too many branches (13 > 12) suggestion: ~ fixable: false location: diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0913_too_many_arguments.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0913_too_many_arguments.py.snap index e43addadf7932..322ae7535f644 100644 --- a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0913_too_many_arguments.py.snap +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0913_too_many_arguments.py.snap @@ -4,7 +4,7 @@ expression: diagnostics --- - kind: name: TooManyArguments - body: Too many arguments to function call (8/5) + body: Too many arguments to function call (8 > 5) suggestion: ~ fixable: false location: @@ -17,7 +17,7 @@ expression: diagnostics parent: ~ - kind: name: TooManyArguments - body: Too many arguments to function call (6/5) + body: Too many arguments to function call (6 > 5) suggestion: ~ fixable: false location: @@ -30,7 +30,7 @@ expression: diagnostics parent: ~ - kind: name: TooManyArguments - body: Too many arguments to function call (6/5) + body: Too many arguments to function call (6 > 5) suggestion: ~ fixable: false location: diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0915_too_many_statements.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0915_too_many_statements.py.snap index 4cd6c40b280ae..b2400d3d11c04 100644 --- a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0915_too_many_statements.py.snap +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0915_too_many_statements.py.snap @@ -4,7 +4,7 @@ expression: diagnostics --- - kind: name: TooManyStatements - body: Too many statements (52/50) + body: Too many statements (52 > 50) suggestion: ~ fixable: false location: diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_args.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_args.snap index b01b0aaae8571..403cd755a1512 100644 --- a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_args.snap +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_args.snap @@ -4,7 +4,7 @@ expression: diagnostics --- - kind: name: TooManyArguments - body: Too many arguments to function call (6/4) + body: Too many arguments to function call (6 > 4) suggestion: ~ fixable: false location: @@ -17,7 +17,7 @@ expression: diagnostics parent: ~ - kind: name: TooManyArguments - body: Too many arguments to function call (6/4) + body: Too many arguments to function call (6 > 4) suggestion: ~ fixable: false location: diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_args_with_dummy_variables.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_args_with_dummy_variables.snap index cf25b6cd3ab57..aabf525484bb2 100644 --- a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_args_with_dummy_variables.snap +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_args_with_dummy_variables.snap @@ -4,7 +4,7 @@ expression: diagnostics --- - kind: name: TooManyArguments - body: Too many arguments to function call (6/5) + body: Too many arguments to function call (6 > 5) suggestion: ~ fixable: false location: diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_branches.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_branches.snap index 57b5398732593..5861ab0c68bc9 100644 --- a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_branches.snap +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_branches.snap @@ -4,7 +4,7 @@ expression: diagnostics --- - kind: name: TooManyBranches - body: Too many branches (2/1) + body: Too many branches (2 > 1) suggestion: ~ fixable: false location: @@ -17,7 +17,7 @@ expression: diagnostics parent: ~ - kind: name: TooManyBranches - body: Too many branches (2/1) + body: Too many branches (2 > 1) suggestion: ~ fixable: false location: diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_return_statements.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_return_statements.snap index de06f892031d5..a670693bf4a5c 100644 --- a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_return_statements.snap +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_return_statements.snap @@ -4,7 +4,7 @@ expression: diagnostics --- - kind: name: TooManyReturnStatements - body: Too many return statements (2/1) + body: Too many return statements (2 > 1) suggestion: ~ fixable: false location: diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_statements.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_statements.snap index b0251849070b3..67f07d53037b6 100644 --- a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_statements.snap +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__max_statements.snap @@ -4,7 +4,7 @@ expression: diagnostics --- - kind: name: TooManyStatements - body: Too many statements (2/1) + body: Too many statements (2 > 1) suggestion: ~ fixable: false location: @@ -17,7 +17,7 @@ expression: diagnostics parent: ~ - kind: name: TooManyStatements - body: Too many statements (3/1) + body: Too many statements (3 > 1) suggestion: ~ fixable: false location: @@ -30,7 +30,7 @@ expression: diagnostics parent: ~ - kind: name: TooManyStatements - body: Too many statements (2/1) + body: Too many statements (2 > 1) suggestion: ~ fixable: false location: From aa51ecedc5ebbba5173350d45f0ea4ae9ae41df6 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 16 Mar 2023 09:05:10 +0100 Subject: [PATCH 27/49] ci: Benchmark CI Step (#3480) --- .github/workflows/benchmark.yaml | 133 ++++++++++++++++++++++++++++++ .github/workflows/ci.yaml | 19 ++++- .github/workflows/pr-comment.yaml | 69 +++++++++++++--- 3 files changed, 206 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/benchmark.yaml diff --git a/.github/workflows/benchmark.yaml b/.github/workflows/benchmark.yaml new file mode 100644 index 0000000000000..48bd53fa6417a --- /dev/null +++ b/.github/workflows/benchmark.yaml @@ -0,0 +1,133 @@ +name: Benchmark + +on: + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + run-benchmark: + if: github.event_name == 'pull_request' + name: "Run | ${{ matrix.os }}" + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + + steps: + - name: "PR - Checkout Branch" + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: "PR - Install Rust toolchain" + run: rustup show + + - uses: Swatinem/rust-cache@v1 + + - name: "PR - Build benchmarks" + uses: actions-rs/cargo@v1 + with: + command: bench + args: -p ruff_benchmark --no-run + + - name: "PR - Run benchmarks" + run: cargo benchmark --save-baseline=pr + + - name: "Main - Checkout Branch" + uses: actions/checkout@v3 + with: + clean: false + ref: main + + - name: "Main - Install Rust toolchain" + run: rustup show + + - name: "Main - Build benchmarks" + uses: actions-rs/cargo@v1 + with: + command: bench + args: -p ruff_benchmark --no-run + + - name: "Main - Run benchmarks" + run: cargo benchmark --save-baseline=main + + - name: "Upload benchmark results" + uses: actions/upload-artifact@v3 + with: + name: benchmark-results-${{ matrix.os }} + path: ./target/criterion + + # Cleanup + - name: Remove Criterion Artifact + uses: JesseTG/rm@v1.0.3 + with: + path: ./target/criterion + + benchmark-compare: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + name: Compare + needs: + - run-benchmark + + steps: + - name: "Install Rust toolchain" + run: rustup show + + - name: "Install critcmp" + # Use debug build: Building takes much longer than the "slowness" of using the debug build. + run: cargo install --debug critcmp + + - name: "Linux | Download PR benchmark results" + uses: actions/download-artifact@v3 + with: + name: benchmark-results-ubuntu-latest + path: ./target/criterion + + - name: "Linux | Compare benchmark results" + shell: bash + run: | + echo "### Benchmark" >> summary.md + echo "#### Linux" >> summary.md + echo "\`\`\`" >> summary.md + critcmp main pr >> summary.md + echo "\`\`\`" >> summary.md + echo "" >> summary.md + + - name: "Linux | Cleanup benchmark results" + run: rm -rf ./target/criterion + + - name: "Windows | Download PR benchmark results" + uses: actions/download-artifact@v3 + with: + name: benchmark-results-windows-latest + path: ./target/criterion + + - name: "Windows | Compare benchmark results" + shell: bash + run: | + echo "#### Windows" >> summary.md + echo "\`\`\`" >> summary.md + critcmp main pr >> summary.md + echo "\`\`\`" >> summary.md + echo "" >> summary.md + + echo ${{ github.event.pull_request.number }} > pr-number + + cat summary.md > $GITHUB_STEP_SUMMARY + + - uses: actions/upload-artifact@v3 + name: Upload PR Number + with: + name: pr-number + path: pr-number + + - uses: actions/upload-artifact@v3 + name: Upload Summary + with: + name: summary + path: summary.md diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1c48a0c44abcf..1e710e7ac30c4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -80,6 +80,7 @@ jobs: # Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025). RUSTDOCFLAGS: "-D warnings" - uses: actions/upload-artifact@v3 + if: ${{ matrix.os == 'ubuntu-latest' }} with: name: ruff path: target/debug/ruff @@ -139,27 +140,39 @@ jobs: - uses: actions/setup-python@v4 with: python-version: "3.11" + - uses: actions/download-artifact@v3 + name: Download Ruff binary id: ruff-target with: name: ruff path: target/debug + - uses: dawidd6/action-download-artifact@v2 + name: Download base results with: name: ruff branch: ${{ github.event.pull_request.base.ref }} check_artifacts: true + - name: Run ecosystem check run: | # Make executable, since artifact download doesn't preserve this chmod +x ruff ${{ steps.ruff-target.outputs.download-path }}/ruff scripts/check_ecosystem.py ruff ${{ steps.ruff-target.outputs.download-path }}/ruff | tee ecosystem-result + cat ecosystem-result > $GITHUB_STEP_SUMMARY echo ${{ github.event.number }} > pr-number + + - uses: actions/upload-artifact@v3 + name: Upload PR Number + with: + name: pr-number + path: pr-number + - uses: actions/upload-artifact@v3 + name: Upload Results with: name: ecosystem-result - path: | - ecosystem-result - pr-number + path: ecosystem-result diff --git a/.github/workflows/pr-comment.yaml b/.github/workflows/pr-comment.yaml index c9f652bdd0954..80ecbd9c14fb8 100644 --- a/.github/workflows/pr-comment.yaml +++ b/.github/workflows/pr-comment.yaml @@ -2,7 +2,7 @@ name: PR Check Comment on: workflow_run: - workflows: [CI] + workflows: [CI, Benchmark] types: [completed] workflow_dispatch: inputs: @@ -17,22 +17,67 @@ jobs: comment: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - uses: dawidd6/action-download-artifact@v2 - id: download-result + name: Download PR Number + with: + name: pr-number + run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }} + if_no_artifact_found: ignore + + - name: Extract PR Number + id: pr-number + run: | + if [[ -f pr-number ]] + then + echo "pr-number=$(> $GITHUB_OUTPUT + fi + + - uses: dawidd6/action-download-artifact@v2 + name: "Download Ecosystem Result" + id: download-ecosystem-result + if: steps.pr-number.outputs.pr-number with: name: ecosystem-result workflow: ci.yaml - run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }} + pr: ${{ steps.pr-number.outputs.pr-number }} + path: pr/ecosystem if_no_artifact_found: ignore - - if: steps.download-result.outputs.found_artifact - id: result + + - uses: dawidd6/action-download-artifact@v2 + name: "Download Benchmark Result" + id: download-benchmark-result + if: steps.pr-number.outputs.pr-number + with: + name: summary + workflow: benchmark.yaml + pr: ${{ steps.pr-number.outputs.pr-number }} + path: pr/benchmark + if_no_artifact_found: ignore + + - name: Generate Comment + id: generate-comment + if: steps.download-ecosystem-result.outputs.found_artifact == 'true' || steps.download-benchmark-result.outputs.found_artifact == 'true' run: | - echo "pr-number=$(> $GITHUB_OUTPUT - - name: Create comment - if: steps.download-result.outputs.found_artifact + echo 'comment<> $GITHUB_OUTPUT + echo '## PR Check Results' >> $GITHUB_OUTPUT + + if [[ -f pr/ecosystem/ecosystem-result ]] + then + echo "### Ecosystem" >> $GITHUB_OUTPUT + cat pr/ecosystem/ecosystem-result >> $GITHUB_OUTPUT + fi + + if [[ -f pr/benchmark/summary.md ]] + then + cat pr/benchmark/summary.md >> $GITHUB_OUTPUT + fi + + echo 'EOF' >> $GITHUB_OUTPUT + + - name: Create or update comment + if: steps.generate-comment.outputs.comment uses: thollander/actions-comment-pull-request@v2 with: - pr_number: ${{ steps.result.outputs.pr-number }} - filePath: ecosystem-result - comment_tag: ecosystem-results + pr_number: ${{ steps.pr-number.outputs.pr-number }} + message: ${{ steps.generate-comment.outputs.comment }} + comment_tag: PR Check Results From eff84442bce927cd7f58484d0d28ffcc4c2d467a Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 16 Mar 2023 17:50:18 +0100 Subject: [PATCH 28/49] refactor: Add Copy implementation to Rule (#3556) --- crates/ruff/src/checkers/ast/mod.rs | 1234 ++++++++--------- crates/ruff/src/checkers/filesystem.rs | 4 +- crates/ruff/src/checkers/imports.rs | 4 +- crates/ruff/src/checkers/logical_lines.rs | 4 +- crates/ruff/src/checkers/noqa.rs | 4 +- crates/ruff/src/checkers/physical_lines.rs | 38 +- crates/ruff/src/checkers/tokens.rs | 40 +- crates/ruff/src/fs.rs | 18 +- crates/ruff/src/linter.rs | 10 +- crates/ruff/src/noqa.rs | 8 +- crates/ruff/src/registry.rs | 10 + crates/ruff/src/rules/eradicate/rules.rs | 2 +- crates/ruff/src/rules/flake8_2020/rules.rs | 18 +- .../src/rules/flake8_annotations/rules.rs | 30 +- .../rules/abstract_base_class.rs | 4 +- .../rules/duplicate_exceptions.rs | 4 +- crates/ruff/src/rules/flake8_commas/rules.rs | 4 +- crates/ruff/src/rules/flake8_errmsg/rules.rs | 6 +- .../src/rules/flake8_logging_format/rules.rs | 20 +- .../flake8_pyi/rules/unrecognized_platform.rs | 6 +- .../flake8_pytest_style/rules/fixture.rs | 43 +- .../rules/flake8_pytest_style/rules/marks.rs | 4 +- .../flake8_pytest_style/rules/parametrize.rs | 4 +- .../rules/flake8_pytest_style/rules/raises.rs | 8 +- crates/ruff/src/rules/flake8_quotes/rules.rs | 8 +- crates/ruff/src/rules/flake8_return/rules.rs | 16 +- .../rules/reimplemented_builtin.rs | 4 +- .../rules/flake8_unused_arguments/types.rs | 12 +- .../rules/isort/rules/add_required_imports.rs | 2 +- crates/ruff/src/rules/pandas_vet/mod.rs | 2 +- .../src/rules/pandas_vet/rules/check_attr.rs | 8 +- .../src/rules/pandas_vet/rules/check_call.rs | 10 +- .../pycodestyle/rules/compound_statements.rs | 2 +- .../pycodestyle/rules/trailing_whitespace.rs | 10 +- .../rules/blank_before_after_class.rs | 21 +- .../rules/blank_before_after_function.rs | 4 +- .../ruff/src/rules/pydocstyle/rules/indent.rs | 6 +- .../rules/multi_line_summary_start.rs | 4 +- .../src/rules/pydocstyle/rules/not_empty.rs | 2 +- .../src/rules/pydocstyle/rules/not_missing.rs | 18 +- .../src/rules/pydocstyle/rules/sections.rs | 50 +- crates/ruff/src/rules/pyflakes/mod.rs | 2 +- .../src/rules/pyflakes/rules/repeated_keys.rs | 4 +- crates/ruff/src/rules/pylint/rules/logging.rs | 4 +- .../pyupgrade/rules/deprecated_import.rs | 2 +- .../pyupgrade/rules/extraneous_parentheses.rs | 2 +- .../pyupgrade/rules/quoted_annotation.rs | 2 +- .../pyupgrade/rules/redundant_open_modes.rs | 4 +- .../pyupgrade/rules/rewrite_mock_import.rs | 2 +- .../rules/unnecessary_encode_utf8.rs | 6 +- crates/ruff/src/settings/mod.rs | 8 +- crates/ruff/src/settings/rule_table.rs | 16 +- crates/ruff_cli/src/commands/rule.rs | 2 +- crates/ruff_cli/src/commands/run.rs | 2 +- crates/ruff_cli/src/lib.rs | 2 +- crates/ruff_cli/src/printer.rs | 18 +- crates/ruff_dev/src/generate_rules_table.rs | 2 +- crates/ruff_macros/src/map_codes.rs | 4 +- crates/ruff_macros/src/register_rules.rs | 12 +- 59 files changed, 835 insertions(+), 965 deletions(-) diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index d20b003b09aa9..633f9033832ec 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -118,12 +118,12 @@ impl<'a> Checker<'a> { impl<'a> Checker<'a> { /// Return `true` if a patch should be generated under the given autofix /// `Mode`. - pub fn patch(&self, code: &Rule) -> bool { + pub fn patch(&self, code: Rule) -> bool { self.autofix.into() && self.settings.rules.should_fix(code) } /// Return `true` if a `Rule` is disabled by a `noqa` directive. - pub fn rule_is_ignored(&self, code: &Rule, lineno: usize) -> bool { + pub fn rule_is_ignored(&self, code: Rule, lineno: usize) -> bool { // TODO(charlie): `noqa` directives are mostly enforced in `check_lines.rs`. // However, in rare cases, we need to check them here. For example, when // removing unused imports, we create a single fix that's applied to all @@ -215,7 +215,7 @@ where } } - if self.settings.rules.enabled(&Rule::AmbiguousVariableName) { + if self.settings.rules.enabled(Rule::AmbiguousVariableName) { self.diagnostics .extend(names.iter().zip(ranges.iter()).filter_map(|(name, range)| { pycodestyle::rules::ambiguous_variable_name(name, *range) @@ -259,7 +259,7 @@ where // Ensure that every nonlocal has an existing binding from a parent scope. if !exists { - if self.settings.rules.enabled(&Rule::NonlocalWithoutBinding) { + if self.settings.rules.enabled(Rule::NonlocalWithoutBinding) { self.diagnostics.push(Diagnostic::new( pylint::rules::NonlocalWithoutBinding { name: name.to_string(), @@ -271,7 +271,7 @@ where } } - if self.settings.rules.enabled(&Rule::AmbiguousVariableName) { + if self.settings.rules.enabled(Rule::AmbiguousVariableName) { self.diagnostics .extend(names.iter().zip(ranges.iter()).filter_map(|(name, range)| { pycodestyle::rules::ambiguous_variable_name(name, *range) @@ -279,7 +279,7 @@ where } } StmtKind::Break => { - if self.settings.rules.enabled(&Rule::BreakOutsideLoop) { + if self.settings.rules.enabled(Rule::BreakOutsideLoop) { if let Some(diagnostic) = pyflakes::rules::break_outside_loop( stmt, &mut self.ctx.parents.iter().rev().map(Into::into).skip(1), @@ -289,7 +289,7 @@ where } } StmtKind::Continue => { - if self.settings.rules.enabled(&Rule::ContinueOutsideLoop) { + if self.settings.rules.enabled(Rule::ContinueOutsideLoop) { if let Some(diagnostic) = pyflakes::rules::continue_outside_loop( stmt, &mut self.ctx.parents.iter().rev().map(Into::into).skip(1), @@ -317,7 +317,7 @@ where if self .settings .rules - .enabled(&Rule::NonLeadingReceiverDecorator) + .enabled(Rule::NonLeadingReceiverDecorator) { self.diagnostics .extend(flake8_django::rules::non_leading_receiver_decorator( @@ -325,7 +325,7 @@ where |expr| self.ctx.resolve_call_path(expr), )); } - if self.settings.rules.enabled(&Rule::AmbiguousFunctionName) { + if self.settings.rules.enabled(Rule::AmbiguousFunctionName) { if let Some(diagnostic) = pycodestyle::rules::ambiguous_function_name(name, || { helpers::identifier_range(stmt, self.locator) @@ -335,7 +335,7 @@ where } } - if self.settings.rules.enabled(&Rule::InvalidFunctionName) { + if self.settings.rules.enabled(Rule::InvalidFunctionName) { if let Some(diagnostic) = pep8_naming::rules::invalid_function_name( stmt, name, @@ -349,7 +349,7 @@ where if self .settings .rules - .enabled(&Rule::InvalidFirstArgumentNameForClassMethod) + .enabled(Rule::InvalidFirstArgumentNameForClassMethod) { if let Some(diagnostic) = pep8_naming::rules::invalid_first_argument_name_for_class_method( @@ -367,7 +367,7 @@ where if self .settings .rules - .enabled(&Rule::InvalidFirstArgumentNameForMethod) + .enabled(Rule::InvalidFirstArgumentNameForMethod) { if let Some(diagnostic) = pep8_naming::rules::invalid_first_argument_name_for_method( @@ -383,15 +383,15 @@ where } if self.is_stub { - if self.settings.rules.enabled(&Rule::PassStatementStubBody) { + if self.settings.rules.enabled(Rule::PassStatementStubBody) { flake8_pyi::rules::pass_statement_stub_body(self, body); } - if self.settings.rules.enabled(&Rule::NonEmptyStubBody) { + if self.settings.rules.enabled(Rule::NonEmptyStubBody) { flake8_pyi::rules::non_empty_stub_body(self, body); } } - if self.settings.rules.enabled(&Rule::DunderFunctionName) { + if self.settings.rules.enabled(Rule::DunderFunctionName) { if let Some(diagnostic) = pep8_naming::rules::dunder_function_name( self.ctx.current_scope(), stmt, @@ -402,45 +402,42 @@ where } } - if self.settings.rules.enabled(&Rule::GlobalStatement) { + if self.settings.rules.enabled(Rule::GlobalStatement) { pylint::rules::global_statement(self, name); } - if self - .settings - .rules - .enabled(&Rule::LRUCacheWithoutParameters) + if self.settings.rules.enabled(Rule::LRUCacheWithoutParameters) && self.settings.target_version >= PythonVersion::Py38 { pyupgrade::rules::lru_cache_without_parameters(self, decorator_list); } - if self.settings.rules.enabled(&Rule::FunctoolsCache) + if self.settings.rules.enabled(Rule::FunctoolsCache) && self.settings.target_version >= PythonVersion::Py39 { pyupgrade::rules::functools_cache(self, decorator_list); } - if self.settings.rules.enabled(&Rule::UselessExpression) { + if self.settings.rules.enabled(Rule::UselessExpression) { flake8_bugbear::rules::useless_expression(self, body); } - if self.settings.rules.enabled(&Rule::CachedInstanceMethod) { + if self.settings.rules.enabled(Rule::CachedInstanceMethod) { flake8_bugbear::rules::cached_instance_method(self, decorator_list); } - if self.settings.rules.enabled(&Rule::UnnecessaryReturnNone) - || self.settings.rules.enabled(&Rule::ImplicitReturnValue) - || self.settings.rules.enabled(&Rule::ImplicitReturn) - || self.settings.rules.enabled(&Rule::UnnecessaryAssign) - || self.settings.rules.enabled(&Rule::SuperfluousElseReturn) - || self.settings.rules.enabled(&Rule::SuperfluousElseRaise) - || self.settings.rules.enabled(&Rule::SuperfluousElseContinue) - || self.settings.rules.enabled(&Rule::SuperfluousElseBreak) + if self.settings.rules.enabled(Rule::UnnecessaryReturnNone) + || self.settings.rules.enabled(Rule::ImplicitReturnValue) + || self.settings.rules.enabled(Rule::ImplicitReturn) + || self.settings.rules.enabled(Rule::UnnecessaryAssign) + || self.settings.rules.enabled(Rule::SuperfluousElseReturn) + || self.settings.rules.enabled(Rule::SuperfluousElseRaise) + || self.settings.rules.enabled(Rule::SuperfluousElseContinue) + || self.settings.rules.enabled(Rule::SuperfluousElseBreak) { flake8_return::rules::function(self, body); } - if self.settings.rules.enabled(&Rule::ComplexStructure) { + if self.settings.rules.enabled(Rule::ComplexStructure) { if let Some(diagnostic) = mccabe::rules::function_is_too_complex( stmt, name, @@ -452,20 +449,20 @@ where } } - if self.settings.rules.enabled(&Rule::HardcodedPasswordDefault) { + if self.settings.rules.enabled(Rule::HardcodedPasswordDefault) { self.diagnostics .extend(flake8_bandit::rules::hardcoded_password_default(args)); } - if self.settings.rules.enabled(&Rule::PropertyWithParameters) { + if self.settings.rules.enabled(Rule::PropertyWithParameters) { pylint::rules::property_with_parameters(self, stmt, decorator_list, args); } - if self.settings.rules.enabled(&Rule::TooManyArguments) { + if self.settings.rules.enabled(Rule::TooManyArguments) { pylint::rules::too_many_arguments(self, args, stmt); } - if self.settings.rules.enabled(&Rule::TooManyReturnStatements) { + if self.settings.rules.enabled(Rule::TooManyReturnStatements) { if let Some(diagnostic) = pylint::rules::too_many_return_statements( stmt, body, @@ -476,7 +473,7 @@ where } } - if self.settings.rules.enabled(&Rule::TooManyBranches) { + if self.settings.rules.enabled(Rule::TooManyBranches) { if let Some(diagnostic) = pylint::rules::too_many_branches( stmt, body, @@ -487,7 +484,7 @@ where } } - if self.settings.rules.enabled(&Rule::TooManyStatements) { + if self.settings.rules.enabled(Rule::TooManyStatements) { if let Some(diagnostic) = pylint::rules::too_many_statements( stmt, body, @@ -501,29 +498,29 @@ where if self .settings .rules - .enabled(&Rule::IncorrectFixtureParenthesesStyle) - || self.settings.rules.enabled(&Rule::FixturePositionalArgs) - || self.settings.rules.enabled(&Rule::ExtraneousScopeFunction) + .enabled(Rule::IncorrectFixtureParenthesesStyle) + || self.settings.rules.enabled(Rule::FixturePositionalArgs) + || self.settings.rules.enabled(Rule::ExtraneousScopeFunction) || self .settings .rules - .enabled(&Rule::MissingFixtureNameUnderscore) + .enabled(Rule::MissingFixtureNameUnderscore) || self .settings .rules - .enabled(&Rule::IncorrectFixtureNameUnderscore) - || self.settings.rules.enabled(&Rule::FixtureParamWithoutValue) - || self.settings.rules.enabled(&Rule::DeprecatedYieldFixture) - || self.settings.rules.enabled(&Rule::FixtureFinalizerCallback) - || self.settings.rules.enabled(&Rule::UselessYieldFixture) + .enabled(Rule::IncorrectFixtureNameUnderscore) + || self.settings.rules.enabled(Rule::FixtureParamWithoutValue) + || self.settings.rules.enabled(Rule::DeprecatedYieldFixture) + || self.settings.rules.enabled(Rule::FixtureFinalizerCallback) + || self.settings.rules.enabled(Rule::UselessYieldFixture) || self .settings .rules - .enabled(&Rule::UnnecessaryAsyncioMarkOnFixture) + .enabled(Rule::UnnecessaryAsyncioMarkOnFixture) || self .settings .rules - .enabled(&Rule::ErroneousUseFixturesOnFixture) + .enabled(Rule::ErroneousUseFixturesOnFixture) { flake8_pytest_style::rules::fixture( self, @@ -535,14 +532,11 @@ where ); } - if self - .settings - .rules - .enabled(&Rule::ParametrizeNamesWrongType) + if self.settings.rules.enabled(Rule::ParametrizeNamesWrongType) || self .settings .rules - .enabled(&Rule::ParametrizeValuesWrongType) + .enabled(Rule::ParametrizeValuesWrongType) { flake8_pytest_style::rules::parametrize(self, decorator_list); } @@ -550,11 +544,11 @@ where if self .settings .rules - .enabled(&Rule::IncorrectMarkParenthesesStyle) + .enabled(Rule::IncorrectMarkParenthesesStyle) || self .settings .rules - .enabled(&Rule::UseFixturesWithoutParameters) + .enabled(Rule::UseFixturesWithoutParameters) { flake8_pytest_style::rules::marks(self, decorator_list); } @@ -562,7 +556,7 @@ where if self .settings .rules - .enabled(&Rule::BooleanPositionalArgInFunctionDefinition) + .enabled(Rule::BooleanPositionalArgInFunctionDefinition) { flake8_boolean_trap::rules::check_positional_boolean_in_def( self, @@ -575,7 +569,7 @@ where if self .settings .rules - .enabled(&Rule::BooleanDefaultValueInFunctionDefinition) + .enabled(Rule::BooleanDefaultValueInFunctionDefinition) { flake8_boolean_trap::rules::check_boolean_default_value_in_function_definition( self, @@ -676,10 +670,10 @@ where ); } StmtKind::Return { .. } => { - if self.settings.rules.enabled(&Rule::ReturnOutsideFunction) { + if self.settings.rules.enabled(Rule::ReturnOutsideFunction) { pyflakes::rules::return_outside_function(self, stmt); } - if self.settings.rules.enabled(&Rule::ReturnInInit) { + if self.settings.rules.enabled(Rule::ReturnInInit) { pylint::rules::return_in_init(self, stmt); } } @@ -690,42 +684,42 @@ where decorator_list, body, } => { - if self.settings.rules.enabled(&Rule::NullableModelStringField) { + if self.settings.rules.enabled(Rule::NullableModelStringField) { self.diagnostics .extend(flake8_django::rules::nullable_model_string_field( self, body, )); } - if self.settings.rules.enabled(&Rule::ExcludeWithModelForm) { + if self.settings.rules.enabled(Rule::ExcludeWithModelForm) { if let Some(diagnostic) = flake8_django::rules::exclude_with_model_form(self, bases, body) { self.diagnostics.push(diagnostic); } } - if self.settings.rules.enabled(&Rule::AllWithModelForm) { + if self.settings.rules.enabled(Rule::AllWithModelForm) { if let Some(diagnostic) = flake8_django::rules::all_with_model_form(self, bases, body) { self.diagnostics.push(diagnostic); } } - if self.settings.rules.enabled(&Rule::ModelWithoutDunderStr) { + if self.settings.rules.enabled(Rule::ModelWithoutDunderStr) { if let Some(diagnostic) = flake8_django::rules::model_without_dunder_str(self, bases, body, stmt) { self.diagnostics.push(diagnostic); } } - if self.settings.rules.enabled(&Rule::GlobalStatement) { + if self.settings.rules.enabled(Rule::GlobalStatement) { pylint::rules::global_statement(self, name); } - if self.settings.rules.enabled(&Rule::UselessObjectInheritance) { + if self.settings.rules.enabled(Rule::UselessObjectInheritance) { pyupgrade::rules::useless_object_inheritance(self, stmt, name, bases, keywords); } - if self.settings.rules.enabled(&Rule::AmbiguousClassName) { + if self.settings.rules.enabled(Rule::AmbiguousClassName) { if let Some(diagnostic) = pycodestyle::rules::ambiguous_class_name(name, || { helpers::identifier_range(stmt, self.locator) }) { @@ -733,7 +727,7 @@ where } } - if self.settings.rules.enabled(&Rule::InvalidClassName) { + if self.settings.rules.enabled(Rule::InvalidClassName) { if let Some(diagnostic) = pep8_naming::rules::invalid_class_name(stmt, name, self.locator) { @@ -744,7 +738,7 @@ where if self .settings .rules - .enabled(&Rule::ErrorSuffixOnExceptionName) + .enabled(Rule::ErrorSuffixOnExceptionName) { if let Some(diagnostic) = pep8_naming::rules::error_suffix_on_exception_name( stmt, @@ -756,7 +750,7 @@ where } } - if self.settings.rules.enabled(&Rule::UselessExpression) { + if self.settings.rules.enabled(Rule::UselessExpression) { flake8_bugbear::rules::useless_expression(self, body); } @@ -764,11 +758,11 @@ where if self .settings .rules - .enabled(&Rule::AbstractBaseClassWithoutAbstractMethod) + .enabled(Rule::AbstractBaseClassWithoutAbstractMethod) || self .settings .rules - .enabled(&Rule::EmptyMethodWithoutAbstractDecorator) + .enabled(Rule::EmptyMethodWithoutAbstractDecorator) { flake8_bugbear::rules::abstract_base_class( self, stmt, name, bases, keywords, body, @@ -776,7 +770,7 @@ where } } if self.is_stub { - if self.settings.rules.enabled(&Rule::PassStatementStubBody) { + if self.settings.rules.enabled(Rule::PassStatementStubBody) { flake8_pyi::rules::pass_statement_stub_body(self, body); } } @@ -784,20 +778,16 @@ where if self .settings .rules - .enabled(&Rule::IncorrectMarkParenthesesStyle) + .enabled(Rule::IncorrectMarkParenthesesStyle) { flake8_pytest_style::rules::marks(self, decorator_list); } - if self - .settings - .rules - .enabled(&Rule::DupeClassFieldDefinitions) - { + if self.settings.rules.enabled(Rule::DupeClassFieldDefinitions) { flake8_pie::rules::dupe_class_field_definitions(self, stmt, body); } - if self.settings.rules.enabled(&Rule::PreferUniqueEnums) { + if self.settings.rules.enabled(Rule::PreferUniqueEnums) { flake8_pie::rules::prefer_unique_enums(self, stmt, body); } @@ -814,18 +804,18 @@ where } } StmtKind::Import { names } => { - if self.settings.rules.enabled(&Rule::MultipleImportsOnOneLine) { + if self.settings.rules.enabled(Rule::MultipleImportsOnOneLine) { pycodestyle::rules::multiple_imports_on_one_line(self, stmt, names); } if self .settings .rules - .enabled(&Rule::ModuleImportNotAtTopOfFile) + .enabled(Rule::ModuleImportNotAtTopOfFile) { pycodestyle::rules::module_import_not_at_top_of_file(self, stmt); } - if self.settings.rules.enabled(&Rule::GlobalStatement) { + if self.settings.rules.enabled(Rule::GlobalStatement) { for name in names.iter() { if let Some(asname) = name.node.asname.as_ref() { pylint::rules::global_statement(self, asname); @@ -835,10 +825,10 @@ where } } - if self.settings.rules.enabled(&Rule::RewriteCElementTree) { + if self.settings.rules.enabled(Rule::RewriteCElementTree) { pyupgrade::rules::replace_c_element_tree(self, stmt); } - if self.settings.rules.enabled(&Rule::RewriteMockImport) { + if self.settings.rules.enabled(Rule::RewriteMockImport) { pyupgrade::rules::rewrite_mock_import(self, stmt); } @@ -875,7 +865,7 @@ where }, ); - if self.settings.rules.enabled(&Rule::LateFutureImport) + if self.settings.rules.enabled(Rule::LateFutureImport) && !self.ctx.futures_allowed { self.diagnostics.push(Diagnostic::new( @@ -957,7 +947,7 @@ where } // flake8-debugger - if self.settings.rules.enabled(&Rule::Debugger) { + if self.settings.rules.enabled(Rule::Debugger) { if let Some(diagnostic) = flake8_debugger::rules::debugger_import(stmt, None, &alias.node.name) { @@ -966,7 +956,7 @@ where } // flake8_tidy_imports - if self.settings.rules.enabled(&Rule::BannedApi) { + if self.settings.rules.enabled(Rule::BannedApi) { if let Some(diagnostic) = flake8_tidy_imports::banned_api::name_or_parent_is_banned( alias, @@ -979,10 +969,10 @@ where } // pylint - if self.settings.rules.enabled(&Rule::UselessImportAlias) { + if self.settings.rules.enabled(Rule::UselessImportAlias) { pylint::rules::useless_import_alias(self, alias); } - if self.settings.rules.enabled(&Rule::ConsiderUsingFromImport) { + if self.settings.rules.enabled(Rule::ConsiderUsingFromImport) { pylint::rules::use_from_import(self, stmt, alias, names); } @@ -991,7 +981,7 @@ where if self .settings .rules - .enabled(&Rule::ConstantImportedAsNonConstant) + .enabled(Rule::ConstantImportedAsNonConstant) { if let Some(diagnostic) = pep8_naming::rules::constant_imported_as_non_constant( @@ -1008,7 +998,7 @@ where if self .settings .rules - .enabled(&Rule::LowercaseImportedAsNonLowercase) + .enabled(Rule::LowercaseImportedAsNonLowercase) { if let Some(diagnostic) = pep8_naming::rules::lowercase_imported_as_non_lowercase( @@ -1025,7 +1015,7 @@ where if self .settings .rules - .enabled(&Rule::CamelcaseImportedAsLowercase) + .enabled(Rule::CamelcaseImportedAsLowercase) { if let Some(diagnostic) = pep8_naming::rules::camelcase_imported_as_lowercase( @@ -1042,7 +1032,7 @@ where if self .settings .rules - .enabled(&Rule::CamelcaseImportedAsConstant) + .enabled(Rule::CamelcaseImportedAsConstant) { if let Some(diagnostic) = pep8_naming::rules::camelcase_imported_as_constant( @@ -1059,7 +1049,7 @@ where if self .settings .rules - .enabled(&Rule::CamelcaseImportedAsAcronym) + .enabled(Rule::CamelcaseImportedAsAcronym) { if let Some(diagnostic) = pep8_naming::rules::camelcase_imported_as_acronym( @@ -1074,11 +1064,7 @@ where } } - if self - .settings - .rules - .enabled(&Rule::UnconventionalImportAlias) - { + if self.settings.rules.enabled(Rule::UnconventionalImportAlias) { if let Some(diagnostic) = flake8_import_conventions::rules::check_conventional_import( stmt, @@ -1091,7 +1077,7 @@ where } } - if self.settings.rules.enabled(&Rule::IncorrectPytestImport) { + if self.settings.rules.enabled(Rule::IncorrectPytestImport) { if let Some(diagnostic) = flake8_pytest_style::rules::import( stmt, &alias.node.name, @@ -1110,12 +1096,12 @@ where if self .settings .rules - .enabled(&Rule::ModuleImportNotAtTopOfFile) + .enabled(Rule::ModuleImportNotAtTopOfFile) { pycodestyle::rules::module_import_not_at_top_of_file(self, stmt); } - if self.settings.rules.enabled(&Rule::GlobalStatement) { + if self.settings.rules.enabled(Rule::GlobalStatement) { for name in names.iter() { if let Some(asname) = name.node.asname.as_ref() { pylint::rules::global_statement(self, asname); @@ -1125,20 +1111,20 @@ where } } - if self.settings.rules.enabled(&Rule::UnnecessaryFutureImport) + if self.settings.rules.enabled(Rule::UnnecessaryFutureImport) && self.settings.target_version >= PythonVersion::Py37 { if let Some("__future__") = module.as_deref() { pyupgrade::rules::unnecessary_future_import(self, stmt, names); } } - if self.settings.rules.enabled(&Rule::RewriteMockImport) { + if self.settings.rules.enabled(Rule::RewriteMockImport) { pyupgrade::rules::rewrite_mock_import(self, stmt); } - if self.settings.rules.enabled(&Rule::RewriteCElementTree) { + if self.settings.rules.enabled(Rule::RewriteCElementTree) { pyupgrade::rules::replace_c_element_tree(self, stmt); } - if self.settings.rules.enabled(&Rule::DeprecatedImport) { + if self.settings.rules.enabled(Rule::DeprecatedImport) { pyupgrade::rules::deprecated_import( self, stmt, @@ -1147,13 +1133,13 @@ where level.as_ref(), ); } - if self.settings.rules.enabled(&Rule::UnnecessaryBuiltinImport) { + if self.settings.rules.enabled(Rule::UnnecessaryBuiltinImport) { if let Some(module) = module.as_deref() { pyupgrade::rules::unnecessary_builtin_import(self, stmt, module, names); } } - if self.settings.rules.enabled(&Rule::BannedApi) { + if self.settings.rules.enabled(Rule::BannedApi) { if let Some(module) = module { for name in names { if let Some(diagnostic) = @@ -1178,7 +1164,7 @@ where } } - if self.settings.rules.enabled(&Rule::IncorrectPytestImport) { + if self.settings.rules.enabled(Rule::IncorrectPytestImport) { if let Some(diagnostic) = flake8_pytest_style::rules::import_from( stmt, module.as_deref(), @@ -1225,11 +1211,11 @@ where self.ctx.annotations_future_enabled = true; } - if self.settings.rules.enabled(&Rule::FutureFeatureNotDefined) { + if self.settings.rules.enabled(Rule::FutureFeatureNotDefined) { pyflakes::rules::future_feature_not_defined(self, alias); } - if self.settings.rules.enabled(&Rule::LateFutureImport) + if self.settings.rules.enabled(Rule::LateFutureImport) && !self.ctx.futures_allowed { self.diagnostics.push(Diagnostic::new( @@ -1263,7 +1249,7 @@ where }, ); - if self.settings.rules.enabled(&Rule::ImportStarNotPermitted) { + if self.settings.rules.enabled(Rule::ImportStarNotPermitted) { let scope = &self.ctx.scopes [*(self.ctx.scope_stack.last().expect("No current scope found"))]; if !matches!(scope.kind, ScopeKind::Module) { @@ -1279,7 +1265,7 @@ where } } - if self.settings.rules.enabled(&Rule::ImportStar) { + if self.settings.rules.enabled(Rule::ImportStar) { self.diagnostics.push(Diagnostic::new( pyflakes::rules::ImportStar { name: helpers::format_import_from( @@ -1343,7 +1329,7 @@ where ); } - if self.settings.rules.enabled(&Rule::RelativeImports) { + if self.settings.rules.enabled(Rule::RelativeImports) { if let Some(diagnostic) = flake8_tidy_imports::relative_imports::banned_relative_import( self, @@ -1359,7 +1345,7 @@ where } // flake8-debugger - if self.settings.rules.enabled(&Rule::Debugger) { + if self.settings.rules.enabled(Rule::Debugger) { if let Some(diagnostic) = flake8_debugger::rules::debugger_import( stmt, module.as_deref(), @@ -1369,11 +1355,7 @@ where } } - if self - .settings - .rules - .enabled(&Rule::UnconventionalImportAlias) - { + if self.settings.rules.enabled(Rule::UnconventionalImportAlias) { let full_name = helpers::format_import_from_member( level.as_ref(), module.as_deref(), @@ -1395,7 +1377,7 @@ where if self .settings .rules - .enabled(&Rule::ConstantImportedAsNonConstant) + .enabled(Rule::ConstantImportedAsNonConstant) { if let Some(diagnostic) = pep8_naming::rules::constant_imported_as_non_constant( @@ -1412,7 +1394,7 @@ where if self .settings .rules - .enabled(&Rule::LowercaseImportedAsNonLowercase) + .enabled(Rule::LowercaseImportedAsNonLowercase) { if let Some(diagnostic) = pep8_naming::rules::lowercase_imported_as_non_lowercase( @@ -1429,7 +1411,7 @@ where if self .settings .rules - .enabled(&Rule::CamelcaseImportedAsLowercase) + .enabled(Rule::CamelcaseImportedAsLowercase) { if let Some(diagnostic) = pep8_naming::rules::camelcase_imported_as_lowercase( @@ -1446,7 +1428,7 @@ where if self .settings .rules - .enabled(&Rule::CamelcaseImportedAsConstant) + .enabled(Rule::CamelcaseImportedAsConstant) { if let Some(diagnostic) = pep8_naming::rules::camelcase_imported_as_constant( @@ -1463,7 +1445,7 @@ where if self .settings .rules - .enabled(&Rule::CamelcaseImportedAsAcronym) + .enabled(Rule::CamelcaseImportedAsAcronym) { if let Some(diagnostic) = pep8_naming::rules::camelcase_imported_as_acronym( @@ -1478,42 +1460,42 @@ where } // pylint - if self.settings.rules.enabled(&Rule::UselessImportAlias) { + if self.settings.rules.enabled(Rule::UselessImportAlias) { pylint::rules::useless_import_alias(self, alias); } } } } StmtKind::Raise { exc, .. } => { - if self.settings.rules.enabled(&Rule::RaiseNotImplemented) { + if self.settings.rules.enabled(Rule::RaiseNotImplemented) { if let Some(expr) = exc { pyflakes::rules::raise_not_implemented(self, expr); } } - if self.settings.rules.enabled(&Rule::CannotRaiseLiteral) { + if self.settings.rules.enabled(Rule::CannotRaiseLiteral) { if let Some(exc) = exc { flake8_bugbear::rules::cannot_raise_literal(self, exc); } } - if self.settings.rules.enabled(&Rule::RawStringInException) - || self.settings.rules.enabled(&Rule::FStringInException) - || self.settings.rules.enabled(&Rule::DotFormatInException) + if self.settings.rules.enabled(Rule::RawStringInException) + || self.settings.rules.enabled(Rule::FStringInException) + || self.settings.rules.enabled(Rule::DotFormatInException) { if let Some(exc) = exc { flake8_errmsg::rules::string_in_exception(self, exc); } } - if self.settings.rules.enabled(&Rule::OSErrorAlias) { + if self.settings.rules.enabled(Rule::OSErrorAlias) { if let Some(item) = exc { pyupgrade::rules::os_error_alias_raise(self, item); } } - if self.settings.rules.enabled(&Rule::RaiseVanillaClass) { + if self.settings.rules.enabled(Rule::RaiseVanillaClass) { if let Some(expr) = exc { tryceratops::rules::raise_vanilla_class(self, expr); } } - if self.settings.rules.enabled(&Rule::RaiseVanillaArgs) { + if self.settings.rules.enabled(Rule::RaiseVanillaArgs) { if let Some(expr) = exc { tryceratops::rules::raise_vanilla_args(self, expr); } @@ -1521,7 +1503,7 @@ where if self .settings .rules - .enabled(&Rule::UnnecessaryParenOnRaiseException) + .enabled(Rule::UnnecessaryParenOnRaiseException) { if let Some(expr) = exc { flake8_raise::rules::unnecessary_paren_on_raise_exception(self, expr); @@ -1531,17 +1513,17 @@ where StmtKind::AugAssign { target, .. } => { self.handle_node_load(target); - if self.settings.rules.enabled(&Rule::GlobalStatement) { + if self.settings.rules.enabled(Rule::GlobalStatement) { if let ExprKind::Name { id, .. } = &target.node { pylint::rules::global_statement(self, id); } } } StmtKind::If { test, body, orelse } => { - if self.settings.rules.enabled(&Rule::IfTuple) { + if self.settings.rules.enabled(Rule::IfTuple) { pyflakes::rules::if_tuple(self, stmt, test); } - if self.settings.rules.enabled(&Rule::CollapsibleIf) { + if self.settings.rules.enabled(Rule::CollapsibleIf) { flake8_simplify::rules::nested_if_statements( self, stmt, @@ -1551,17 +1533,17 @@ where self.ctx.current_stmt_parent().map(Into::into), ); } - if self.settings.rules.enabled(&Rule::IfWithSameArms) { + if self.settings.rules.enabled(Rule::IfWithSameArms) { flake8_simplify::rules::if_with_same_arms( self, stmt, self.ctx.current_stmt_parent().map(Into::into), ); } - if self.settings.rules.enabled(&Rule::NeedlessBool) { + if self.settings.rules.enabled(Rule::NeedlessBool) { flake8_simplify::rules::needless_bool(self, stmt); } - if self.settings.rules.enabled(&Rule::ManualDictLookup) { + if self.settings.rules.enabled(Rule::ManualDictLookup) { flake8_simplify::rules::manual_dict_lookup( self, stmt, @@ -1571,14 +1553,14 @@ where self.ctx.current_stmt_parent().map(Into::into), ); } - if self.settings.rules.enabled(&Rule::UseTernaryOperator) { + if self.settings.rules.enabled(Rule::UseTernaryOperator) { flake8_simplify::rules::use_ternary_operator( self, stmt, self.ctx.current_stmt_parent().map(Into::into), ); } - if self.settings.rules.enabled(&Rule::DictGetWithDefault) { + if self.settings.rules.enabled(Rule::DictGetWithDefault) { flake8_simplify::rules::use_dict_get_with_default( self, stmt, @@ -1588,7 +1570,7 @@ where self.ctx.current_stmt_parent().map(Into::into), ); } - if self.settings.rules.enabled(&Rule::PreferTypeError) { + if self.settings.rules.enabled(Rule::PreferTypeError) { tryceratops::rules::prefer_type_error( self, body, @@ -1597,10 +1579,10 @@ where self.ctx.current_stmt_parent().map(Into::into), ); } - if self.settings.rules.enabled(&Rule::OutdatedVersionBlock) { + if self.settings.rules.enabled(Rule::OutdatedVersionBlock) { pyupgrade::rules::outdated_version_block(self, stmt, test, body, orelse); } - if self.settings.rules.enabled(&Rule::CollapsibleElseIf) { + if self.settings.rules.enabled(Rule::CollapsibleElseIf) { if let Some(diagnostic) = pylint::rules::collapsible_else_if(orelse, self.locator) { @@ -1609,22 +1591,22 @@ where } } StmtKind::Assert { test, msg } => { - if self.settings.rules.enabled(&Rule::AssertTuple) { + if self.settings.rules.enabled(Rule::AssertTuple) { pyflakes::rules::assert_tuple(self, stmt, test); } - if self.settings.rules.enabled(&Rule::AssertFalse) { + if self.settings.rules.enabled(Rule::AssertFalse) { flake8_bugbear::rules::assert_false(self, stmt, test, msg.as_deref()); } - if self.settings.rules.enabled(&Rule::Assert) { + if self.settings.rules.enabled(Rule::Assert) { self.diagnostics .push(flake8_bandit::rules::assert_used(stmt)); } - if self.settings.rules.enabled(&Rule::AssertAlwaysFalse) { + if self.settings.rules.enabled(Rule::AssertAlwaysFalse) { if let Some(diagnostic) = flake8_pytest_style::rules::assert_falsy(stmt, test) { self.diagnostics.push(diagnostic); } } - if self.settings.rules.enabled(&Rule::CompositeAssertion) { + if self.settings.rules.enabled(Rule::CompositeAssertion) { flake8_pytest_style::rules::composite_condition( self, stmt, @@ -1634,17 +1616,17 @@ where } } StmtKind::With { items, body, .. } => { - if self.settings.rules.enabled(&Rule::AssertRaisesException) { + if self.settings.rules.enabled(Rule::AssertRaisesException) { flake8_bugbear::rules::assert_raises_exception(self, stmt, items); } if self .settings .rules - .enabled(&Rule::RaisesWithMultipleStatements) + .enabled(Rule::RaisesWithMultipleStatements) { flake8_pytest_style::rules::complex_raises(self, stmt, items, body); } - if self.settings.rules.enabled(&Rule::MultipleWithStatements) { + if self.settings.rules.enabled(Rule::MultipleWithStatements) { flake8_simplify::rules::multiple_with_statements( self, stmt, @@ -1652,15 +1634,15 @@ where self.ctx.current_stmt_parent().map(Into::into), ); } - if self.settings.rules.enabled(&Rule::RedefinedLoopName) { + if self.settings.rules.enabled(Rule::RedefinedLoopName) { pylint::rules::redefined_loop_name(self, &Node::Stmt(stmt)); } } StmtKind::While { body, orelse, .. } => { - if self.settings.rules.enabled(&Rule::FunctionUsesLoopVariable) { + if self.settings.rules.enabled(Rule::FunctionUsesLoopVariable) { flake8_bugbear::rules::function_uses_loop_variable(self, &Node::Stmt(stmt)); } - if self.settings.rules.enabled(&Rule::UselessElseOnLoop) { + if self.settings.rules.enabled(Rule::UselessElseOnLoop) { pylint::rules::useless_else_on_loop(self, stmt, body, orelse); } } @@ -1678,11 +1660,7 @@ where orelse, .. } => { - if self - .settings - .rules - .enabled(&Rule::UnusedLoopControlVariable) - { + if self.settings.rules.enabled(Rule::UnusedLoopControlVariable) { self.deferred.for_loops.push(( stmt, (self.ctx.scope_stack.clone(), self.ctx.parents.clone()), @@ -1691,28 +1669,28 @@ where if self .settings .rules - .enabled(&Rule::LoopVariableOverridesIterator) + .enabled(Rule::LoopVariableOverridesIterator) { flake8_bugbear::rules::loop_variable_overrides_iterator(self, target, iter); } - if self.settings.rules.enabled(&Rule::FunctionUsesLoopVariable) { + if self.settings.rules.enabled(Rule::FunctionUsesLoopVariable) { flake8_bugbear::rules::function_uses_loop_variable(self, &Node::Stmt(stmt)); } - if self.settings.rules.enabled(&Rule::UselessElseOnLoop) { + if self.settings.rules.enabled(Rule::UselessElseOnLoop) { pylint::rules::useless_else_on_loop(self, stmt, body, orelse); } - if self.settings.rules.enabled(&Rule::RedefinedLoopName) { + if self.settings.rules.enabled(Rule::RedefinedLoopName) { pylint::rules::redefined_loop_name(self, &Node::Stmt(stmt)); } if matches!(stmt.node, StmtKind::For { .. }) { - if self.settings.rules.enabled(&Rule::ReimplementedBuiltin) { + if self.settings.rules.enabled(Rule::ReimplementedBuiltin) { flake8_simplify::rules::convert_for_loop_to_any_all( self, stmt, self.ctx.current_sibling_stmt(), ); } - if self.settings.rules.enabled(&Rule::KeyInDict) { + if self.settings.rules.enabled(Rule::KeyInDict) { flake8_simplify::rules::key_in_dict_for(self, target, iter); } } @@ -1731,77 +1709,74 @@ where finalbody, .. } => { - if self.settings.rules.enabled(&Rule::DefaultExceptNotLast) { + if self.settings.rules.enabled(Rule::DefaultExceptNotLast) { if let Some(diagnostic) = pyflakes::rules::default_except_not_last(handlers, self.locator) { self.diagnostics.push(diagnostic); } } - if self - .settings - .rules - .enabled(&Rule::DuplicateHandlerException) + if self.settings.rules.enabled(Rule::DuplicateHandlerException) || self .settings .rules - .enabled(&Rule::DuplicateTryBlockException) + .enabled(Rule::DuplicateTryBlockException) { flake8_bugbear::rules::duplicate_exceptions(self, handlers); } if self .settings .rules - .enabled(&Rule::RedundantTupleInExceptionHandler) + .enabled(Rule::RedundantTupleInExceptionHandler) { flake8_bugbear::rules::redundant_tuple_in_exception_handler(self, handlers); } - if self.settings.rules.enabled(&Rule::OSErrorAlias) { + if self.settings.rules.enabled(Rule::OSErrorAlias) { pyupgrade::rules::os_error_alias_handlers(self, handlers); } - if self.settings.rules.enabled(&Rule::AssertInExcept) { + if self.settings.rules.enabled(Rule::AssertInExcept) { self.diagnostics.extend( flake8_pytest_style::rules::assert_in_exception_handler(handlers), ); } - if self.settings.rules.enabled(&Rule::UseContextlibSuppress) { + if self.settings.rules.enabled(Rule::UseContextlibSuppress) { flake8_simplify::rules::use_contextlib_suppress( self, stmt, body, handlers, orelse, finalbody, ); } - if self.settings.rules.enabled(&Rule::ReturnInTryExceptFinally) { + if self.settings.rules.enabled(Rule::ReturnInTryExceptFinally) { flake8_simplify::rules::return_in_try_except_finally( self, body, handlers, finalbody, ); } - if self.settings.rules.enabled(&Rule::TryConsiderElse) { + if self.settings.rules.enabled(Rule::TryConsiderElse) { tryceratops::rules::try_consider_else(self, body, orelse); } - if self.settings.rules.enabled(&Rule::VerboseRaise) { + if self.settings.rules.enabled(Rule::VerboseRaise) { tryceratops::rules::verbose_raise(self, handlers); } - if self.settings.rules.enabled(&Rule::VerboseLogMessage) { + if self.settings.rules.enabled(Rule::VerboseLogMessage) { tryceratops::rules::verbose_log_message(self, handlers); } - if self.settings.rules.enabled(&Rule::RaiseWithinTry) { + if self.settings.rules.enabled(Rule::RaiseWithinTry) { tryceratops::rules::raise_within_try(self, body); } - if self.settings.rules.enabled(&Rule::ErrorInsteadOfException) { + if self.settings.rules.enabled(Rule::ErrorInsteadOfException) { tryceratops::rules::error_instead_of_exception(self, handlers); } } StmtKind::Assign { targets, value, .. } => { - if self.settings.rules.enabled(&Rule::LambdaAssignment) { + if self.settings.rules.enabled(Rule::LambdaAssignment) { if let [target] = &targets[..] { pycodestyle::rules::lambda_assignment(self, target, value, stmt); } } - if self.settings.rules.enabled(&Rule::AssignmentToOsEnviron) { + if self.settings.rules.enabled(Rule::AssignmentToOsEnviron) { flake8_bugbear::rules::assignment_to_os_environ(self, targets); } - if self.settings.rules.enabled(&Rule::HardcodedPasswordString) { + if self.settings.rules.enabled(Rule::HardcodedPasswordString) { if let Some(diagnostic) = flake8_bandit::rules::assign_hardcoded_password_string(value, targets) { @@ -1810,12 +1785,12 @@ where } if self.is_stub { - if self.settings.rules.enabled(&Rule::PrefixTypeParams) { + if self.settings.rules.enabled(Rule::PrefixTypeParams) { flake8_pyi::rules::prefix_type_params(self, value, targets); } } - if self.settings.rules.enabled(&Rule::GlobalStatement) { + if self.settings.rules.enabled(Rule::GlobalStatement) { for target in targets.iter() { if let ExprKind::Name { id, .. } = &target.node { pylint::rules::global_statement(self, id); @@ -1823,13 +1798,13 @@ where } } - if self.settings.rules.enabled(&Rule::UselessMetaclassType) { + if self.settings.rules.enabled(Rule::UselessMetaclassType) { pyupgrade::rules::useless_metaclass_type(self, stmt, value, targets); } if self .settings .rules - .enabled(&Rule::ConvertTypedDictFunctionalToClass) + .enabled(Rule::ConvertTypedDictFunctionalToClass) { pyupgrade::rules::convert_typed_dict_functional_to_class( self, stmt, targets, value, @@ -1838,24 +1813,24 @@ where if self .settings .rules - .enabled(&Rule::ConvertNamedTupleFunctionalToClass) + .enabled(Rule::ConvertNamedTupleFunctionalToClass) { pyupgrade::rules::convert_named_tuple_functional_to_class( self, stmt, targets, value, ); } - if self.settings.rules.enabled(&Rule::RewriteListComprehension) { + if self.settings.rules.enabled(Rule::RewriteListComprehension) { pyupgrade::rules::unpack_list_comprehension(self, targets, value); } - if self.settings.rules.enabled(&Rule::DfIsABadVariableName) { + if self.settings.rules.enabled(Rule::DfIsABadVariableName) { if let Some(diagnostic) = pandas_vet::rules::assignment_to_df(targets) { self.diagnostics.push(diagnostic); } } } StmtKind::AnnAssign { target, value, .. } => { - if self.settings.rules.enabled(&Rule::LambdaAssignment) { + if self.settings.rules.enabled(Rule::LambdaAssignment) { if let Some(value) = value { pycodestyle::rules::lambda_assignment(self, target, value, stmt); } @@ -1863,7 +1838,7 @@ where if self .settings .rules - .enabled(&Rule::UnintentionalTypeAnnotation) + .enabled(Rule::UnintentionalTypeAnnotation) { flake8_bugbear::rules::unintentional_type_annotation( self, @@ -1874,7 +1849,7 @@ where } } StmtKind::Delete { targets } => { - if self.settings.rules.enabled(&Rule::GlobalStatement) { + if self.settings.rules.enabled(Rule::GlobalStatement) { for target in targets.iter() { if let ExprKind::Name { id, .. } = &target.node { pylint::rules::global_statement(self, id); @@ -1883,17 +1858,17 @@ where } } StmtKind::Expr { value, .. } => { - if self.settings.rules.enabled(&Rule::UselessComparison) { + if self.settings.rules.enabled(Rule::UselessComparison) { flake8_bugbear::rules::useless_comparison(self, value); } if self .settings .rules - .enabled(&Rule::UseCapitalEnvironmentVariables) + .enabled(Rule::UseCapitalEnvironmentVariables) { flake8_simplify::rules::use_capital_environment_variables(self, value); } - if self.settings.rules.enabled(&Rule::AsyncioDanglingTask) { + if self.settings.rules.enabled(Rule::AsyncioDanglingTask) { if let Some(diagnostic) = ruff::rules::asyncio_dangling_task(value, |expr| { self.ctx.resolve_call_path(expr) }) { @@ -1922,7 +1897,7 @@ where decorator_list, .. } => { - if self.settings.rules.enabled(&Rule::FStringDocstring) { + if self.settings.rules.enabled(Rule::FStringDocstring) { flake8_bugbear::rules::f_string_docstring(self, body); } let definition = docstrings::extraction::extract( @@ -1931,7 +1906,7 @@ where body, &Documentable::Function, ); - if self.settings.rules.enabled(&Rule::RewriteYieldFrom) { + if self.settings.rules.enabled(Rule::RewriteYieldFrom) { pyupgrade::rules::rewrite_yield_from(self, stmt); } let scope = @@ -1991,7 +1966,7 @@ where decorator_list, .. } => { - if self.settings.rules.enabled(&Rule::FStringDocstring) { + if self.settings.rules.enabled(Rule::FStringDocstring) { flake8_bugbear::rules::f_string_docstring(self, body); } let definition = docstrings::extraction::extract( @@ -2066,7 +2041,7 @@ where } self.ctx.handled_exceptions.push(handled_exceptions); - if self.settings.rules.enabled(&Rule::JumpStatementInFinally) { + if self.settings.rules.enabled(Rule::JumpStatementInFinally) { flake8_bugbear::rules::jump_statement_in_finally(self, finalbody); } self.visit_body(body); @@ -2133,7 +2108,7 @@ where self.visit_expr(test); if flake8_type_checking::helpers::is_type_checking_block(&self.ctx, test) { - if self.settings.rules.enabled(&Rule::EmptyTypeCheckingBlock) { + if self.settings.rules.enabled(Rule::EmptyTypeCheckingBlock) { flake8_type_checking::rules::empty_type_checking_block(self, stmt, body); } @@ -2222,7 +2197,7 @@ where if self.ctx.in_type_definition && !self.ctx.in_deferred_string_type_definition && !self.settings.pyupgrade.keep_runtime_typing - && self.settings.rules.enabled(&Rule::TypingUnion) + && self.settings.rules.enabled(Rule::TypingUnion) && (self.settings.target_version >= PythonVersion::Py310 || (self.settings.target_version >= PythonVersion::Py37 && self.ctx.annotations_future_enabled @@ -2238,13 +2213,13 @@ where if self .settings .rules - .enabled(&Rule::SysVersionSlice3Referenced) - || self.settings.rules.enabled(&Rule::SysVersion2Referenced) - || self.settings.rules.enabled(&Rule::SysVersion0Referenced) + .enabled(Rule::SysVersionSlice3Referenced) + || self.settings.rules.enabled(Rule::SysVersion2Referenced) + || self.settings.rules.enabled(Rule::SysVersion0Referenced) || self .settings .rules - .enabled(&Rule::SysVersionSlice1Referenced) + .enabled(Rule::SysVersionSlice1Referenced) { flake8_2020::rules::subscript(self, value, slice); } @@ -2254,9 +2229,9 @@ where let check_too_many_expressions = self .settings .rules - .enabled(&Rule::ExpressionsInStarAssignment); + .enabled(Rule::ExpressionsInStarAssignment); let check_two_starred_expressions = - self.settings.rules.enabled(&Rule::TwoStarredExpressions); + self.settings.rules.enabled(Rule::TwoStarredExpressions); if let Some(diagnostic) = pyflakes::rules::starred_expressions( elts, check_too_many_expressions, @@ -2270,17 +2245,17 @@ where ExprKind::Name { id, ctx } => { match ctx { ExprContext::Load => { - if self.settings.rules.enabled(&Rule::TypingTextStrAlias) { + if self.settings.rules.enabled(Rule::TypingTextStrAlias) { pyupgrade::rules::typing_text_str_alias(self, expr); } - if self.settings.rules.enabled(&Rule::NumpyDeprecatedTypeAlias) { + if self.settings.rules.enabled(Rule::NumpyDeprecatedTypeAlias) { numpy::rules::deprecated_type_alias(self, expr); } // Ex) List[...] if !self.ctx.in_deferred_string_type_definition && !self.settings.pyupgrade.keep_runtime_typing - && self.settings.rules.enabled(&Rule::DeprecatedCollectionType) + && self.settings.rules.enabled(Rule::DeprecatedCollectionType) && (self.settings.target_version >= PythonVersion::Py39 || (self.settings.target_version >= PythonVersion::Py37 && self.ctx.annotations_future_enabled @@ -2295,7 +2270,7 @@ where self.handle_node_load(expr); } ExprContext::Store => { - if self.settings.rules.enabled(&Rule::AmbiguousVariableName) { + if self.settings.rules.enabled(Rule::AmbiguousVariableName) { if let Some(diagnostic) = pycodestyle::rules::ambiguous_variable_name(id, Range::from(expr)) { @@ -2310,14 +2285,14 @@ where ExprContext::Del => self.handle_node_delete(expr), } - if self.settings.rules.enabled(&Rule::SixPY3Referenced) { + if self.settings.rules.enabled(Rule::SixPY3Referenced) { flake8_2020::rules::name_or_attribute(self, expr); } if self .settings .rules - .enabled(&Rule::UsedPriorGlobalDeclaration) + .enabled(Rule::UsedPriorGlobalDeclaration) { pylint::rules::used_prior_global_declaration(self, id, expr); } @@ -2326,7 +2301,7 @@ where // Ex) typing.List[...] if !self.ctx.in_deferred_string_type_definition && !self.settings.pyupgrade.keep_runtime_typing - && self.settings.rules.enabled(&Rule::DeprecatedCollectionType) + && self.settings.rules.enabled(Rule::DeprecatedCollectionType) && (self.settings.target_version >= PythonVersion::Py39 || (self.settings.target_version >= PythonVersion::Py37 && self.ctx.annotations_future_enabled @@ -2335,27 +2310,27 @@ where { pyupgrade::rules::use_pep585_annotation(self, expr); } - if self.settings.rules.enabled(&Rule::DatetimeTimezoneUTC) + if self.settings.rules.enabled(Rule::DatetimeTimezoneUTC) && self.settings.target_version >= PythonVersion::Py311 { pyupgrade::rules::datetime_utc_alias(self, expr); } - if self.settings.rules.enabled(&Rule::TypingTextStrAlias) { + if self.settings.rules.enabled(Rule::TypingTextStrAlias) { pyupgrade::rules::typing_text_str_alias(self, expr); } - if self.settings.rules.enabled(&Rule::NumpyDeprecatedTypeAlias) { + if self.settings.rules.enabled(Rule::NumpyDeprecatedTypeAlias) { numpy::rules::deprecated_type_alias(self, expr); } - if self.settings.rules.enabled(&Rule::RewriteMockImport) { + if self.settings.rules.enabled(Rule::RewriteMockImport) { pyupgrade::rules::rewrite_mock_attribute(self, expr); } - if self.settings.rules.enabled(&Rule::SixPY3Referenced) { + if self.settings.rules.enabled(Rule::SixPY3Referenced) { flake8_2020::rules::name_or_attribute(self, expr); } - if self.settings.rules.enabled(&Rule::BannedApi) { + if self.settings.rules.enabled(Rule::BannedApi) { flake8_tidy_imports::banned_api::banned_attribute_access(self, expr); } - if self.settings.rules.enabled(&Rule::PrivateMemberAccess) { + if self.settings.rules.enabled(Rule::PrivateMemberAccess) { flake8_self::rules::private_member_access(self, expr); } pandas_vet::rules::check_attr(self, attr, value, expr); @@ -2366,14 +2341,14 @@ where keywords, } => { // pyflakes - if self.settings.rules.enabled(&Rule::StringDotFormatInvalidFormat) - || self.settings.rules.enabled(&Rule::StringDotFormatExtraNamedArguments) - || self.settings.rules.enabled(&Rule::StringDotFormatExtraPositionalArguments) - || self.settings.rules.enabled(&Rule::StringDotFormatMissingArguments) - || self.settings.rules.enabled(&Rule::StringDotFormatMixingAutomatic) + if self.settings.rules.enabled(Rule::StringDotFormatInvalidFormat) + || self.settings.rules.enabled(Rule::StringDotFormatExtraNamedArguments) + || self.settings.rules.enabled(Rule::StringDotFormatExtraPositionalArguments) + || self.settings.rules.enabled(Rule::StringDotFormatMissingArguments) + || self.settings.rules.enabled(Rule::StringDotFormatMixingAutomatic) // pyupgrade - || self.settings.rules.enabled(&Rule::FormatLiterals) - || self.settings.rules.enabled(&Rule::FString) + || self.settings.rules.enabled(Rule::FormatLiterals) + || self.settings.rules.enabled(Rule::FString) { if let ExprKind::Attribute { value, attr, .. } = &func.node { if let ExprKind::Constant { @@ -2389,7 +2364,7 @@ where if self .settings .rules - .enabled(&Rule::StringDotFormatInvalidFormat) + .enabled(Rule::StringDotFormatInvalidFormat) { self.diagnostics.push(Diagnostic::new( pyflakes::rules::StringDotFormatInvalidFormat { @@ -2403,7 +2378,7 @@ where if self .settings .rules - .enabled(&Rule::StringDotFormatExtraNamedArguments) + .enabled(Rule::StringDotFormatExtraNamedArguments) { pyflakes::rules::string_dot_format_extra_named_arguments( self, &summary, keywords, location, @@ -2413,7 +2388,7 @@ where if self .settings .rules - .enabled(&Rule::StringDotFormatExtraPositionalArguments) + .enabled(Rule::StringDotFormatExtraPositionalArguments) { pyflakes::rules::string_dot_format_extra_positional_arguments( self, @@ -2424,7 +2399,7 @@ where if self .settings .rules - .enabled(&Rule::StringDotFormatMissingArguments) + .enabled(Rule::StringDotFormatMissingArguments) { pyflakes::rules::string_dot_format_missing_argument( self, &summary, args, keywords, location, @@ -2434,18 +2409,18 @@ where if self .settings .rules - .enabled(&Rule::StringDotFormatMixingAutomatic) + .enabled(Rule::StringDotFormatMixingAutomatic) { pyflakes::rules::string_dot_format_mixing_automatic( self, &summary, location, ); } - if self.settings.rules.enabled(&Rule::FormatLiterals) { + if self.settings.rules.enabled(Rule::FormatLiterals) { pyupgrade::rules::format_literals(self, &summary, expr); } - if self.settings.rules.enabled(&Rule::FString) { + if self.settings.rules.enabled(Rule::FString) { pyupgrade::rules::f_strings(self, &summary, expr); } } @@ -2456,149 +2431,145 @@ where } // pyupgrade - if self.settings.rules.enabled(&Rule::TypeOfPrimitive) { + if self.settings.rules.enabled(Rule::TypeOfPrimitive) { pyupgrade::rules::type_of_primitive(self, expr, func, args); } - if self.settings.rules.enabled(&Rule::DeprecatedUnittestAlias) { + if self.settings.rules.enabled(Rule::DeprecatedUnittestAlias) { pyupgrade::rules::deprecated_unittest_alias(self, func); } - if self.settings.rules.enabled(&Rule::SuperCallWithParameters) { + if self.settings.rules.enabled(Rule::SuperCallWithParameters) { pyupgrade::rules::super_call_with_parameters(self, expr, func, args); } - if self.settings.rules.enabled(&Rule::UnnecessaryEncodeUTF8) { + if self.settings.rules.enabled(Rule::UnnecessaryEncodeUTF8) { pyupgrade::rules::unnecessary_encode_utf8(self, expr, func, args, keywords); } - if self.settings.rules.enabled(&Rule::RedundantOpenModes) { + if self.settings.rules.enabled(Rule::RedundantOpenModes) { pyupgrade::rules::redundant_open_modes(self, expr); } - if self.settings.rules.enabled(&Rule::NativeLiterals) { + if self.settings.rules.enabled(Rule::NativeLiterals) { pyupgrade::rules::native_literals(self, expr, func, args, keywords); } - if self.settings.rules.enabled(&Rule::OpenAlias) { + if self.settings.rules.enabled(Rule::OpenAlias) { pyupgrade::rules::open_alias(self, expr, func); } - if self.settings.rules.enabled(&Rule::ReplaceUniversalNewlines) { + if self.settings.rules.enabled(Rule::ReplaceUniversalNewlines) { pyupgrade::rules::replace_universal_newlines(self, func, keywords); } - if self.settings.rules.enabled(&Rule::ReplaceStdoutStderr) { + if self.settings.rules.enabled(Rule::ReplaceStdoutStderr) { pyupgrade::rules::replace_stdout_stderr(self, expr, func, keywords); } - if self.settings.rules.enabled(&Rule::OSErrorAlias) { + if self.settings.rules.enabled(Rule::OSErrorAlias) { pyupgrade::rules::os_error_alias_call(self, func); } - if self.settings.rules.enabled(&Rule::IsinstanceWithTuple) + if self.settings.rules.enabled(Rule::IsinstanceWithTuple) && self.settings.target_version >= PythonVersion::Py310 { pyupgrade::rules::use_pep604_isinstance(self, expr, func, args); } // flake8-print - if self.settings.rules.enabled(&Rule::PrintFound) - || self.settings.rules.enabled(&Rule::PPrintFound) + if self.settings.rules.enabled(Rule::PrintFound) + || self.settings.rules.enabled(Rule::PPrintFound) { flake8_print::rules::print_call(self, func, keywords); } // flake8-bugbear - if self.settings.rules.enabled(&Rule::UnreliableCallableCheck) { + if self.settings.rules.enabled(Rule::UnreliableCallableCheck) { flake8_bugbear::rules::unreliable_callable_check(self, expr, func, args); } - if self.settings.rules.enabled(&Rule::StripWithMultiCharacters) { + if self.settings.rules.enabled(Rule::StripWithMultiCharacters) { flake8_bugbear::rules::strip_with_multi_characters(self, expr, func, args); } - if self.settings.rules.enabled(&Rule::GetAttrWithConstant) { + if self.settings.rules.enabled(Rule::GetAttrWithConstant) { flake8_bugbear::rules::getattr_with_constant(self, expr, func, args); } - if self.settings.rules.enabled(&Rule::SetAttrWithConstant) { + if self.settings.rules.enabled(Rule::SetAttrWithConstant) { flake8_bugbear::rules::setattr_with_constant(self, expr, func, args); } - if self - .settings - .rules - .enabled(&Rule::UselessContextlibSuppress) - { + if self.settings.rules.enabled(Rule::UselessContextlibSuppress) { flake8_bugbear::rules::useless_contextlib_suppress(self, expr, func, args); } if self .settings .rules - .enabled(&Rule::StarArgUnpackingAfterKeywordArg) + .enabled(Rule::StarArgUnpackingAfterKeywordArg) { flake8_bugbear::rules::star_arg_unpacking_after_keyword_arg( self, args, keywords, ); } - if self.settings.rules.enabled(&Rule::ZipWithoutExplicitStrict) + if self.settings.rules.enabled(Rule::ZipWithoutExplicitStrict) && self.settings.target_version >= PythonVersion::Py310 { flake8_bugbear::rules::zip_without_explicit_strict(self, expr, func, keywords); } // flake8-pie - if self.settings.rules.enabled(&Rule::UnnecessaryDictKwargs) { + if self.settings.rules.enabled(Rule::UnnecessaryDictKwargs) { flake8_pie::rules::no_unnecessary_dict_kwargs(self, expr, keywords); } if self .settings .rules - .enabled(&Rule::UnnecessaryComprehensionAnyAll) + .enabled(Rule::UnnecessaryComprehensionAnyAll) { flake8_pie::rules::unnecessary_comprehension_any_all(self, expr, func, args); } // flake8-bandit - if self.settings.rules.enabled(&Rule::ExecBuiltin) { + if self.settings.rules.enabled(Rule::ExecBuiltin) { if let Some(diagnostic) = flake8_bandit::rules::exec_used(expr, func) { self.diagnostics.push(diagnostic); } } - if self.settings.rules.enabled(&Rule::BadFilePermissions) { + if self.settings.rules.enabled(Rule::BadFilePermissions) { flake8_bandit::rules::bad_file_permissions(self, func, args, keywords); } if self .settings .rules - .enabled(&Rule::RequestWithNoCertValidation) + .enabled(Rule::RequestWithNoCertValidation) { flake8_bandit::rules::request_with_no_cert_validation( self, func, args, keywords, ); } - if self.settings.rules.enabled(&Rule::UnsafeYAMLLoad) { + if self.settings.rules.enabled(Rule::UnsafeYAMLLoad) { flake8_bandit::rules::unsafe_yaml_load(self, func, args, keywords); } - if self.settings.rules.enabled(&Rule::SnmpInsecureVersion) { + if self.settings.rules.enabled(Rule::SnmpInsecureVersion) { flake8_bandit::rules::snmp_insecure_version(self, func, args, keywords); } - if self.settings.rules.enabled(&Rule::SnmpWeakCryptography) { + if self.settings.rules.enabled(Rule::SnmpWeakCryptography) { flake8_bandit::rules::snmp_weak_cryptography(self, func, args, keywords); } - if self.settings.rules.enabled(&Rule::Jinja2AutoescapeFalse) { + if self.settings.rules.enabled(Rule::Jinja2AutoescapeFalse) { flake8_bandit::rules::jinja2_autoescape_false(self, func, args, keywords); } - if self.settings.rules.enabled(&Rule::HardcodedPasswordFuncArg) { + if self.settings.rules.enabled(Rule::HardcodedPasswordFuncArg) { self.diagnostics .extend(flake8_bandit::rules::hardcoded_password_func_arg(keywords)); } - if self.settings.rules.enabled(&Rule::HardcodedSQLExpression) { + if self.settings.rules.enabled(Rule::HardcodedSQLExpression) { flake8_bandit::rules::hardcoded_sql_expression(self, expr); } if self .settings .rules - .enabled(&Rule::HashlibInsecureHashFunction) + .enabled(Rule::HashlibInsecureHashFunction) { flake8_bandit::rules::hashlib_insecure_hash_functions( self, func, args, keywords, ); } - if self.settings.rules.enabled(&Rule::RequestWithoutTimeout) { + if self.settings.rules.enabled(Rule::RequestWithoutTimeout) { flake8_bandit::rules::request_without_timeout(self, func, args, keywords); } if self .settings .rules - .enabled(&Rule::LoggingConfigInsecureListen) + .enabled(Rule::LoggingConfigInsecureListen) { flake8_bandit::rules::logging_config_insecure_listen( self, func, args, keywords, @@ -2606,12 +2577,12 @@ where } // flake8-comprehensions - if self.settings.rules.enabled(&Rule::UnnecessaryGeneratorList) { + if self.settings.rules.enabled(Rule::UnnecessaryGeneratorList) { flake8_comprehensions::rules::unnecessary_generator_list( self, expr, func, args, keywords, ); } - if self.settings.rules.enabled(&Rule::UnnecessaryGeneratorSet) { + if self.settings.rules.enabled(Rule::UnnecessaryGeneratorSet) { flake8_comprehensions::rules::unnecessary_generator_set( self, expr, @@ -2621,7 +2592,7 @@ where keywords, ); } - if self.settings.rules.enabled(&Rule::UnnecessaryGeneratorDict) { + if self.settings.rules.enabled(Rule::UnnecessaryGeneratorDict) { flake8_comprehensions::rules::unnecessary_generator_dict( self, expr, @@ -2634,7 +2605,7 @@ where if self .settings .rules - .enabled(&Rule::UnnecessaryListComprehensionSet) + .enabled(Rule::UnnecessaryListComprehensionSet) { flake8_comprehensions::rules::unnecessary_list_comprehension_set( self, expr, func, args, keywords, @@ -2643,27 +2614,23 @@ where if self .settings .rules - .enabled(&Rule::UnnecessaryListComprehensionDict) + .enabled(Rule::UnnecessaryListComprehensionDict) { flake8_comprehensions::rules::unnecessary_list_comprehension_dict( self, expr, func, args, keywords, ); } - if self.settings.rules.enabled(&Rule::UnnecessaryLiteralSet) { + if self.settings.rules.enabled(Rule::UnnecessaryLiteralSet) { flake8_comprehensions::rules::unnecessary_literal_set( self, expr, func, args, keywords, ); } - if self.settings.rules.enabled(&Rule::UnnecessaryLiteralDict) { + if self.settings.rules.enabled(Rule::UnnecessaryLiteralDict) { flake8_comprehensions::rules::unnecessary_literal_dict( self, expr, func, args, keywords, ); } - if self - .settings - .rules - .enabled(&Rule::UnnecessaryCollectionCall) - { + if self.settings.rules.enabled(Rule::UnnecessaryCollectionCall) { flake8_comprehensions::rules::unnecessary_collection_call( self, expr, @@ -2676,7 +2643,7 @@ where if self .settings .rules - .enabled(&Rule::UnnecessaryLiteralWithinTupleCall) + .enabled(Rule::UnnecessaryLiteralWithinTupleCall) { flake8_comprehensions::rules::unnecessary_literal_within_tuple_call( self, expr, func, args, @@ -2685,19 +2652,19 @@ where if self .settings .rules - .enabled(&Rule::UnnecessaryLiteralWithinListCall) + .enabled(Rule::UnnecessaryLiteralWithinListCall) { flake8_comprehensions::rules::unnecessary_literal_within_list_call( self, expr, func, args, ); } - if self.settings.rules.enabled(&Rule::UnnecessaryListCall) { + if self.settings.rules.enabled(Rule::UnnecessaryListCall) { flake8_comprehensions::rules::unnecessary_list_call(self, expr, func, args); } if self .settings .rules - .enabled(&Rule::UnnecessaryCallAroundSorted) + .enabled(Rule::UnnecessaryCallAroundSorted) { flake8_comprehensions::rules::unnecessary_call_around_sorted( self, expr, func, args, @@ -2706,7 +2673,7 @@ where if self .settings .rules - .enabled(&Rule::UnnecessaryDoubleCastOrProcess) + .enabled(Rule::UnnecessaryDoubleCastOrProcess) { flake8_comprehensions::rules::unnecessary_double_cast_or_process( self, expr, func, args, @@ -2715,13 +2682,13 @@ where if self .settings .rules - .enabled(&Rule::UnnecessarySubscriptReversal) + .enabled(Rule::UnnecessarySubscriptReversal) { flake8_comprehensions::rules::unnecessary_subscript_reversal( self, expr, func, args, ); } - if self.settings.rules.enabled(&Rule::UnnecessaryMap) { + if self.settings.rules.enabled(Rule::UnnecessaryMap) { flake8_comprehensions::rules::unnecessary_map( self, expr, @@ -2735,7 +2702,7 @@ where if self .settings .rules - .enabled(&Rule::BooleanPositionalValueInFunctionCall) + .enabled(Rule::BooleanPositionalValueInFunctionCall) { flake8_boolean_trap::rules::check_boolean_positional_value_in_function_call( self, args, func, @@ -2750,30 +2717,26 @@ where } // flake8-debugger - if self.settings.rules.enabled(&Rule::Debugger) { + if self.settings.rules.enabled(Rule::Debugger) { flake8_debugger::rules::debugger_call(self, expr, func); } // pandas-vet - if self.settings.rules.enabled(&Rule::UseOfInplaceArgument) { + if self.settings.rules.enabled(Rule::UseOfInplaceArgument) { self.diagnostics.extend( pandas_vet::rules::inplace_argument(self, expr, args, keywords).into_iter(), ); } pandas_vet::rules::check_call(self, func); - if self.settings.rules.enabled(&Rule::UseOfPdMerge) { + if self.settings.rules.enabled(Rule::UseOfPdMerge) { if let Some(diagnostic) = pandas_vet::rules::use_of_pd_merge(func) { self.diagnostics.push(diagnostic); }; } // flake8-datetimez - if self - .settings - .rules - .enabled(&Rule::CallDatetimeWithoutTzinfo) - { + if self.settings.rules.enabled(Rule::CallDatetimeWithoutTzinfo) { flake8_datetimez::rules::call_datetime_without_tzinfo( self, func, @@ -2782,16 +2745,16 @@ where Range::from(expr), ); } - if self.settings.rules.enabled(&Rule::CallDatetimeToday) { + if self.settings.rules.enabled(Rule::CallDatetimeToday) { flake8_datetimez::rules::call_datetime_today(self, func, Range::from(expr)); } - if self.settings.rules.enabled(&Rule::CallDatetimeUtcnow) { + if self.settings.rules.enabled(Rule::CallDatetimeUtcnow) { flake8_datetimez::rules::call_datetime_utcnow(self, func, Range::from(expr)); } if self .settings .rules - .enabled(&Rule::CallDatetimeUtcfromtimestamp) + .enabled(Rule::CallDatetimeUtcfromtimestamp) { flake8_datetimez::rules::call_datetime_utcfromtimestamp( self, @@ -2802,7 +2765,7 @@ where if self .settings .rules - .enabled(&Rule::CallDatetimeNowWithoutTzinfo) + .enabled(Rule::CallDatetimeNowWithoutTzinfo) { flake8_datetimez::rules::call_datetime_now_without_tzinfo( self, @@ -2812,11 +2775,7 @@ where Range::from(expr), ); } - if self - .settings - .rules - .enabled(&Rule::CallDatetimeFromtimestamp) - { + if self.settings.rules.enabled(Rule::CallDatetimeFromtimestamp) { flake8_datetimez::rules::call_datetime_fromtimestamp( self, func, @@ -2828,7 +2787,7 @@ where if self .settings .rules - .enabled(&Rule::CallDatetimeStrptimeWithoutZone) + .enabled(Rule::CallDatetimeStrptimeWithoutZone) { flake8_datetimez::rules::call_datetime_strptime_without_zone( self, @@ -2837,18 +2796,18 @@ where Range::from(expr), ); } - if self.settings.rules.enabled(&Rule::CallDateToday) { + if self.settings.rules.enabled(Rule::CallDateToday) { flake8_datetimez::rules::call_date_today(self, func, Range::from(expr)); } - if self.settings.rules.enabled(&Rule::CallDateFromtimestamp) { + if self.settings.rules.enabled(Rule::CallDateFromtimestamp) { flake8_datetimez::rules::call_date_fromtimestamp(self, func, Range::from(expr)); } // pygrep-hooks - if self.settings.rules.enabled(&Rule::NoEval) { + if self.settings.rules.enabled(Rule::NoEval) { pygrep_hooks::rules::no_eval(self, func); } - if self.settings.rules.enabled(&Rule::DeprecatedLogWarn) { + if self.settings.rules.enabled(Rule::DeprecatedLogWarn) { pygrep_hooks::rules::deprecated_log_warn(self, func); } @@ -2856,32 +2815,32 @@ where if self .settings .rules - .enabled(&Rule::UnnecessaryDirectLambdaCall) + .enabled(Rule::UnnecessaryDirectLambdaCall) { pylint::rules::unnecessary_direct_lambda_call(self, expr, func); } - if self.settings.rules.enabled(&Rule::ConsiderUsingSysExit) { + if self.settings.rules.enabled(Rule::ConsiderUsingSysExit) { pylint::rules::consider_using_sys_exit(self, func); } - if self.settings.rules.enabled(&Rule::BadStrStripCall) { + if self.settings.rules.enabled(Rule::BadStrStripCall) { pylint::rules::bad_str_strip_call(self, func, args); } - if self.settings.rules.enabled(&Rule::InvalidEnvvarDefault) { + if self.settings.rules.enabled(Rule::InvalidEnvvarDefault) { pylint::rules::invalid_envvar_default(self, func, args, keywords); } - if self.settings.rules.enabled(&Rule::InvalidEnvvarValue) { + if self.settings.rules.enabled(Rule::InvalidEnvvarValue) { pylint::rules::invalid_envvar_value(self, func, args, keywords); } // flake8-pytest-style - if self.settings.rules.enabled(&Rule::PatchWithLambda) { + if self.settings.rules.enabled(Rule::PatchWithLambda) { if let Some(diagnostic) = flake8_pytest_style::rules::patch_with_lambda(func, args, keywords) { self.diagnostics.push(diagnostic); } } - if self.settings.rules.enabled(&Rule::UnittestAssertion) { + if self.settings.rules.enabled(Rule::UnittestAssertion) { if let Some(diagnostic) = flake8_pytest_style::rules::unittest_assertion( self, expr, func, args, keywords, ) { @@ -2889,13 +2848,13 @@ where } } - if self.settings.rules.enabled(&Rule::RaisesWithoutException) - || self.settings.rules.enabled(&Rule::RaisesTooBroad) + if self.settings.rules.enabled(Rule::RaisesWithoutException) + || self.settings.rules.enabled(Rule::RaisesTooBroad) { flake8_pytest_style::rules::raises_call(self, func, args, keywords); } - if self.settings.rules.enabled(&Rule::FailWithoutMessage) { + if self.settings.rules.enabled(Rule::FailWithoutMessage) { flake8_pytest_style::rules::fail_call(self, func, args, keywords); } @@ -2903,67 +2862,67 @@ where if self .settings .rules - .enabled(&Rule::OpenFileWithContextHandler) + .enabled(Rule::OpenFileWithContextHandler) { flake8_simplify::rules::open_file_with_context_handler(self, func); } // flake8-use-pathlib - if self.settings.rules.enabled(&Rule::PathlibAbspath) - || self.settings.rules.enabled(&Rule::PathlibChmod) - || self.settings.rules.enabled(&Rule::PathlibMkdir) - || self.settings.rules.enabled(&Rule::PathlibMakedirs) - || self.settings.rules.enabled(&Rule::PathlibRename) - || self.settings.rules.enabled(&Rule::PathlibReplace) - || self.settings.rules.enabled(&Rule::PathlibRmdir) - || self.settings.rules.enabled(&Rule::PathlibRemove) - || self.settings.rules.enabled(&Rule::PathlibUnlink) - || self.settings.rules.enabled(&Rule::PathlibGetcwd) - || self.settings.rules.enabled(&Rule::PathlibExists) - || self.settings.rules.enabled(&Rule::PathlibExpanduser) - || self.settings.rules.enabled(&Rule::PathlibIsDir) - || self.settings.rules.enabled(&Rule::PathlibIsFile) - || self.settings.rules.enabled(&Rule::PathlibIsLink) - || self.settings.rules.enabled(&Rule::PathlibReadlink) - || self.settings.rules.enabled(&Rule::PathlibStat) - || self.settings.rules.enabled(&Rule::PathlibIsAbs) - || self.settings.rules.enabled(&Rule::PathlibJoin) - || self.settings.rules.enabled(&Rule::PathlibBasename) - || self.settings.rules.enabled(&Rule::PathlibSamefile) - || self.settings.rules.enabled(&Rule::PathlibSplitext) - || self.settings.rules.enabled(&Rule::PathlibOpen) - || self.settings.rules.enabled(&Rule::PathlibPyPath) + if self.settings.rules.enabled(Rule::PathlibAbspath) + || self.settings.rules.enabled(Rule::PathlibChmod) + || self.settings.rules.enabled(Rule::PathlibMkdir) + || self.settings.rules.enabled(Rule::PathlibMakedirs) + || self.settings.rules.enabled(Rule::PathlibRename) + || self.settings.rules.enabled(Rule::PathlibReplace) + || self.settings.rules.enabled(Rule::PathlibRmdir) + || self.settings.rules.enabled(Rule::PathlibRemove) + || self.settings.rules.enabled(Rule::PathlibUnlink) + || self.settings.rules.enabled(Rule::PathlibGetcwd) + || self.settings.rules.enabled(Rule::PathlibExists) + || self.settings.rules.enabled(Rule::PathlibExpanduser) + || self.settings.rules.enabled(Rule::PathlibIsDir) + || self.settings.rules.enabled(Rule::PathlibIsFile) + || self.settings.rules.enabled(Rule::PathlibIsLink) + || self.settings.rules.enabled(Rule::PathlibReadlink) + || self.settings.rules.enabled(Rule::PathlibStat) + || self.settings.rules.enabled(Rule::PathlibIsAbs) + || self.settings.rules.enabled(Rule::PathlibJoin) + || self.settings.rules.enabled(Rule::PathlibBasename) + || self.settings.rules.enabled(Rule::PathlibSamefile) + || self.settings.rules.enabled(Rule::PathlibSplitext) + || self.settings.rules.enabled(Rule::PathlibOpen) + || self.settings.rules.enabled(Rule::PathlibPyPath) { flake8_use_pathlib::helpers::replaceable_by_pathlib(self, func); } // numpy - if self.settings.rules.enabled(&Rule::NumpyLegacyRandom) { + if self.settings.rules.enabled(Rule::NumpyLegacyRandom) { numpy::rules::numpy_legacy_random(self, func); } // flake8-logging-format - if self.settings.rules.enabled(&Rule::LoggingStringFormat) - || self.settings.rules.enabled(&Rule::LoggingPercentFormat) - || self.settings.rules.enabled(&Rule::LoggingStringConcat) - || self.settings.rules.enabled(&Rule::LoggingFString) - || self.settings.rules.enabled(&Rule::LoggingWarn) - || self.settings.rules.enabled(&Rule::LoggingExtraAttrClash) - || self.settings.rules.enabled(&Rule::LoggingExcInfo) - || self.settings.rules.enabled(&Rule::LoggingRedundantExcInfo) + if self.settings.rules.enabled(Rule::LoggingStringFormat) + || self.settings.rules.enabled(Rule::LoggingPercentFormat) + || self.settings.rules.enabled(Rule::LoggingStringConcat) + || self.settings.rules.enabled(Rule::LoggingFString) + || self.settings.rules.enabled(Rule::LoggingWarn) + || self.settings.rules.enabled(Rule::LoggingExtraAttrClash) + || self.settings.rules.enabled(Rule::LoggingExcInfo) + || self.settings.rules.enabled(Rule::LoggingRedundantExcInfo) { flake8_logging_format::rules::logging_call(self, func, args, keywords); } // pylint logging checker - if self.settings.rules.enabled(&Rule::LoggingTooFewArgs) - || self.settings.rules.enabled(&Rule::LoggingTooManyArgs) + if self.settings.rules.enabled(Rule::LoggingTooFewArgs) + || self.settings.rules.enabled(Rule::LoggingTooManyArgs) { pylint::rules::logging_call(self, func, args, keywords); } // flake8-django - if self.settings.rules.enabled(&Rule::LocalsInRenderFunction) { + if self.settings.rules.enabled(Rule::LocalsInRenderFunction) { flake8_django::rules::locals_in_render_function(self, func, args, keywords); } } @@ -2971,40 +2930,40 @@ where if self .settings .rules - .enabled(&Rule::MultiValueRepeatedKeyLiteral) + .enabled(Rule::MultiValueRepeatedKeyLiteral) || self .settings .rules - .enabled(&Rule::MultiValueRepeatedKeyVariable) + .enabled(Rule::MultiValueRepeatedKeyVariable) { pyflakes::rules::repeated_keys(self, keys, values); } - if self.settings.rules.enabled(&Rule::UnnecessarySpread) { + if self.settings.rules.enabled(Rule::UnnecessarySpread) { flake8_pie::rules::no_unnecessary_spread(self, keys, values); } } ExprKind::Yield { .. } => { - if self.settings.rules.enabled(&Rule::YieldOutsideFunction) { + if self.settings.rules.enabled(Rule::YieldOutsideFunction) { pyflakes::rules::yield_outside_function(self, expr); } - if self.settings.rules.enabled(&Rule::YieldInInit) { + if self.settings.rules.enabled(Rule::YieldInInit) { pylint::rules::yield_in_init(self, expr); } } ExprKind::YieldFrom { .. } => { - if self.settings.rules.enabled(&Rule::YieldOutsideFunction) { + if self.settings.rules.enabled(Rule::YieldOutsideFunction) { pyflakes::rules::yield_outside_function(self, expr); } - if self.settings.rules.enabled(&Rule::YieldInInit) { + if self.settings.rules.enabled(Rule::YieldInInit) { pylint::rules::yield_in_init(self, expr); } } ExprKind::Await { .. } => { - if self.settings.rules.enabled(&Rule::YieldOutsideFunction) { + if self.settings.rules.enabled(Rule::YieldOutsideFunction) { pyflakes::rules::yield_outside_function(self, expr); } - if self.settings.rules.enabled(&Rule::AwaitOutsideAsync) { + if self.settings.rules.enabled(Rule::AwaitOutsideAsync) { pylint::rules::await_outside_async(self, expr); } } @@ -3012,11 +2971,11 @@ where if self .settings .rules - .enabled(&Rule::FStringMissingPlaceholders) + .enabled(Rule::FStringMissingPlaceholders) { pyflakes::rules::f_string_missing_placeholders(expr, values, self); } - if self.settings.rules.enabled(&Rule::HardcodedSQLExpression) { + if self.settings.rules.enabled(Rule::HardcodedSQLExpression) { flake8_bandit::rules::hardcoded_sql_expression(self, expr); } } @@ -3025,7 +2984,7 @@ where op: Operator::RShift, .. } => { - if self.settings.rules.enabled(&Rule::InvalidPrintSyntax) { + if self.settings.rules.enabled(Rule::InvalidPrintSyntax) { pyflakes::rules::invalid_print_syntax(self, left); } } @@ -3042,39 +3001,39 @@ where if self .settings .rules - .enabled(&Rule::PercentFormatInvalidFormat) + .enabled(Rule::PercentFormatInvalidFormat) || self .settings .rules - .enabled(&Rule::PercentFormatExpectedMapping) + .enabled(Rule::PercentFormatExpectedMapping) || self .settings .rules - .enabled(&Rule::PercentFormatExpectedSequence) + .enabled(Rule::PercentFormatExpectedSequence) || self .settings .rules - .enabled(&Rule::PercentFormatExtraNamedArguments) + .enabled(Rule::PercentFormatExtraNamedArguments) || self .settings .rules - .enabled(&Rule::PercentFormatMissingArgument) + .enabled(Rule::PercentFormatMissingArgument) || self .settings .rules - .enabled(&Rule::PercentFormatMixedPositionalAndNamed) + .enabled(Rule::PercentFormatMixedPositionalAndNamed) || self .settings .rules - .enabled(&Rule::PercentFormatPositionalCountMismatch) + .enabled(Rule::PercentFormatPositionalCountMismatch) || self .settings .rules - .enabled(&Rule::PercentFormatStarRequiresSequence) + .enabled(Rule::PercentFormatStarRequiresSequence) || self .settings .rules - .enabled(&Rule::PercentFormatUnsupportedFormatCharacter) + .enabled(Rule::PercentFormatUnsupportedFormatCharacter) { let location = Range::from(expr); match pyflakes::cformat::CFormatSummary::try_from(value.as_str()) { @@ -3085,7 +3044,7 @@ where if self .settings .rules - .enabled(&Rule::PercentFormatUnsupportedFormatCharacter) + .enabled(Rule::PercentFormatUnsupportedFormatCharacter) { self.diagnostics.push(Diagnostic::new( pyflakes::rules::PercentFormatUnsupportedFormatCharacter { @@ -3099,7 +3058,7 @@ where if self .settings .rules - .enabled(&Rule::PercentFormatInvalidFormat) + .enabled(Rule::PercentFormatInvalidFormat) { self.diagnostics.push(Diagnostic::new( pyflakes::rules::PercentFormatInvalidFormat { @@ -3113,7 +3072,7 @@ where if self .settings .rules - .enabled(&Rule::PercentFormatExpectedMapping) + .enabled(Rule::PercentFormatExpectedMapping) { pyflakes::rules::percent_format_expected_mapping( self, &summary, right, location, @@ -3122,7 +3081,7 @@ where if self .settings .rules - .enabled(&Rule::PercentFormatExpectedSequence) + .enabled(Rule::PercentFormatExpectedSequence) { pyflakes::rules::percent_format_expected_sequence( self, &summary, right, location, @@ -3131,7 +3090,7 @@ where if self .settings .rules - .enabled(&Rule::PercentFormatExtraNamedArguments) + .enabled(Rule::PercentFormatExtraNamedArguments) { pyflakes::rules::percent_format_extra_named_arguments( self, &summary, right, location, @@ -3140,7 +3099,7 @@ where if self .settings .rules - .enabled(&Rule::PercentFormatMissingArgument) + .enabled(Rule::PercentFormatMissingArgument) { pyflakes::rules::percent_format_missing_arguments( self, &summary, right, location, @@ -3149,7 +3108,7 @@ where if self .settings .rules - .enabled(&Rule::PercentFormatMixedPositionalAndNamed) + .enabled(Rule::PercentFormatMixedPositionalAndNamed) { pyflakes::rules::percent_format_mixed_positional_and_named( self, &summary, location, @@ -3158,7 +3117,7 @@ where if self .settings .rules - .enabled(&Rule::PercentFormatPositionalCountMismatch) + .enabled(Rule::PercentFormatPositionalCountMismatch) { pyflakes::rules::percent_format_positional_count_mismatch( self, &summary, right, location, @@ -3167,7 +3126,7 @@ where if self .settings .rules - .enabled(&Rule::PercentFormatStarRequiresSequence) + .enabled(Rule::PercentFormatStarRequiresSequence) { pyflakes::rules::percent_format_star_requires_sequence( self, &summary, right, location, @@ -3177,13 +3136,13 @@ where } } - if self.settings.rules.enabled(&Rule::PrintfStringFormatting) { + if self.settings.rules.enabled(Rule::PrintfStringFormatting) { pyupgrade::rules::printf_string_formatting(self, expr, left, right); } - if self.settings.rules.enabled(&Rule::BadStringFormatType) { + if self.settings.rules.enabled(Rule::BadStringFormatType) { pylint::rules::bad_string_format_type(self, expr, right); } - if self.settings.rules.enabled(&Rule::HardcodedSQLExpression) { + if self.settings.rules.enabled(Rule::HardcodedSQLExpression) { flake8_bandit::rules::hardcoded_sql_expression(self, expr); } } @@ -3194,7 +3153,7 @@ where if self .settings .rules - .enabled(&Rule::ExplicitStringConcatenation) + .enabled(Rule::ExplicitStringConcatenation) { if let Some(diagnostic) = flake8_implicit_str_concat::rules::explicit(expr) { self.diagnostics.push(diagnostic); @@ -3203,17 +3162,17 @@ where if self .settings .rules - .enabled(&Rule::UnpackInsteadOfConcatenatingToCollectionLiteral) + .enabled(Rule::UnpackInsteadOfConcatenatingToCollectionLiteral) { ruff::rules::unpack_instead_of_concatenating_to_collection_literal(self, expr); } - if self.settings.rules.enabled(&Rule::HardcodedSQLExpression) { + if self.settings.rules.enabled(Rule::HardcodedSQLExpression) { flake8_bandit::rules::hardcoded_sql_expression(self, expr); } } ExprKind::UnaryOp { op, operand } => { - let check_not_in = self.settings.rules.enabled(&Rule::NotInTest); - let check_not_is = self.settings.rules.enabled(&Rule::NotIsTest); + let check_not_in = self.settings.rules.enabled(Rule::NotInTest); + let check_not_is = self.settings.rules.enabled(Rule::NotIsTest); if check_not_in || check_not_is { pycodestyle::rules::not_tests( self, @@ -3225,17 +3184,17 @@ where ); } - if self.settings.rules.enabled(&Rule::UnaryPrefixIncrement) { + if self.settings.rules.enabled(Rule::UnaryPrefixIncrement) { flake8_bugbear::rules::unary_prefix_increment(self, expr, op, operand); } - if self.settings.rules.enabled(&Rule::NegateEqualOp) { + if self.settings.rules.enabled(Rule::NegateEqualOp) { flake8_simplify::rules::negation_with_equal_op(self, expr, op, operand); } - if self.settings.rules.enabled(&Rule::NegateNotEqualOp) { + if self.settings.rules.enabled(Rule::NegateNotEqualOp) { flake8_simplify::rules::negation_with_not_equal_op(self, expr, op, operand); } - if self.settings.rules.enabled(&Rule::DoubleNegation) { + if self.settings.rules.enabled(Rule::DoubleNegation) { flake8_simplify::rules::double_negation(self, expr, op, operand); } } @@ -3244,9 +3203,9 @@ where ops, comparators, } => { - let check_none_comparisons = self.settings.rules.enabled(&Rule::NoneComparison); + let check_none_comparisons = self.settings.rules.enabled(Rule::NoneComparison); let check_true_false_comparisons = - self.settings.rules.enabled(&Rule::TrueFalseComparison); + self.settings.rules.enabled(Rule::TrueFalseComparison); if check_none_comparisons || check_true_false_comparisons { pycodestyle::rules::literal_comparisons( self, @@ -3259,7 +3218,7 @@ where ); } - if self.settings.rules.enabled(&Rule::IsLiteral) { + if self.settings.rules.enabled(Rule::IsLiteral) { pyflakes::rules::invalid_literal_comparison( self, left, @@ -3269,7 +3228,7 @@ where ); } - if self.settings.rules.enabled(&Rule::TypeComparison) { + if self.settings.rules.enabled(Rule::TypeComparison) { self.diagnostics.extend(pycodestyle::rules::type_comparison( ops, comparators, @@ -3277,22 +3236,19 @@ where )); } - if self.settings.rules.enabled(&Rule::SysVersionCmpStr3) + if self.settings.rules.enabled(Rule::SysVersionCmpStr3) || self .settings .rules - .enabled(&Rule::SysVersionInfo0Eq3Referenced) - || self.settings.rules.enabled(&Rule::SysVersionInfo1CmpInt) - || self - .settings - .rules - .enabled(&Rule::SysVersionInfoMinorCmpInt) - || self.settings.rules.enabled(&Rule::SysVersionCmpStr10) + .enabled(Rule::SysVersionInfo0Eq3Referenced) + || self.settings.rules.enabled(Rule::SysVersionInfo1CmpInt) + || self.settings.rules.enabled(Rule::SysVersionInfoMinorCmpInt) + || self.settings.rules.enabled(Rule::SysVersionCmpStr10) { flake8_2020::rules::compare(self, left, ops, comparators); } - if self.settings.rules.enabled(&Rule::HardcodedPasswordString) { + if self.settings.rules.enabled(Rule::HardcodedPasswordString) { self.diagnostics.extend( flake8_bandit::rules::compare_to_hardcoded_password_string( left, @@ -3301,32 +3257,29 @@ where ); } - if self.settings.rules.enabled(&Rule::ComparisonOfConstant) { + if self.settings.rules.enabled(Rule::ComparisonOfConstant) { pylint::rules::comparison_of_constant(self, left, ops, comparators); } - if self.settings.rules.enabled(&Rule::CompareToEmptyString) { + if self.settings.rules.enabled(Rule::CompareToEmptyString) { pylint::rules::compare_to_empty_string(self, left, ops, comparators); } - if self.settings.rules.enabled(&Rule::MagicValueComparison) { + if self.settings.rules.enabled(Rule::MagicValueComparison) { pylint::rules::magic_value_comparison(self, left, comparators); } - if self.settings.rules.enabled(&Rule::KeyInDict) { + if self.settings.rules.enabled(Rule::KeyInDict) { flake8_simplify::rules::key_in_dict_compare(self, expr, left, ops, comparators); } - if self.settings.rules.enabled(&Rule::YodaConditions) { + if self.settings.rules.enabled(Rule::YodaConditions) { flake8_simplify::rules::yoda_conditions(self, expr, left, ops, comparators); } if self.is_stub { - if self - .settings - .rules - .enabled(&Rule::UnrecognizedPlatformCheck) - || self.settings.rules.enabled(&Rule::UnrecognizedPlatformName) + if self.settings.rules.enabled(Rule::UnrecognizedPlatformCheck) + || self.settings.rules.enabled(Rule::UnrecognizedPlatformName) { flake8_pyi::rules::unrecognized_platform( self, @@ -3337,7 +3290,7 @@ where ); } - if self.settings.rules.enabled(&Rule::BadVersionInfoComparison) { + if self.settings.rules.enabled(Rule::BadVersionInfoComparison) { flake8_pyi::rules::bad_version_info_comparison( self, expr, @@ -3363,7 +3316,7 @@ where if self .settings .rules - .enabled(&Rule::HardcodedBindAllInterfaces) + .enabled(Rule::HardcodedBindAllInterfaces) { if let Some(diagnostic) = flake8_bandit::rules::hardcoded_bind_all_interfaces( value, @@ -3372,7 +3325,7 @@ where self.diagnostics.push(diagnostic); } } - if self.settings.rules.enabled(&Rule::HardcodedTempFile) { + if self.settings.rules.enabled(Rule::HardcodedTempFile) { if let Some(diagnostic) = flake8_bandit::rules::hardcoded_tmp_directory( expr, value, @@ -3381,12 +3334,12 @@ where self.diagnostics.push(diagnostic); } } - if self.settings.rules.enabled(&Rule::RewriteUnicodeLiteral) { + if self.settings.rules.enabled(Rule::RewriteUnicodeLiteral) { pyupgrade::rules::rewrite_unicode_literal(self, expr, kind.as_deref()); } } ExprKind::Lambda { args, body, .. } => { - if self.settings.rules.enabled(&Rule::PreferListBuiltin) { + if self.settings.rules.enabled(Rule::PreferListBuiltin) { flake8_pie::rules::prefer_list_builtin(self, expr); } @@ -3401,64 +3354,60 @@ where .push_scope(Scope::new(ScopeKind::Lambda(Lambda { args, body }))); } ExprKind::IfExp { test, body, orelse } => { - if self.settings.rules.enabled(&Rule::IfExprWithTrueFalse) { + if self.settings.rules.enabled(Rule::IfExprWithTrueFalse) { flake8_simplify::rules::explicit_true_false_in_ifexpr( self, expr, test, body, orelse, ); } - if self.settings.rules.enabled(&Rule::IfExprWithFalseTrue) { + if self.settings.rules.enabled(Rule::IfExprWithFalseTrue) { flake8_simplify::rules::explicit_false_true_in_ifexpr( self, expr, test, body, orelse, ); } - if self.settings.rules.enabled(&Rule::IfExprWithTwistedArms) { + if self.settings.rules.enabled(Rule::IfExprWithTwistedArms) { flake8_simplify::rules::twisted_arms_in_ifexpr(self, expr, test, body, orelse); } } ExprKind::ListComp { elt, generators } | ExprKind::SetComp { elt, generators } => { - if self.settings.rules.enabled(&Rule::UnnecessaryComprehension) { + if self.settings.rules.enabled(Rule::UnnecessaryComprehension) { flake8_comprehensions::rules::unnecessary_comprehension( self, expr, elt, generators, ); } - if self.settings.rules.enabled(&Rule::FunctionUsesLoopVariable) { + if self.settings.rules.enabled(Rule::FunctionUsesLoopVariable) { flake8_bugbear::rules::function_uses_loop_variable(self, &Node::Expr(expr)); } self.ctx.push_scope(Scope::new(ScopeKind::Generator)); } ExprKind::GeneratorExp { .. } | ExprKind::DictComp { .. } => { - if self.settings.rules.enabled(&Rule::FunctionUsesLoopVariable) { + if self.settings.rules.enabled(Rule::FunctionUsesLoopVariable) { flake8_bugbear::rules::function_uses_loop_variable(self, &Node::Expr(expr)); } self.ctx.push_scope(Scope::new(ScopeKind::Generator)); } ExprKind::BoolOp { op, values } => { - if self - .settings - .rules - .enabled(&Rule::ConsiderMergingIsinstance) - { + if self.settings.rules.enabled(Rule::ConsiderMergingIsinstance) { pylint::rules::merge_isinstance(self, expr, op, values); } - if self.settings.rules.enabled(&Rule::SingleStartsEndsWith) { + if self.settings.rules.enabled(Rule::SingleStartsEndsWith) { flake8_pie::rules::single_starts_ends_with(self, expr); } - if self.settings.rules.enabled(&Rule::DuplicateIsinstanceCall) { + if self.settings.rules.enabled(Rule::DuplicateIsinstanceCall) { flake8_simplify::rules::duplicate_isinstance_call(self, expr); } - if self.settings.rules.enabled(&Rule::CompareWithTuple) { + if self.settings.rules.enabled(Rule::CompareWithTuple) { flake8_simplify::rules::compare_with_tuple(self, expr); } - if self.settings.rules.enabled(&Rule::ExprAndNotExpr) { + if self.settings.rules.enabled(Rule::ExprAndNotExpr) { flake8_simplify::rules::expr_and_not_expr(self, expr); } - if self.settings.rules.enabled(&Rule::ExprOrNotExpr) { + if self.settings.rules.enabled(Rule::ExprOrNotExpr) { flake8_simplify::rules::expr_or_not_expr(self, expr); } - if self.settings.rules.enabled(&Rule::ExprOrTrue) { + if self.settings.rules.enabled(Rule::ExprOrTrue) { flake8_simplify::rules::expr_or_true(self, expr); } - if self.settings.rules.enabled(&Rule::ExprAndFalse) { + if self.settings.rules.enabled(Rule::ExprAndFalse) { flake8_simplify::rules::expr_and_false(self, expr); } } @@ -3714,7 +3663,7 @@ where } fn visit_comprehension(&mut self, comprehension: &'b Comprehension) { - if self.settings.rules.enabled(&Rule::KeyInDict) { + if self.settings.rules.enabled(Rule::KeyInDict) { flake8_simplify::rules::key_in_dict_for( self, &comprehension.target, @@ -3729,7 +3678,7 @@ where ExcepthandlerKind::ExceptHandler { type_, name, body, .. } => { - if self.settings.rules.enabled(&Rule::BareExcept) { + if self.settings.rules.enabled(Rule::BareExcept) { if let Some(diagnostic) = pycodestyle::rules::bare_except( type_.as_deref(), body, @@ -3742,11 +3691,11 @@ where if self .settings .rules - .enabled(&Rule::RaiseWithoutFromInsideExcept) + .enabled(Rule::RaiseWithoutFromInsideExcept) { flake8_bugbear::rules::raise_without_from_inside_except(self, body); } - if self.settings.rules.enabled(&Rule::BlindExcept) { + if self.settings.rules.enabled(Rule::BlindExcept) { flake8_blind_except::rules::blind_except( self, type_.as_deref(), @@ -3754,7 +3703,7 @@ where body, ); } - if self.settings.rules.enabled(&Rule::TryExceptPass) { + if self.settings.rules.enabled(Rule::TryExceptPass) { flake8_bandit::rules::try_except_pass( self, excepthandler, @@ -3764,7 +3713,7 @@ where self.settings.flake8_bandit.check_typed_exception, ); } - if self.settings.rules.enabled(&Rule::TryExceptContinue) { + if self.settings.rules.enabled(Rule::TryExceptContinue) { flake8_bandit::rules::try_except_continue( self, excepthandler, @@ -3774,22 +3723,22 @@ where self.settings.flake8_bandit.check_typed_exception, ); } - if self.settings.rules.enabled(&Rule::ExceptWithEmptyTuple) { + if self.settings.rules.enabled(Rule::ExceptWithEmptyTuple) { flake8_bugbear::rules::except_with_empty_tuple(self, excepthandler); } if self .settings .rules - .enabled(&Rule::ExceptWithNonExceptionClasses) + .enabled(Rule::ExceptWithNonExceptionClasses) { flake8_bugbear::rules::except_with_non_exception_classes(self, excepthandler); } - if self.settings.rules.enabled(&Rule::ReraiseNoCause) { + if self.settings.rules.enabled(Rule::ReraiseNoCause) { tryceratops::rules::reraise_no_cause(self, body); } match name { Some(name) => { - if self.settings.rules.enabled(&Rule::AmbiguousVariableName) { + if self.settings.rules.enabled(Rule::AmbiguousVariableName) { if let Some(diagnostic) = pycodestyle::rules::ambiguous_variable_name( name, helpers::excepthandler_name_range(excepthandler, self.locator) @@ -3849,14 +3798,14 @@ where &scope.bindings.remove(&name.as_str()) } { if !self.ctx.bindings[*index].used() { - if self.settings.rules.enabled(&Rule::UnusedVariable) { + if self.settings.rules.enabled(Rule::UnusedVariable) { let mut diagnostic = Diagnostic::new( pyflakes::rules::UnusedVariable { name: name.to_string(), }, name_range, ); - if self.patch(&Rule::UnusedVariable) { + if self.patch(Rule::UnusedVariable) { match pyflakes::fixes::remove_exception_handler_assignment( excepthandler, self.locator, @@ -3902,13 +3851,13 @@ where } fn visit_arguments(&mut self, arguments: &'b Arguments) { - if self.settings.rules.enabled(&Rule::MutableArgumentDefault) { + if self.settings.rules.enabled(Rule::MutableArgumentDefault) { flake8_bugbear::rules::mutable_argument_default(self, arguments); } if self .settings .rules - .enabled(&Rule::FunctionCallArgumentDefault) + .enabled(Rule::FunctionCallArgumentDefault) { flake8_bugbear::rules::function_call_argument_default(self, arguments); } @@ -3917,13 +3866,13 @@ where if self .settings .rules - .enabled(&Rule::TypedArgumentSimpleDefaults) + .enabled(Rule::TypedArgumentSimpleDefaults) { flake8_pyi::rules::typed_argument_simple_defaults(self, arguments); } } if self.is_stub { - if self.settings.rules.enabled(&Rule::ArgumentSimpleDefaults) { + if self.settings.rules.enabled(Rule::ArgumentSimpleDefaults) { flake8_pyi::rules::argument_simple_defaults(self, arguments); } } @@ -3963,7 +3912,7 @@ where }, ); - if self.settings.rules.enabled(&Rule::AmbiguousVariableName) { + if self.settings.rules.enabled(Rule::AmbiguousVariableName) { if let Some(diagnostic) = pycodestyle::rules::ambiguous_variable_name(&arg.node.arg, Range::from(arg)) { @@ -3971,7 +3920,7 @@ where } } - if self.settings.rules.enabled(&Rule::InvalidArgumentName) { + if self.settings.rules.enabled(Rule::InvalidArgumentName) { if let Some(diagnostic) = pep8_naming::rules::invalid_argument_name( &arg.node.arg, arg, @@ -4011,7 +3960,7 @@ where } fn visit_body(&mut self, body: &'b [Stmt]) { - if self.settings.rules.enabled(&Rule::UnnecessaryPass) { + if self.settings.rules.enabled(Rule::UnnecessaryPass) { flake8_pie::rules::no_unnecessary_pass(self, body); } @@ -4069,7 +4018,7 @@ impl<'a> Checker<'a> { | BindingKind::FutureImportation ); if binding.kind.is_loop_var() && existing_is_import { - if self.settings.rules.enabled(&Rule::ImportShadowedByLoopVar) { + if self.settings.rules.enabled(Rule::ImportShadowedByLoopVar) { self.diagnostics.push(Diagnostic::new( pyflakes::rules::ImportShadowedByLoopVar { name: name.to_string(), @@ -4088,7 +4037,7 @@ impl<'a> Checker<'a> { cast::decorator_list(existing.source.as_ref().unwrap()), )) { - if self.settings.rules.enabled(&Rule::RedefinedWhileUnused) { + if self.settings.rules.enabled(Rule::RedefinedWhileUnused) { let mut diagnostic = Diagnostic::new( pyflakes::rules::RedefinedWhileUnused { name: name.to_string(), @@ -4276,7 +4225,7 @@ impl<'a> Checker<'a> { } if import_starred { - if self.settings.rules.enabled(&Rule::ImportStarUsage) { + if self.settings.rules.enabled(Rule::ImportStarUsage) { let mut from_list = vec![]; for scope_index in self.ctx.scope_stack.iter().rev() { let scope = &self.ctx.scopes[*scope_index]; @@ -4306,7 +4255,7 @@ impl<'a> Checker<'a> { return; } - if self.settings.rules.enabled(&Rule::UndefinedName) { + if self.settings.rules.enabled(Rule::UndefinedName) { // Allow __path__. if self.path.ends_with("__init__.py") && id == "__path__" { return; @@ -4342,7 +4291,7 @@ impl<'a> Checker<'a> { { let parent = self.ctx.current_stmt().0; - if self.settings.rules.enabled(&Rule::UndefinedLocal) { + if self.settings.rules.enabled(Rule::UndefinedLocal) { let scopes: Vec<&Scope> = self .ctx .scope_stack @@ -4359,7 +4308,7 @@ impl<'a> Checker<'a> { if self .settings .rules - .enabled(&Rule::NonLowercaseVariableInFunction) + .enabled(Rule::NonLowercaseVariableInFunction) { if matches!(self.ctx.current_scope().kind, ScopeKind::Function(..)) { // Ignore globals. @@ -4378,7 +4327,7 @@ impl<'a> Checker<'a> { if self .settings .rules - .enabled(&Rule::MixedCaseVariableInClassScope) + .enabled(Rule::MixedCaseVariableInClassScope) { if matches!(self.ctx.current_scope().kind, ScopeKind::Class(..)) { pep8_naming::rules::mixed_case_variable_in_class_scope(self, expr, parent, id); @@ -4388,7 +4337,7 @@ impl<'a> Checker<'a> { if self .settings .rules - .enabled(&Rule::MixedCaseVariableInGlobalScope) + .enabled(Rule::MixedCaseVariableInGlobalScope) { if matches!(self.ctx.current_scope().kind, ScopeKind::Module) { pep8_naming::rules::mixed_case_variable_in_global_scope(self, expr, parent, id); @@ -4483,14 +4432,14 @@ impl<'a> Checker<'a> { } { let (all_names, all_names_flags) = extract_all_names(&self.ctx, parent, current); - if self.settings.rules.enabled(&Rule::InvalidAllFormat) { + if self.settings.rules.enabled(Rule::InvalidAllFormat) { if matches!(all_names_flags, AllNamesFlags::INVALID_FORMAT) { self.diagnostics .push(pylint::rules::invalid_all_format(expr)); } } - if self.settings.rules.enabled(&Rule::InvalidAllObject) { + if self.settings.rules.enabled(Rule::InvalidAllObject) { if matches!(all_names_flags, AllNamesFlags::INVALID_OBJECT) { self.diagnostics .push(pylint::rules::invalid_all_object(expr)); @@ -4543,7 +4492,7 @@ impl<'a> Checker<'a> { if scope.bindings.remove(&id.as_str()).is_some() { return; } - if !self.settings.rules.enabled(&Rule::UndefinedName) { + if !self.settings.rules.enabled(Rule::UndefinedName) { return; } @@ -4559,7 +4508,7 @@ impl<'a> Checker<'a> { where 'b: 'a, { - if self.settings.rules.enabled(&Rule::FStringDocstring) { + if self.settings.rules.enabled(Rule::FStringDocstring) { flake8_bugbear::rules::f_string_docstring(self, python_ast); } let docstring = docstrings::extraction::docstring_from(python_ast); @@ -4606,7 +4555,7 @@ impl<'a> Checker<'a> { { if let Ok(mut expr) = parser::parse_expression(expression, "") { if in_annotation && self.ctx.annotations_future_enabled { - if self.settings.rules.enabled(&Rule::QuotedAnnotation) { + if self.settings.rules.enabled(Rule::QuotedAnnotation) { pyupgrade::rules::quoted_annotation(self, expression, range); } } @@ -4617,7 +4566,7 @@ impl<'a> Checker<'a> { if self .settings .rules - .enabled(&Rule::ForwardAnnotationSyntaxError) + .enabled(Rule::ForwardAnnotationSyntaxError) { self.diagnostics.push(Diagnostic::new( pyflakes::rules::ForwardAnnotationSyntaxError { @@ -4687,26 +4636,23 @@ impl<'a> Checker<'a> { let parent_scope_index = scopes[scopes.len() - 2]; // pyflakes - if self.settings.rules.enabled(&Rule::UnusedVariable) { + if self.settings.rules.enabled(Rule::UnusedVariable) { pyflakes::rules::unused_variable(self, scope_index); } - if self.settings.rules.enabled(&Rule::UnusedAnnotation) { + if self.settings.rules.enabled(Rule::UnusedAnnotation) { pyflakes::rules::unused_annotation(self, scope_index); } if !self.is_stub { // flake8-unused-arguments - if self.settings.rules.enabled(&Rule::UnusedFunctionArgument) - || self.settings.rules.enabled(&Rule::UnusedMethodArgument) - || self - .settings - .rules - .enabled(&Rule::UnusedClassMethodArgument) + if self.settings.rules.enabled(Rule::UnusedFunctionArgument) + || self.settings.rules.enabled(Rule::UnusedMethodArgument) + || self.settings.rules.enabled(Rule::UnusedClassMethodArgument) || self .settings .rules - .enabled(&Rule::UnusedStaticMethodArgument) - || self.settings.rules.enabled(&Rule::UnusedLambdaArgument) + .enabled(Rule::UnusedStaticMethodArgument) + || self.settings.rules.enabled(Rule::UnusedLambdaArgument) { self.diagnostics .extend(flake8_unused_arguments::rules::unused_arguments( @@ -4729,11 +4675,7 @@ impl<'a> Checker<'a> { if let StmtKind::For { target, body, .. } | StmtKind::AsyncFor { target, body, .. } = &stmt.node { - if self - .settings - .rules - .enabled(&Rule::UnusedLoopControlVariable) - { + if self.settings.rules.enabled(Rule::UnusedLoopControlVariable) { flake8_bugbear::rules::unused_loop_control_variable(self, stmt, target, body); } } else { @@ -4744,31 +4686,28 @@ impl<'a> Checker<'a> { fn check_dead_scopes(&mut self) { let enforce_typing_imports = !self.is_stub - && (self - .settings - .rules - .enabled(&Rule::GlobalVariableNotAssigned) + && (self.settings.rules.enabled(Rule::GlobalVariableNotAssigned) || self .settings .rules - .enabled(&Rule::RuntimeImportInTypeCheckingBlock) + .enabled(Rule::RuntimeImportInTypeCheckingBlock) || self .settings .rules - .enabled(&Rule::TypingOnlyFirstPartyImport) + .enabled(Rule::TypingOnlyFirstPartyImport) || self .settings .rules - .enabled(&Rule::TypingOnlyThirdPartyImport) + .enabled(Rule::TypingOnlyThirdPartyImport) || self .settings .rules - .enabled(&Rule::TypingOnlyStandardLibraryImport)); + .enabled(Rule::TypingOnlyStandardLibraryImport)); - if !(self.settings.rules.enabled(&Rule::UnusedImport) - || self.settings.rules.enabled(&Rule::ImportStarUsage) - || self.settings.rules.enabled(&Rule::RedefinedWhileUnused) - || self.settings.rules.enabled(&Rule::UndefinedExport) + if !(self.settings.rules.enabled(Rule::UnusedImport) + || self.settings.rules.enabled(Rule::ImportStarUsage) + || self.settings.rules.enabled(Rule::RedefinedWhileUnused) + || self.settings.rules.enabled(Rule::UndefinedExport) || enforce_typing_imports) { return; @@ -4847,7 +4786,7 @@ impl<'a> Checker<'a> { // F822 if *index == GLOBAL_SCOPE_INDEX { - if self.settings.rules.enabled(&Rule::UndefinedExport) { + if self.settings.rules.enabled(Rule::UndefinedExport) { if let Some((names, range)) = &all_names { diagnostics.extend(pyflakes::rules::undefined_export( names, range, self.path, scope, @@ -4857,11 +4796,7 @@ impl<'a> Checker<'a> { } // PLW0602 - if self - .settings - .rules - .enabled(&Rule::GlobalVariableNotAssigned) - { + if self.settings.rules.enabled(Rule::GlobalVariableNotAssigned) { for (name, index) in &scope.bindings { let binding = &self.ctx.bindings[*index]; if binding.kind.is_global() { @@ -4887,7 +4822,7 @@ impl<'a> Checker<'a> { // Look for any bindings that were redefined in another scope, and remain // unused. Note that we only store references in `redefinitions` if // the bindings are in different scopes. - if self.settings.rules.enabled(&Rule::RedefinedWhileUnused) { + if self.settings.rules.enabled(Rule::RedefinedWhileUnused) { for (name, index) in &scope.bindings { let binding = &self.ctx.bindings[*index]; @@ -4927,7 +4862,7 @@ impl<'a> Checker<'a> { } } - if self.settings.rules.enabled(&Rule::ImportStarUsage) { + if self.settings.rules.enabled(Rule::ImportStarUsage) { if scope.import_starred { if let Some((names, range)) = &all_names { let mut from_list = vec![]; @@ -4996,7 +4931,7 @@ impl<'a> Checker<'a> { } } - if self.settings.rules.enabled(&Rule::UnusedImport) { + if self.settings.rules.enabled(Rule::UnusedImport) { // Collect all unused imports by location. (Multiple unused imports at the same // location indicates an `import from`.) type UnusedImport<'a> = (&'a str, &'a Range); @@ -5034,9 +4969,9 @@ impl<'a> Checker<'a> { None }; - if self.rule_is_ignored(&Rule::UnusedImport, diagnostic_lineno) + if self.rule_is_ignored(Rule::UnusedImport, diagnostic_lineno) || parent_lineno.map_or(false, |parent_lineno| { - self.rule_is_ignored(&Rule::UnusedImport, parent_lineno) + self.rule_is_ignored(Rule::UnusedImport, parent_lineno) }) { ignored @@ -5060,7 +4995,7 @@ impl<'a> Checker<'a> { let child: &Stmt = defined_by.into(); let parent: Option<&Stmt> = defined_in.map(Into::into); - let fix = if !ignore_init && self.patch(&Rule::UnusedImport) { + let fix = if !ignore_init && self.patch(Rule::UnusedImport) { let deleted: Vec<&Stmt> = self.deletions.iter().map(Into::into).collect(); match autofix::helpers::remove_unused_imports( unused_imports.iter().map(|(full_name, _)| *full_name), @@ -5139,111 +5074,93 @@ impl<'a> Checker<'a> { let enforce_annotations = self .settings .rules - .enabled(&Rule::MissingTypeFunctionArgument) - || self.settings.rules.enabled(&Rule::MissingTypeArgs) - || self.settings.rules.enabled(&Rule::MissingTypeKwargs) - || self.settings.rules.enabled(&Rule::MissingTypeSelf) - || self.settings.rules.enabled(&Rule::MissingTypeCls) - || self - .settings - .rules - .enabled(&Rule::MissingReturnTypePublicFunction) - || self - .settings - .rules - .enabled(&Rule::MissingReturnTypePrivateFunction) - || self - .settings - .rules - .enabled(&Rule::MissingReturnTypeSpecialMethod) - || self - .settings - .rules - .enabled(&Rule::MissingReturnTypeStaticMethod) - || self - .settings - .rules - .enabled(&Rule::MissingReturnTypeClassMethod) - || self.settings.rules.enabled(&Rule::AnyType); - let enforce_docstrings = self.settings.rules.enabled(&Rule::PublicModule) - || self.settings.rules.enabled(&Rule::PublicClass) - || self.settings.rules.enabled(&Rule::PublicMethod) - || self.settings.rules.enabled(&Rule::PublicFunction) - || self.settings.rules.enabled(&Rule::PublicPackage) - || self.settings.rules.enabled(&Rule::MagicMethod) - || self.settings.rules.enabled(&Rule::PublicNestedClass) - || self.settings.rules.enabled(&Rule::PublicInit) - || self.settings.rules.enabled(&Rule::FitsOnOneLine) - || self - .settings - .rules - .enabled(&Rule::NoBlankLineBeforeFunction) - || self.settings.rules.enabled(&Rule::NoBlankLineAfterFunction) - || self.settings.rules.enabled(&Rule::OneBlankLineBeforeClass) - || self.settings.rules.enabled(&Rule::OneBlankLineAfterClass) - || self.settings.rules.enabled(&Rule::BlankLineAfterSummary) - || self.settings.rules.enabled(&Rule::IndentWithSpaces) - || self.settings.rules.enabled(&Rule::NoUnderIndentation) - || self.settings.rules.enabled(&Rule::NoOverIndentation) + .enabled(Rule::MissingTypeFunctionArgument) + || self.settings.rules.enabled(Rule::MissingTypeArgs) + || self.settings.rules.enabled(Rule::MissingTypeKwargs) + || self.settings.rules.enabled(Rule::MissingTypeSelf) + || self.settings.rules.enabled(Rule::MissingTypeCls) || self .settings .rules - .enabled(&Rule::NewLineAfterLastParagraph) - || self.settings.rules.enabled(&Rule::NoSurroundingWhitespace) - || self.settings.rules.enabled(&Rule::NoBlankLineBeforeClass) + .enabled(Rule::MissingReturnTypePublicFunction) || self .settings .rules - .enabled(&Rule::MultiLineSummaryFirstLine) + .enabled(Rule::MissingReturnTypePrivateFunction) || self .settings .rules - .enabled(&Rule::MultiLineSummarySecondLine) - || self.settings.rules.enabled(&Rule::SectionNotOverIndented) + .enabled(Rule::MissingReturnTypeSpecialMethod) || self .settings .rules - .enabled(&Rule::SectionUnderlineNotOverIndented) - || self.settings.rules.enabled(&Rule::TripleSingleQuotes) + .enabled(Rule::MissingReturnTypeStaticMethod) || self .settings .rules - .enabled(&Rule::EscapeSequenceInDocstring) - || self.settings.rules.enabled(&Rule::EndsInPeriod) - || self.settings.rules.enabled(&Rule::NonImperativeMood) - || self.settings.rules.enabled(&Rule::NoSignature) - || self.settings.rules.enabled(&Rule::FirstLineCapitalized) - || self.settings.rules.enabled(&Rule::DocstringStartsWithThis) - || self.settings.rules.enabled(&Rule::CapitalizeSectionName) - || self.settings.rules.enabled(&Rule::NewLineAfterSectionName) + .enabled(Rule::MissingReturnTypeClassMethod) + || self.settings.rules.enabled(Rule::AnyType); + let enforce_docstrings = self.settings.rules.enabled(Rule::PublicModule) + || self.settings.rules.enabled(Rule::PublicClass) + || self.settings.rules.enabled(Rule::PublicMethod) + || self.settings.rules.enabled(Rule::PublicFunction) + || self.settings.rules.enabled(Rule::PublicPackage) + || self.settings.rules.enabled(Rule::MagicMethod) + || self.settings.rules.enabled(Rule::PublicNestedClass) + || self.settings.rules.enabled(Rule::PublicInit) + || self.settings.rules.enabled(Rule::FitsOnOneLine) + || self.settings.rules.enabled(Rule::NoBlankLineBeforeFunction) + || self.settings.rules.enabled(Rule::NoBlankLineAfterFunction) + || self.settings.rules.enabled(Rule::OneBlankLineBeforeClass) + || self.settings.rules.enabled(Rule::OneBlankLineAfterClass) + || self.settings.rules.enabled(Rule::BlankLineAfterSummary) + || self.settings.rules.enabled(Rule::IndentWithSpaces) + || self.settings.rules.enabled(Rule::NoUnderIndentation) + || self.settings.rules.enabled(Rule::NoOverIndentation) + || self.settings.rules.enabled(Rule::NewLineAfterLastParagraph) + || self.settings.rules.enabled(Rule::NoSurroundingWhitespace) + || self.settings.rules.enabled(Rule::NoBlankLineBeforeClass) + || self.settings.rules.enabled(Rule::MultiLineSummaryFirstLine) || self .settings .rules - .enabled(&Rule::DashedUnderlineAfterSection) + .enabled(Rule::MultiLineSummarySecondLine) + || self.settings.rules.enabled(Rule::SectionNotOverIndented) || self .settings .rules - .enabled(&Rule::SectionUnderlineAfterName) + .enabled(Rule::SectionUnderlineNotOverIndented) + || self.settings.rules.enabled(Rule::TripleSingleQuotes) + || self.settings.rules.enabled(Rule::EscapeSequenceInDocstring) + || self.settings.rules.enabled(Rule::EndsInPeriod) + || self.settings.rules.enabled(Rule::NonImperativeMood) + || self.settings.rules.enabled(Rule::NoSignature) + || self.settings.rules.enabled(Rule::FirstLineCapitalized) + || self.settings.rules.enabled(Rule::DocstringStartsWithThis) + || self.settings.rules.enabled(Rule::CapitalizeSectionName) + || self.settings.rules.enabled(Rule::NewLineAfterSectionName) || self .settings .rules - .enabled(&Rule::SectionUnderlineMatchesSectionLength) - || self.settings.rules.enabled(&Rule::BlankLineAfterSection) - || self.settings.rules.enabled(&Rule::BlankLineBeforeSection) + .enabled(Rule::DashedUnderlineAfterSection) + || self.settings.rules.enabled(Rule::SectionUnderlineAfterName) || self .settings .rules - .enabled(&Rule::NoBlankLinesBetweenHeaderAndContent) + .enabled(Rule::SectionUnderlineMatchesSectionLength) + || self.settings.rules.enabled(Rule::BlankLineAfterSection) + || self.settings.rules.enabled(Rule::BlankLineBeforeSection) || self .settings .rules - .enabled(&Rule::BlankLineAfterLastSection) - || self.settings.rules.enabled(&Rule::EmptyDocstringSection) - || self.settings.rules.enabled(&Rule::EndsInPunctuation) - || self.settings.rules.enabled(&Rule::SectionNameEndsInColon) - || self.settings.rules.enabled(&Rule::UndocumentedParam) - || self.settings.rules.enabled(&Rule::OverloadWithDocstring) - || self.settings.rules.enabled(&Rule::EmptyDocstring); + .enabled(Rule::NoBlankLinesBetweenHeaderAndContent) + || self.settings.rules.enabled(Rule::BlankLineAfterLastSection) + || self.settings.rules.enabled(Rule::EmptyDocstringSection) + || self.settings.rules.enabled(Rule::EndsInPunctuation) + || self.settings.rules.enabled(Rule::SectionNameEndsInColon) + || self.settings.rules.enabled(Rule::UndocumentedParam) + || self.settings.rules.enabled(Rule::OverloadWithDocstring) + || self.settings.rules.enabled(Rule::EmptyDocstring); let mut overloaded_name: Option = None; self.deferred.definitions.reverse(); @@ -5277,7 +5194,7 @@ impl<'a> Checker<'a> { overloaded_name = flake8_annotations::helpers::overloaded_name(self, &definition); } if self.is_stub { - if self.settings.rules.enabled(&Rule::DocstringInStub) { + if self.settings.rules.enabled(Rule::DocstringInStub) { flake8_pyi::rules::docstring_in_stubs(self, definition.docstring); } } @@ -5318,124 +5235,101 @@ impl<'a> Checker<'a> { continue; } - if self.settings.rules.enabled(&Rule::FitsOnOneLine) { + if self.settings.rules.enabled(Rule::FitsOnOneLine) { pydocstyle::rules::one_liner(self, &docstring); } - if self - .settings - .rules - .enabled(&Rule::NoBlankLineBeforeFunction) - || self.settings.rules.enabled(&Rule::NoBlankLineAfterFunction) + if self.settings.rules.enabled(Rule::NoBlankLineBeforeFunction) + || self.settings.rules.enabled(Rule::NoBlankLineAfterFunction) { pydocstyle::rules::blank_before_after_function(self, &docstring); } - if self.settings.rules.enabled(&Rule::OneBlankLineBeforeClass) - || self.settings.rules.enabled(&Rule::OneBlankLineAfterClass) - || self.settings.rules.enabled(&Rule::NoBlankLineBeforeClass) + if self.settings.rules.enabled(Rule::OneBlankLineBeforeClass) + || self.settings.rules.enabled(Rule::OneBlankLineAfterClass) + || self.settings.rules.enabled(Rule::NoBlankLineBeforeClass) { pydocstyle::rules::blank_before_after_class(self, &docstring); } - if self.settings.rules.enabled(&Rule::BlankLineAfterSummary) { + if self.settings.rules.enabled(Rule::BlankLineAfterSummary) { pydocstyle::rules::blank_after_summary(self, &docstring); } - if self.settings.rules.enabled(&Rule::IndentWithSpaces) - || self.settings.rules.enabled(&Rule::NoUnderIndentation) - || self.settings.rules.enabled(&Rule::NoOverIndentation) + if self.settings.rules.enabled(Rule::IndentWithSpaces) + || self.settings.rules.enabled(Rule::NoUnderIndentation) + || self.settings.rules.enabled(Rule::NoOverIndentation) { pydocstyle::rules::indent(self, &docstring); } - if self - .settings - .rules - .enabled(&Rule::NewLineAfterLastParagraph) - { + if self.settings.rules.enabled(Rule::NewLineAfterLastParagraph) { pydocstyle::rules::newline_after_last_paragraph(self, &docstring); } - if self.settings.rules.enabled(&Rule::NoSurroundingWhitespace) { + if self.settings.rules.enabled(Rule::NoSurroundingWhitespace) { pydocstyle::rules::no_surrounding_whitespace(self, &docstring); } - if self - .settings - .rules - .enabled(&Rule::MultiLineSummaryFirstLine) + if self.settings.rules.enabled(Rule::MultiLineSummaryFirstLine) || self .settings .rules - .enabled(&Rule::MultiLineSummarySecondLine) + .enabled(Rule::MultiLineSummarySecondLine) { pydocstyle::rules::multi_line_summary_start(self, &docstring); } - if self.settings.rules.enabled(&Rule::TripleSingleQuotes) { + if self.settings.rules.enabled(Rule::TripleSingleQuotes) { pydocstyle::rules::triple_quotes(self, &docstring); } - if self - .settings - .rules - .enabled(&Rule::EscapeSequenceInDocstring) - { + if self.settings.rules.enabled(Rule::EscapeSequenceInDocstring) { pydocstyle::rules::backslashes(self, &docstring); } - if self.settings.rules.enabled(&Rule::EndsInPeriod) { + if self.settings.rules.enabled(Rule::EndsInPeriod) { pydocstyle::rules::ends_with_period(self, &docstring); } - if self.settings.rules.enabled(&Rule::NonImperativeMood) { + if self.settings.rules.enabled(Rule::NonImperativeMood) { pydocstyle::rules::non_imperative_mood( self, &docstring, &self.settings.pydocstyle.property_decorators, ); } - if self.settings.rules.enabled(&Rule::NoSignature) { + if self.settings.rules.enabled(Rule::NoSignature) { pydocstyle::rules::no_signature(self, &docstring); } - if self.settings.rules.enabled(&Rule::FirstLineCapitalized) { + if self.settings.rules.enabled(Rule::FirstLineCapitalized) { pydocstyle::rules::capitalized(self, &docstring); } - if self.settings.rules.enabled(&Rule::DocstringStartsWithThis) { + if self.settings.rules.enabled(Rule::DocstringStartsWithThis) { pydocstyle::rules::starts_with_this(self, &docstring); } - if self.settings.rules.enabled(&Rule::EndsInPunctuation) { + if self.settings.rules.enabled(Rule::EndsInPunctuation) { pydocstyle::rules::ends_with_punctuation(self, &docstring); } - if self.settings.rules.enabled(&Rule::OverloadWithDocstring) { + if self.settings.rules.enabled(Rule::OverloadWithDocstring) { pydocstyle::rules::if_needed(self, &docstring); } - if self - .settings - .rules - .enabled(&Rule::MultiLineSummaryFirstLine) - || self.settings.rules.enabled(&Rule::SectionNotOverIndented) + if self.settings.rules.enabled(Rule::MultiLineSummaryFirstLine) + || self.settings.rules.enabled(Rule::SectionNotOverIndented) || self .settings .rules - .enabled(&Rule::SectionUnderlineNotOverIndented) - || self.settings.rules.enabled(&Rule::CapitalizeSectionName) - || self.settings.rules.enabled(&Rule::NewLineAfterSectionName) + .enabled(Rule::SectionUnderlineNotOverIndented) + || self.settings.rules.enabled(Rule::CapitalizeSectionName) + || self.settings.rules.enabled(Rule::NewLineAfterSectionName) || self .settings .rules - .enabled(&Rule::DashedUnderlineAfterSection) + .enabled(Rule::DashedUnderlineAfterSection) + || self.settings.rules.enabled(Rule::SectionUnderlineAfterName) || self .settings .rules - .enabled(&Rule::SectionUnderlineAfterName) + .enabled(Rule::SectionUnderlineMatchesSectionLength) + || self.settings.rules.enabled(Rule::BlankLineAfterSection) + || self.settings.rules.enabled(Rule::BlankLineBeforeSection) || self .settings .rules - .enabled(&Rule::SectionUnderlineMatchesSectionLength) - || self.settings.rules.enabled(&Rule::BlankLineAfterSection) - || self.settings.rules.enabled(&Rule::BlankLineBeforeSection) - || self - .settings - .rules - .enabled(&Rule::NoBlankLinesBetweenHeaderAndContent) - || self - .settings - .rules - .enabled(&Rule::BlankLineAfterLastSection) - || self.settings.rules.enabled(&Rule::EmptyDocstringSection) - || self.settings.rules.enabled(&Rule::SectionNameEndsInColon) - || self.settings.rules.enabled(&Rule::UndocumentedParam) + .enabled(Rule::NoBlankLinesBetweenHeaderAndContent) + || self.settings.rules.enabled(Rule::BlankLineAfterLastSection) + || self.settings.rules.enabled(Rule::EmptyDocstringSection) + || self.settings.rules.enabled(Rule::SectionNameEndsInColon) + || self.settings.rules.enabled(Rule::UndocumentedParam) { pydocstyle::rules::sections( self, @@ -5449,11 +5343,7 @@ impl<'a> Checker<'a> { fn check_builtin_shadowing(&mut self, name: &str, located: &Located, is_attribute: bool) { if is_attribute && matches!(self.ctx.current_scope().kind, ScopeKind::Class(_)) { - if self - .settings - .rules - .enabled(&Rule::BuiltinAttributeShadowing) - { + if self.settings.rules.enabled(Rule::BuiltinAttributeShadowing) { if let Some(diagnostic) = flake8_builtins::rules::builtin_shadowing( name, located, @@ -5464,7 +5354,7 @@ impl<'a> Checker<'a> { } } } else { - if self.settings.rules.enabled(&Rule::BuiltinVariableShadowing) { + if self.settings.rules.enabled(Rule::BuiltinVariableShadowing) { if let Some(diagnostic) = flake8_builtins::rules::builtin_shadowing( name, located, @@ -5478,7 +5368,7 @@ impl<'a> Checker<'a> { } fn check_builtin_arg_shadowing(&mut self, name: &str, arg: &Arg) { - if self.settings.rules.enabled(&Rule::BuiltinArgumentShadowing) { + if self.settings.rules.enabled(Rule::BuiltinArgumentShadowing) { if let Some(diagnostic) = flake8_builtins::rules::builtin_shadowing( name, arg, diff --git a/crates/ruff/src/checkers/filesystem.rs b/crates/ruff/src/checkers/filesystem.rs index 5e8405ce69cb1..0238b6b8946f3 100644 --- a/crates/ruff/src/checkers/filesystem.rs +++ b/crates/ruff/src/checkers/filesystem.rs @@ -15,7 +15,7 @@ pub fn check_file_path( let mut diagnostics: Vec = vec![]; // flake8-no-pep420 - if settings.rules.enabled(&Rule::ImplicitNamespacePackage) { + if settings.rules.enabled(Rule::ImplicitNamespacePackage) { if let Some(diagnostic) = implicit_namespace_package(path, package, &settings.project_root, &settings.src) { @@ -24,7 +24,7 @@ pub fn check_file_path( } // pep8-naming - if settings.rules.enabled(&Rule::InvalidModuleName) { + if settings.rules.enabled(Rule::InvalidModuleName) { if let Some(diagnostic) = invalid_module_name(path, package) { diagnostics.push(diagnostic); } diff --git a/crates/ruff/src/checkers/imports.rs b/crates/ruff/src/checkers/imports.rs index cfa1e6fc6698b..9c174c7880d69 100644 --- a/crates/ruff/src/checkers/imports.rs +++ b/crates/ruff/src/checkers/imports.rs @@ -38,7 +38,7 @@ pub fn check_imports( // Enforce import rules. let mut diagnostics = vec![]; - if settings.rules.enabled(&Rule::UnsortedImports) { + if settings.rules.enabled(Rule::UnsortedImports) { for block in &blocks { if !block.imports.is_empty() { if let Some(diagnostic) = isort::rules::organize_imports( @@ -49,7 +49,7 @@ pub fn check_imports( } } } - if settings.rules.enabled(&Rule::MissingRequiredImport) { + if settings.rules.enabled(Rule::MissingRequiredImport) { diagnostics.extend(isort::rules::add_required_imports( &blocks, python_ast, locator, stylist, settings, autofix, )); diff --git a/crates/ruff/src/checkers/logical_lines.rs b/crates/ruff/src/checkers/logical_lines.rs index 5a6d101f3f891..3337fae95e123 100644 --- a/crates/ruff/src/checkers/logical_lines.rs +++ b/crates/ruff/src/checkers/logical_lines.rs @@ -166,7 +166,7 @@ pub fn check_logical_lines( } #[cfg(feature = "logical_lines")] - let should_fix = autofix.into() && settings.rules.should_fix(&Rule::MissingWhitespace); + let should_fix = autofix.into() && settings.rules.should_fix(Rule::MissingWhitespace); #[cfg(not(feature = "logical_lines"))] let should_fix = false; @@ -181,7 +181,7 @@ pub fn check_logical_lines( if line.flags.contains(TokenFlags::BRACKET) { #[cfg(feature = "logical_lines")] let should_fix = - autofix.into() && settings.rules.should_fix(&Rule::WhitespaceBeforeParameters); + autofix.into() && settings.rules.should_fix(Rule::WhitespaceBeforeParameters); #[cfg(not(feature = "logical_lines"))] let should_fix = false; diff --git a/crates/ruff/src/checkers/noqa.rs b/crates/ruff/src/checkers/noqa.rs index 0d8790a230b76..d9742a744d499 100644 --- a/crates/ruff/src/checkers/noqa.rs +++ b/crates/ruff/src/checkers/noqa.rs @@ -24,7 +24,7 @@ pub fn check_noqa( settings: &Settings, autofix: flags::Autofix, ) -> Vec { - let enforce_noqa = settings.rules.enabled(&Rule::UnusedNOQA); + let enforce_noqa = settings.rules.enabled(Rule::UnusedNOQA); // Whether the file is exempted from all checks. let mut file_exempted = false; @@ -188,7 +188,7 @@ pub fn check_noqa( valid_codes.push(code); } else { if let Ok(rule) = Rule::from_code(code) { - if settings.rules.enabled(&rule) { + if settings.rules.enabled(rule) { unmatched_codes.push(code); } else { disabled_codes.push(code); diff --git a/crates/ruff/src/checkers/physical_lines.rs b/crates/ruff/src/checkers/physical_lines.rs index adb2963e40494..cba4861f526e5 100644 --- a/crates/ruff/src/checkers/physical_lines.rs +++ b/crates/ruff/src/checkers/physical_lines.rs @@ -32,28 +32,28 @@ pub fn check_physical_lines( let mut diagnostics: Vec = vec![]; let mut has_any_shebang = false; - let enforce_blanket_noqa = settings.rules.enabled(&Rule::BlanketNOQA); - let enforce_shebang_not_executable = settings.rules.enabled(&Rule::ShebangNotExecutable); - let enforce_shebang_missing = settings.rules.enabled(&Rule::ShebangMissingExecutableFile); - let enforce_shebang_whitespace = settings.rules.enabled(&Rule::ShebangWhitespace); - let enforce_shebang_newline = settings.rules.enabled(&Rule::ShebangNewline); - let enforce_shebang_python = settings.rules.enabled(&Rule::ShebangPython); - let enforce_blanket_type_ignore = settings.rules.enabled(&Rule::BlanketTypeIgnore); - let enforce_doc_line_too_long = settings.rules.enabled(&Rule::DocLineTooLong); - let enforce_line_too_long = settings.rules.enabled(&Rule::LineTooLong); - let enforce_no_newline_at_end_of_file = settings.rules.enabled(&Rule::NoNewLineAtEndOfFile); - let enforce_unnecessary_coding_comment = settings.rules.enabled(&Rule::UTF8EncodingDeclaration); - let enforce_mixed_spaces_and_tabs = settings.rules.enabled(&Rule::MixedSpacesAndTabs); - let enforce_bidirectional_unicode = settings.rules.enabled(&Rule::BidirectionalUnicode); - let enforce_trailing_whitespace = settings.rules.enabled(&Rule::TrailingWhitespace); + let enforce_blanket_noqa = settings.rules.enabled(Rule::BlanketNOQA); + let enforce_shebang_not_executable = settings.rules.enabled(Rule::ShebangNotExecutable); + let enforce_shebang_missing = settings.rules.enabled(Rule::ShebangMissingExecutableFile); + let enforce_shebang_whitespace = settings.rules.enabled(Rule::ShebangWhitespace); + let enforce_shebang_newline = settings.rules.enabled(Rule::ShebangNewline); + let enforce_shebang_python = settings.rules.enabled(Rule::ShebangPython); + let enforce_blanket_type_ignore = settings.rules.enabled(Rule::BlanketTypeIgnore); + let enforce_doc_line_too_long = settings.rules.enabled(Rule::DocLineTooLong); + let enforce_line_too_long = settings.rules.enabled(Rule::LineTooLong); + let enforce_no_newline_at_end_of_file = settings.rules.enabled(Rule::NoNewLineAtEndOfFile); + let enforce_unnecessary_coding_comment = settings.rules.enabled(Rule::UTF8EncodingDeclaration); + let enforce_mixed_spaces_and_tabs = settings.rules.enabled(Rule::MixedSpacesAndTabs); + let enforce_bidirectional_unicode = settings.rules.enabled(Rule::BidirectionalUnicode); + let enforce_trailing_whitespace = settings.rules.enabled(Rule::TrailingWhitespace); let enforce_blank_line_contains_whitespace = - settings.rules.enabled(&Rule::BlankLineContainsWhitespace); - let enforce_indentation_contains_tabs = settings.rules.enabled(&Rule::IndentationContainsTabs); + settings.rules.enabled(Rule::BlankLineContainsWhitespace); + let enforce_indentation_contains_tabs = settings.rules.enabled(Rule::IndentationContainsTabs); let fix_unnecessary_coding_comment = - autofix.into() && settings.rules.should_fix(&Rule::UTF8EncodingDeclaration); + autofix.into() && settings.rules.should_fix(Rule::UTF8EncodingDeclaration); let fix_shebang_whitespace = - autofix.into() && settings.rules.should_fix(&Rule::ShebangWhitespace); + autofix.into() && settings.rules.should_fix(Rule::ShebangWhitespace); let mut commented_lines_iter = commented_lines.iter().peekable(); let mut doc_lines_iter = doc_lines.iter().peekable(); @@ -165,7 +165,7 @@ pub fn check_physical_lines( if let Some(diagnostic) = no_newline_at_end_of_file( locator, stylist, - autofix.into() && settings.rules.should_fix(&Rule::NoNewLineAtEndOfFile), + autofix.into() && settings.rules.should_fix(Rule::NoNewLineAtEndOfFile), ) { diagnostics.push(diagnostic); } diff --git a/crates/ruff/src/checkers/tokens.rs b/crates/ruff/src/checkers/tokens.rs index c2d093b90d398..bffdc85c4e975 100644 --- a/crates/ruff/src/checkers/tokens.rs +++ b/crates/ruff/src/checkers/tokens.rs @@ -25,39 +25,39 @@ pub fn check_tokens( let enforce_ambiguous_unicode_character = settings .rules - .enabled(&Rule::AmbiguousUnicodeCharacterString) + .enabled(Rule::AmbiguousUnicodeCharacterString) || settings .rules - .enabled(&Rule::AmbiguousUnicodeCharacterDocstring) + .enabled(Rule::AmbiguousUnicodeCharacterDocstring) || settings .rules - .enabled(&Rule::AmbiguousUnicodeCharacterComment); - let enforce_quotes = settings.rules.enabled(&Rule::BadQuotesInlineString) - || settings.rules.enabled(&Rule::BadQuotesMultilineString) - || settings.rules.enabled(&Rule::BadQuotesDocstring) - || settings.rules.enabled(&Rule::AvoidableEscapedQuote); - let enforce_commented_out_code = settings.rules.enabled(&Rule::CommentedOutCode); + .enabled(Rule::AmbiguousUnicodeCharacterComment); + let enforce_quotes = settings.rules.enabled(Rule::BadQuotesInlineString) + || settings.rules.enabled(Rule::BadQuotesMultilineString) + || settings.rules.enabled(Rule::BadQuotesDocstring) + || settings.rules.enabled(Rule::AvoidableEscapedQuote); + let enforce_commented_out_code = settings.rules.enabled(Rule::CommentedOutCode); let enforce_compound_statements = settings .rules - .enabled(&Rule::MultipleStatementsOnOneLineColon) + .enabled(Rule::MultipleStatementsOnOneLineColon) || settings .rules - .enabled(&Rule::MultipleStatementsOnOneLineSemicolon) - || settings.rules.enabled(&Rule::UselessSemicolon); - let enforce_invalid_escape_sequence = settings.rules.enabled(&Rule::InvalidEscapeSequence); + .enabled(Rule::MultipleStatementsOnOneLineSemicolon) + || settings.rules.enabled(Rule::UselessSemicolon); + let enforce_invalid_escape_sequence = settings.rules.enabled(Rule::InvalidEscapeSequence); let enforce_implicit_string_concatenation = settings .rules - .enabled(&Rule::SingleLineImplicitStringConcatenation) + .enabled(Rule::SingleLineImplicitStringConcatenation) || settings .rules - .enabled(&Rule::MultiLineImplicitStringConcatenation); - let enforce_trailing_comma = settings.rules.enabled(&Rule::TrailingCommaMissing) + .enabled(Rule::MultiLineImplicitStringConcatenation); + let enforce_trailing_comma = settings.rules.enabled(Rule::TrailingCommaMissing) || settings .rules - .enabled(&Rule::TrailingCommaOnBareTupleProhibited) - || settings.rules.enabled(&Rule::TrailingCommaProhibited); - let enforce_extraneous_parenthesis = settings.rules.enabled(&Rule::ExtraneousParentheses); - let enforce_type_comment_in_stub = settings.rules.enabled(&Rule::TypeCommentInStub); + .enabled(Rule::TrailingCommaOnBareTupleProhibited) + || settings.rules.enabled(Rule::TrailingCommaProhibited); + let enforce_extraneous_parenthesis = settings.rules.enabled(Rule::ExtraneousParentheses); + let enforce_type_comment_in_stub = settings.rules.enabled(Rule::TypeCommentInStub); // RUF001, RUF002, RUF003 if enforce_ambiguous_unicode_character { @@ -111,7 +111,7 @@ pub fn check_tokens( locator, *start, *end, - autofix.into() && settings.rules.should_fix(&Rule::InvalidEscapeSequence), + autofix.into() && settings.rules.should_fix(Rule::InvalidEscapeSequence), )); } } diff --git a/crates/ruff/src/fs.rs b/crates/ruff/src/fs.rs index 8e8dd2acc1913..a580a5cf411dc 100644 --- a/crates/ruff/src/fs.rs +++ b/crates/ruff/src/fs.rs @@ -22,11 +22,12 @@ pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> { } /// Create a set with codes matching the pattern/code pairs. -pub(crate) fn ignores_from_path<'a>( +pub(crate) fn ignores_from_path( path: &Path, - pattern_code_pairs: &'a [(GlobMatcher, GlobMatcher, FxHashSet)], -) -> FxHashSet<&'a Rule> { + pattern_code_pairs: &[(GlobMatcher, GlobMatcher, FxHashSet)], +) -> FxHashSet { let (file_path, file_basename) = extract_path_names(path).expect("Unable to parse filename"); + pattern_code_pairs .iter() .filter_map(|(absolute, basename, codes)| { @@ -37,20 +38,21 @@ pub(crate) fn ignores_from_path<'a>( basename.glob().regex(), codes ); - return Some(codes.iter()); - } - if absolute.is_match(file_path) { + Some(codes) + } else if absolute.is_match(file_path) { debug!( "Adding per-file ignores for {:?} due to absolute match on {:?}: {:?}", path, absolute.glob().regex(), codes ); - return Some(codes.iter()); + Some(codes) + } else { + None } - None }) .flatten() + .copied() .collect() } diff --git a/crates/ruff/src/linter.rs b/crates/ruff/src/linter.rs index 1ad51a0893602..c2bdbfdb4a804 100644 --- a/crates/ruff/src/linter.rs +++ b/crates/ruff/src/linter.rs @@ -50,7 +50,7 @@ impl LinterResult { } } -pub type FixTable = FxHashMap<&'static Rule, usize>; +pub type FixTable = FxHashMap; /// Generate `Diagnostic`s from the source code contents at the /// given `Path`. @@ -74,7 +74,7 @@ pub fn check_path( // Collect doc lines. This requires a rare mix of tokens (for comments) and AST // (for docstrings), which demands special-casing at this level. - let use_doc_lines = settings.rules.enabled(&Rule::DocLineTooLong); + let use_doc_lines = settings.rules.enabled(Rule::DocLineTooLong); let mut doc_lines = vec![]; if use_doc_lines { doc_lines.extend(doc_lines_from_tokens(&tokens)); @@ -159,14 +159,14 @@ pub fn check_path( } } Err(parse_error) => { - if settings.rules.enabled(&Rule::SyntaxError) { + if settings.rules.enabled(Rule::SyntaxError) { pycodestyle::rules::syntax_error(&mut diagnostics, &parse_error); } // If the syntax error is ignored, suppress it (regardless of whether // `Rule::SyntaxError` is enabled). if !rule_is_ignored( - &Rule::SyntaxError, + Rule::SyntaxError, parse_error.location.row(), &directives.noqa_line_for, locator, @@ -204,7 +204,7 @@ pub fn check_path( if !diagnostics.is_empty() && !settings.per_file_ignores.is_empty() { let ignores = fs::ignores_from_path(path, &settings.per_file_ignores); if !ignores.is_empty() { - diagnostics.retain(|diagnostic| !ignores.contains(diagnostic.kind.rule())); + diagnostics.retain(|diagnostic| !ignores.contains(&diagnostic.kind.rule())); } }; diff --git a/crates/ruff/src/noqa.rs b/crates/ruff/src/noqa.rs index 844a64fe5d40f..a20844df772e4 100644 --- a/crates/ruff/src/noqa.rs +++ b/crates/ruff/src/noqa.rs @@ -121,7 +121,7 @@ pub fn extract_noqa_directive(line: &str) -> Directive { /// Returns `true` if the string list of `codes` includes `code` (or an alias /// thereof). -pub fn includes(needle: &Rule, haystack: &[&str]) -> bool { +pub fn includes(needle: Rule, haystack: &[&str]) -> bool { let needle = needle.noqa_code(); haystack .iter() @@ -130,7 +130,7 @@ pub fn includes(needle: &Rule, haystack: &[&str]) -> bool { /// Returns `true` if the given [`Rule`] is ignored at the specified `lineno`. pub fn rule_is_ignored( - code: &Rule, + code: Rule, lineno: usize, noqa_line_for: &IntMap, locator: &Locator, @@ -174,7 +174,7 @@ fn add_noqa_inner( line_ending: &LineEnding, ) -> (usize, String) { // Map of line number to set of (non-ignored) diagnostic codes that are triggered on that line. - let mut matches_by_line: FxHashMap> = FxHashMap::default(); + let mut matches_by_line: FxHashMap> = FxHashMap::default(); // Whether the file is exempted from all checks. let mut file_exempted = false; @@ -280,7 +280,7 @@ fn add_noqa_inner( output.push_str(" # noqa: "); // Add codes. - push_codes(&mut output, rules.iter().map(|r| r.noqa_code())); + push_codes(&mut output, rules.iter().map(Rule::noqa_code)); output.push_str(line_ending); count += 1; } diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 426481818e1ae..32453d9cefa7f 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -610,6 +610,10 @@ ruff_macros::register_rules!( rules::flake8_django::rules::NonLeadingReceiverDecorator, ); +pub trait AsRule { + fn rule(&self) -> Rule; +} + impl Rule { pub fn from_code(code: &str) -> Result { let (linter, code) = Linter::parse_code(code).ok_or(FromCodeError::Unknown)?; @@ -916,6 +920,7 @@ pub const INCOMPATIBLE_CODES: &[(Rule, Rule, &str); 2] = &[ #[cfg(test)] mod tests { + use std::mem::size_of; use strum::IntoEnumIterator; use super::{Linter, Rule, RuleNamespace}; @@ -961,4 +966,9 @@ mod tests { assert_eq!(code, format!("{}{rest}", linter.common_prefix())); } } + + #[test] + fn rule_size() { + assert_eq!(2, size_of::()); + } } diff --git a/crates/ruff/src/rules/eradicate/rules.rs b/crates/ruff/src/rules/eradicate/rules.rs index 4a364c15062c4..05661ca24a8c6 100644 --- a/crates/ruff/src/rules/eradicate/rules.rs +++ b/crates/ruff/src/rules/eradicate/rules.rs @@ -61,7 +61,7 @@ pub fn commented_out_code( // Verify that the comment is on its own line, and that it contains code. if is_standalone_comment(line) && comment_contains_code(line, &settings.task_tags[..]) { let mut diagnostic = Diagnostic::new(CommentedOutCode, Range::new(start, end)); - if autofix.into() && settings.rules.should_fix(&Rule::CommentedOutCode) { + if autofix.into() && settings.rules.should_fix(Rule::CommentedOutCode) { diagnostic.amend(Fix::deletion(location, end_location)); } Some(diagnostic) diff --git a/crates/ruff/src/rules/flake8_2020/rules.rs b/crates/ruff/src/rules/flake8_2020/rules.rs index 54c561f59ab39..4ee4f6398fb9a 100644 --- a/crates/ruff/src/rules/flake8_2020/rules.rs +++ b/crates/ruff/src/rules/flake8_2020/rules.rs @@ -140,7 +140,7 @@ pub fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) { && checker .settings .rules - .enabled(&Rule::SysVersionSlice1Referenced) + .enabled(Rule::SysVersionSlice1Referenced) { checker.diagnostics.push(Diagnostic::new( SysVersionSlice1Referenced, @@ -150,7 +150,7 @@ pub fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) { && checker .settings .rules - .enabled(&Rule::SysVersionSlice3Referenced) + .enabled(Rule::SysVersionSlice3Referenced) { checker.diagnostics.push(Diagnostic::new( SysVersionSlice3Referenced, @@ -165,13 +165,13 @@ pub fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) { .. } => { if *i == BigInt::from(2) - && checker.settings.rules.enabled(&Rule::SysVersion2Referenced) + && checker.settings.rules.enabled(Rule::SysVersion2Referenced) { checker .diagnostics .push(Diagnostic::new(SysVersion2Referenced, Range::from(value))); } else if *i == BigInt::from(0) - && checker.settings.rules.enabled(&Rule::SysVersion0Referenced) + && checker.settings.rules.enabled(Rule::SysVersion0Referenced) { checker .diagnostics @@ -210,7 +210,7 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: & && checker .settings .rules - .enabled(&Rule::SysVersionInfo0Eq3Referenced) + .enabled(Rule::SysVersionInfo0Eq3Referenced) { checker.diagnostics.push(Diagnostic::new( SysVersionInfo0Eq3Referenced, @@ -231,7 +231,7 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: & }], ) = (ops, comparators) { - if checker.settings.rules.enabled(&Rule::SysVersionInfo1CmpInt) { + if checker.settings.rules.enabled(Rule::SysVersionInfo1CmpInt) { checker .diagnostics .push(Diagnostic::new(SysVersionInfo1CmpInt, Range::from(left))); @@ -259,7 +259,7 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: & if checker .settings .rules - .enabled(&Rule::SysVersionInfoMinorCmpInt) + .enabled(Rule::SysVersionInfoMinorCmpInt) { checker.diagnostics.push(Diagnostic::new( SysVersionInfoMinorCmpInt, @@ -286,12 +286,12 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: & ) = (ops, comparators) { if s.len() == 1 { - if checker.settings.rules.enabled(&Rule::SysVersionCmpStr10) { + if checker.settings.rules.enabled(Rule::SysVersionCmpStr10) { checker .diagnostics .push(Diagnostic::new(SysVersionCmpStr10, Range::from(left))); } - } else if checker.settings.rules.enabled(&Rule::SysVersionCmpStr3) { + } else if checker.settings.rules.enabled(Rule::SysVersionCmpStr3) { checker .diagnostics .push(Diagnostic::new(SysVersionCmpStr3, Range::from(left))); diff --git a/crates/ruff/src/rules/flake8_annotations/rules.rs b/crates/ruff/src/rules/flake8_annotations/rules.rs index 626b289695263..fac4201b44e61 100644 --- a/crates/ruff/src/rules/flake8_annotations/rules.rs +++ b/crates/ruff/src/rules/flake8_annotations/rules.rs @@ -492,7 +492,7 @@ pub fn definition( // ANN401 for dynamically typed arguments if let Some(annotation) = &arg.node.annotation { has_any_typed_arg = true; - if checker.settings.rules.enabled(&Rule::AnyType) { + if checker.settings.rules.enabled(Rule::AnyType) { check_dynamically_typed( checker, annotation, @@ -507,7 +507,7 @@ pub fn definition( if checker .settings .rules - .enabled(&Rule::MissingTypeFunctionArgument) + .enabled(Rule::MissingTypeFunctionArgument) { diagnostics.push(Diagnostic::new( MissingTypeFunctionArgument { @@ -525,7 +525,7 @@ pub fn definition( if let Some(expr) = &arg.node.annotation { has_any_typed_arg = true; if !checker.settings.flake8_annotations.allow_star_arg_any { - if checker.settings.rules.enabled(&Rule::AnyType) { + if checker.settings.rules.enabled(Rule::AnyType) { let name = &arg.node.arg; check_dynamically_typed( checker, @@ -539,7 +539,7 @@ pub fn definition( if !(checker.settings.flake8_annotations.suppress_dummy_args && checker.settings.dummy_variable_rgx.is_match(&arg.node.arg)) { - if checker.settings.rules.enabled(&Rule::MissingTypeArgs) { + if checker.settings.rules.enabled(Rule::MissingTypeArgs) { diagnostics.push(Diagnostic::new( MissingTypeArgs { name: arg.node.arg.to_string(), @@ -556,7 +556,7 @@ pub fn definition( if let Some(expr) = &arg.node.annotation { has_any_typed_arg = true; if !checker.settings.flake8_annotations.allow_star_arg_any { - if checker.settings.rules.enabled(&Rule::AnyType) { + if checker.settings.rules.enabled(Rule::AnyType) { let name = &arg.node.arg; check_dynamically_typed( checker, @@ -570,7 +570,7 @@ pub fn definition( if !(checker.settings.flake8_annotations.suppress_dummy_args && checker.settings.dummy_variable_rgx.is_match(&arg.node.arg)) { - if checker.settings.rules.enabled(&Rule::MissingTypeKwargs) { + if checker.settings.rules.enabled(Rule::MissingTypeKwargs) { diagnostics.push(Diagnostic::new( MissingTypeKwargs { name: arg.node.arg.to_string(), @@ -587,7 +587,7 @@ pub fn definition( if let Some(arg) = args.posonlyargs.first().or_else(|| args.args.first()) { if arg.node.annotation.is_none() { if visibility::is_classmethod(&checker.ctx, cast::decorator_list(stmt)) { - if checker.settings.rules.enabled(&Rule::MissingTypeCls) { + if checker.settings.rules.enabled(Rule::MissingTypeCls) { diagnostics.push(Diagnostic::new( MissingTypeCls { name: arg.node.arg.to_string(), @@ -596,7 +596,7 @@ pub fn definition( )); } } else { - if checker.settings.rules.enabled(&Rule::MissingTypeSelf) { + if checker.settings.rules.enabled(Rule::MissingTypeSelf) { diagnostics.push(Diagnostic::new( MissingTypeSelf { name: arg.node.arg.to_string(), @@ -614,7 +614,7 @@ pub fn definition( // ANN201, ANN202, ANN401 if let Some(expr) = &returns { has_typed_return = true; - if checker.settings.rules.enabled(&Rule::AnyType) { + if checker.settings.rules.enabled(Rule::AnyType) { check_dynamically_typed(checker, expr, || name.to_string(), &mut diagnostics); } } else if !( @@ -626,7 +626,7 @@ pub fn definition( if checker .settings .rules - .enabled(&Rule::MissingReturnTypeClassMethod) + .enabled(Rule::MissingReturnTypeClassMethod) { diagnostics.push(Diagnostic::new( MissingReturnTypeClassMethod { @@ -641,7 +641,7 @@ pub fn definition( if checker .settings .rules - .enabled(&Rule::MissingReturnTypeStaticMethod) + .enabled(Rule::MissingReturnTypeStaticMethod) { diagnostics.push(Diagnostic::new( MissingReturnTypeStaticMethod { @@ -656,7 +656,7 @@ pub fn definition( if checker .settings .rules - .enabled(&Rule::MissingReturnTypeSpecialMethod) + .enabled(Rule::MissingReturnTypeSpecialMethod) { if !(checker.settings.flake8_annotations.mypy_init_return && has_any_typed_arg) { @@ -681,7 +681,7 @@ pub fn definition( if checker .settings .rules - .enabled(&Rule::MissingReturnTypeSpecialMethod) + .enabled(Rule::MissingReturnTypeSpecialMethod) { diagnostics.push(Diagnostic::new( MissingReturnTypeSpecialMethod { @@ -696,7 +696,7 @@ pub fn definition( if checker .settings .rules - .enabled(&Rule::MissingReturnTypePublicFunction) + .enabled(Rule::MissingReturnTypePublicFunction) { diagnostics.push(Diagnostic::new( MissingReturnTypePublicFunction { @@ -710,7 +710,7 @@ pub fn definition( if checker .settings .rules - .enabled(&Rule::MissingReturnTypePrivateFunction) + .enabled(Rule::MissingReturnTypePrivateFunction) { diagnostics.push(Diagnostic::new( MissingReturnTypePrivateFunction { diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs b/crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs index 5d6ec2e7c6bcc..2bf4244827496 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs @@ -115,7 +115,7 @@ pub fn abstract_base_class( if !checker .settings .rules - .enabled(&Rule::EmptyMethodWithoutAbstractDecorator) + .enabled(Rule::EmptyMethodWithoutAbstractDecorator) { continue; } @@ -135,7 +135,7 @@ pub fn abstract_base_class( if checker .settings .rules - .enabled(&Rule::AbstractBaseClassWithoutAbstractMethod) + .enabled(Rule::AbstractBaseClassWithoutAbstractMethod) { if !has_abstract_method { checker.diagnostics.push(Diagnostic::new( diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs b/crates/ruff/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs index 61827f570d055..969dcd2a9ffad 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs @@ -83,7 +83,7 @@ fn duplicate_handler_exceptions<'a>( if checker .settings .rules - .enabled(&Rule::DuplicateHandlerException) + .enabled(Rule::DuplicateHandlerException) { // TODO(charlie): Handle "BaseException" and redundant exception aliases. if !duplicates.is_empty() { @@ -149,7 +149,7 @@ pub fn duplicate_exceptions(checker: &mut Checker, handlers: &[Excepthandler]) { if checker .settings .rules - .enabled(&Rule::DuplicateTryBlockException) + .enabled(Rule::DuplicateTryBlockException) { for (name, exprs) in duplicates { for expr in exprs { diff --git a/crates/ruff/src/rules/flake8_commas/rules.rs b/crates/ruff/src/rules/flake8_commas/rules.rs index ee872de2198da..fdc57be813bdf 100644 --- a/crates/ruff/src/rules/flake8_commas/rules.rs +++ b/crates/ruff/src/rules/flake8_commas/rules.rs @@ -260,7 +260,7 @@ pub fn trailing_commas( end_location: comma.2, }, ); - if autofix.into() && settings.rules.should_fix(&Rule::TrailingCommaProhibited) { + if autofix.into() && settings.rules.should_fix(Rule::TrailingCommaProhibited) { diagnostic.amend(Fix::deletion(comma.0, comma.2)); } diagnostics.push(diagnostic); @@ -304,7 +304,7 @@ pub fn trailing_commas( end_location: missing_comma.2, }, ); - if autofix.into() && settings.rules.should_fix(&Rule::TrailingCommaMissing) { + if autofix.into() && settings.rules.should_fix(Rule::TrailingCommaMissing) { // Create a replacement that includes the final bracket (or other token), // rather than just inserting a comma at the end. This prevents the UP034 autofix // removing any brackets in the same linter pass - doing both at the same time could diff --git a/crates/ruff/src/rules/flake8_errmsg/rules.rs b/crates/ruff/src/rules/flake8_errmsg/rules.rs index 3246fee601df1..f1064c911f062 100644 --- a/crates/ruff/src/rules/flake8_errmsg/rules.rs +++ b/crates/ruff/src/rules/flake8_errmsg/rules.rs @@ -162,7 +162,7 @@ pub fn string_in_exception(checker: &mut Checker, exc: &Expr) { value: Constant::Str(string), .. } => { - if checker.settings.rules.enabled(&Rule::RawStringInException) { + if checker.settings.rules.enabled(Rule::RawStringInException) { if string.len() > checker.settings.flake8_errmsg.max_string_length { checker .diagnostics @@ -172,7 +172,7 @@ pub fn string_in_exception(checker: &mut Checker, exc: &Expr) { } // Check for f-strings ExprKind::JoinedStr { .. } => { - if checker.settings.rules.enabled(&Rule::FStringInException) { + if checker.settings.rules.enabled(Rule::FStringInException) { checker .diagnostics .push(Diagnostic::new(FStringInException, Range::from(first))); @@ -180,7 +180,7 @@ pub fn string_in_exception(checker: &mut Checker, exc: &Expr) { } // Check for .format() calls ExprKind::Call { func, .. } => { - if checker.settings.rules.enabled(&Rule::DotFormatInException) { + if checker.settings.rules.enabled(Rule::DotFormatInException) { if let ExprKind::Attribute { value, attr, .. } = &func.node { if attr == "format" && matches!(value.node, ExprKind::Constant { .. }) { checker.diagnostics.push(Diagnostic::new( diff --git a/crates/ruff/src/rules/flake8_logging_format/rules.rs b/crates/ruff/src/rules/flake8_logging_format/rules.rs index 3e6b8f7ff8fb5..8c98f439fe9ca 100644 --- a/crates/ruff/src/rules/flake8_logging_format/rules.rs +++ b/crates/ruff/src/rules/flake8_logging_format/rules.rs @@ -43,14 +43,14 @@ fn check_msg(checker: &mut Checker, msg: &Expr) { // Check for string concatenation and percent format. ExprKind::BinOp { op, .. } => match op { Operator::Add => { - if checker.settings.rules.enabled(&Rule::LoggingStringConcat) { + if checker.settings.rules.enabled(Rule::LoggingStringConcat) { checker .diagnostics .push(Diagnostic::new(LoggingStringConcat, Range::from(msg))); } } Operator::Mod => { - if checker.settings.rules.enabled(&Rule::LoggingPercentFormat) { + if checker.settings.rules.enabled(Rule::LoggingPercentFormat) { checker .diagnostics .push(Diagnostic::new(LoggingPercentFormat, Range::from(msg))); @@ -60,7 +60,7 @@ fn check_msg(checker: &mut Checker, msg: &Expr) { }, // Check for f-strings. ExprKind::JoinedStr { .. } => { - if checker.settings.rules.enabled(&Rule::LoggingFString) { + if checker.settings.rules.enabled(Rule::LoggingFString) { checker .diagnostics .push(Diagnostic::new(LoggingFString, Range::from(msg))); @@ -68,7 +68,7 @@ fn check_msg(checker: &mut Checker, msg: &Expr) { } // Check for .format() calls. ExprKind::Call { func, .. } => { - if checker.settings.rules.enabled(&Rule::LoggingStringFormat) { + if checker.settings.rules.enabled(Rule::LoggingStringFormat) { if let ExprKind::Attribute { value, attr, .. } = &func.node { if attr == "format" && matches!(value.node, ExprKind::Constant { .. }) { checker @@ -151,7 +151,7 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: } // G010 - if checker.settings.rules.enabled(&Rule::LoggingWarn) + if checker.settings.rules.enabled(Rule::LoggingWarn) && matches!(logging_level, LoggingLevel::Warn) { let mut diagnostic = Diagnostic::new(LoggingWarn, level_call_range); @@ -166,18 +166,18 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: } // G101 - if checker.settings.rules.enabled(&Rule::LoggingExtraAttrClash) { + if checker.settings.rules.enabled(Rule::LoggingExtraAttrClash) { if let Some(extra) = find_keyword(keywords, "extra") { check_log_record_attr_clash(checker, extra); } } // G201, G202 - if checker.settings.rules.enabled(&Rule::LoggingExcInfo) + if checker.settings.rules.enabled(Rule::LoggingExcInfo) || checker .settings .rules - .enabled(&Rule::LoggingRedundantExcInfo) + .enabled(Rule::LoggingRedundantExcInfo) { if !checker.ctx.in_exception_handler() { return; @@ -206,7 +206,7 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: match logging_level { LoggingLevel::Error => { - if checker.settings.rules.enabled(&Rule::LoggingExcInfo) { + if checker.settings.rules.enabled(Rule::LoggingExcInfo) { checker .diagnostics .push(Diagnostic::new(LoggingExcInfo, level_call_range)); @@ -216,7 +216,7 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: if checker .settings .rules - .enabled(&Rule::LoggingRedundantExcInfo) + .enabled(Rule::LoggingRedundantExcInfo) { checker.diagnostics.push(Diagnostic::new( LoggingRedundantExcInfo, diff --git a/crates/ruff/src/rules/flake8_pyi/rules/unrecognized_platform.rs b/crates/ruff/src/rules/flake8_pyi/rules/unrecognized_platform.rs index af9437aadea44..7492c0eb686d0 100644 --- a/crates/ruff/src/rules/flake8_pyi/rules/unrecognized_platform.rs +++ b/crates/ruff/src/rules/flake8_pyi/rules/unrecognized_platform.rs @@ -118,7 +118,7 @@ pub fn unrecognized_platform( && checker .settings .rules - .enabled(&Rule::UnrecognizedPlatformCheck) + .enabled(Rule::UnrecognizedPlatformCheck) { checker .diagnostics @@ -137,7 +137,7 @@ pub fn unrecognized_platform( && checker .settings .rules - .enabled(&Rule::UnrecognizedPlatformName) + .enabled(Rule::UnrecognizedPlatformName) { checker.diagnostics.push(Diagnostic::new( UnrecognizedPlatformName { @@ -151,7 +151,7 @@ pub fn unrecognized_platform( if checker .settings .rules - .enabled(&Rule::UnrecognizedPlatformCheck) + .enabled(Rule::UnrecognizedPlatformCheck) { checker .diagnostics diff --git a/crates/ruff/src/rules/flake8_pytest_style/rules/fixture.rs b/crates/ruff/src/rules/flake8_pytest_style/rules/fixture.rs index 27f6bdbf9b89c..666c60c5bf40b 100644 --- a/crates/ruff/src/rules/flake8_pytest_style/rules/fixture.rs +++ b/crates/ruff/src/rules/flake8_pytest_style/rules/fixture.rs @@ -277,7 +277,7 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &E if checker .settings .rules - .enabled(&Rule::IncorrectFixtureParenthesesStyle) + .enabled(Rule::IncorrectFixtureParenthesesStyle) && !checker.settings.flake8_pytest_style.fixture_parentheses && args.is_empty() && keywords.is_empty() @@ -287,7 +287,7 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &E pytest_fixture_parentheses(checker, decorator, fix, "", "()"); } - if checker.settings.rules.enabled(&Rule::FixturePositionalArgs) && !args.is_empty() { + if checker.settings.rules.enabled(Rule::FixturePositionalArgs) && !args.is_empty() { checker.diagnostics.push(Diagnostic::new( FixturePositionalArgs { function: func_name.to_string(), @@ -299,7 +299,7 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &E if checker .settings .rules - .enabled(&Rule::ExtraneousScopeFunction) + .enabled(Rule::ExtraneousScopeFunction) { let scope_keyword = keywords .iter() @@ -333,7 +333,7 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &E if checker .settings .rules - .enabled(&Rule::IncorrectFixtureParenthesesStyle) + .enabled(Rule::IncorrectFixtureParenthesesStyle) && checker.settings.flake8_pytest_style.fixture_parentheses { let fix = Fix::insertion("()".to_string(), decorator.end_location.unwrap()); @@ -354,7 +354,7 @@ fn check_fixture_returns(checker: &mut Checker, func: &Stmt, func_name: &str, bo if checker .settings .rules - .enabled(&Rule::IncorrectFixtureNameUnderscore) + .enabled(Rule::IncorrectFixtureNameUnderscore) && visitor.has_return_with_value && func_name.starts_with('_') { @@ -367,7 +367,7 @@ fn check_fixture_returns(checker: &mut Checker, func: &Stmt, func_name: &str, bo } else if checker .settings .rules - .enabled(&Rule::MissingFixtureNameUnderscore) + .enabled(Rule::MissingFixtureNameUnderscore) && !visitor.has_return_with_value && !visitor.has_yield_from && !func_name.starts_with('_') @@ -380,7 +380,7 @@ fn check_fixture_returns(checker: &mut Checker, func: &Stmt, func_name: &str, bo )); } - if checker.settings.rules.enabled(&Rule::UselessYieldFixture) { + if checker.settings.rules.enabled(Rule::UselessYieldFixture) { if let Some(stmt) = body.last() { if let StmtKind::Expr { value, .. } = &stmt.node { if let ExprKind::Yield { .. } = value.node { @@ -462,7 +462,7 @@ fn check_fixture_marks(checker: &mut Checker, decorators: &[Expr]) { if checker .settings .rules - .enabled(&Rule::UnnecessaryAsyncioMarkOnFixture) + .enabled(Rule::UnnecessaryAsyncioMarkOnFixture) { if name == "asyncio" { let mut diagnostic = @@ -479,7 +479,7 @@ fn check_fixture_marks(checker: &mut Checker, decorators: &[Expr]) { if checker .settings .rules - .enabled(&Rule::ErroneousUseFixturesOnFixture) + .enabled(Rule::ErroneousUseFixturesOnFixture) { if name == "usefixtures" { let mut diagnostic = @@ -508,20 +508,17 @@ pub fn fixture( if checker .settings .rules - .enabled(&Rule::IncorrectFixtureParenthesesStyle) - || checker.settings.rules.enabled(&Rule::FixturePositionalArgs) + .enabled(Rule::IncorrectFixtureParenthesesStyle) + || checker.settings.rules.enabled(Rule::FixturePositionalArgs) || checker .settings .rules - .enabled(&Rule::ExtraneousScopeFunction) + .enabled(Rule::ExtraneousScopeFunction) { check_fixture_decorator(checker, func_name, decorator); } - if checker - .settings - .rules - .enabled(&Rule::DeprecatedYieldFixture) + if checker.settings.rules.enabled(Rule::DeprecatedYieldFixture) && checker.settings.flake8_pytest_style.fixture_parentheses { check_fixture_decorator_name(checker, decorator); @@ -530,12 +527,12 @@ pub fn fixture( if (checker .settings .rules - .enabled(&Rule::MissingFixtureNameUnderscore) + .enabled(Rule::MissingFixtureNameUnderscore) || checker .settings .rules - .enabled(&Rule::IncorrectFixtureNameUnderscore) - || checker.settings.rules.enabled(&Rule::UselessYieldFixture)) + .enabled(Rule::IncorrectFixtureNameUnderscore) + || checker.settings.rules.enabled(Rule::UselessYieldFixture)) && !has_abstractmethod_decorator(decorators, checker) { check_fixture_returns(checker, func, func_name, body); @@ -544,7 +541,7 @@ pub fn fixture( if checker .settings .rules - .enabled(&Rule::FixtureFinalizerCallback) + .enabled(Rule::FixtureFinalizerCallback) { check_fixture_addfinalizer(checker, args, body); } @@ -552,11 +549,11 @@ pub fn fixture( if checker .settings .rules - .enabled(&Rule::UnnecessaryAsyncioMarkOnFixture) + .enabled(Rule::UnnecessaryAsyncioMarkOnFixture) || checker .settings .rules - .enabled(&Rule::ErroneousUseFixturesOnFixture) + .enabled(Rule::ErroneousUseFixturesOnFixture) { check_fixture_marks(checker, decorators); } @@ -565,7 +562,7 @@ pub fn fixture( if checker .settings .rules - .enabled(&Rule::FixtureParamWithoutValue) + .enabled(Rule::FixtureParamWithoutValue) && func_name.starts_with("test_") { check_test_function_args(checker, args); diff --git a/crates/ruff/src/rules/flake8_pytest_style/rules/marks.rs b/crates/ruff/src/rules/flake8_pytest_style/rules/marks.rs index 22bcda769d38f..d49ec129f4e94 100644 --- a/crates/ruff/src/rules/flake8_pytest_style/rules/marks.rs +++ b/crates/ruff/src/rules/flake8_pytest_style/rules/marks.rs @@ -123,11 +123,11 @@ pub fn marks(checker: &mut Checker, decorators: &[Expr]) { let enforce_parentheses = checker .settings .rules - .enabled(&Rule::IncorrectMarkParenthesesStyle); + .enabled(Rule::IncorrectMarkParenthesesStyle); let enforce_useless_usefixtures = checker .settings .rules - .enabled(&Rule::UseFixturesWithoutParameters); + .enabled(Rule::UseFixturesWithoutParameters); for mark in get_mark_decorators(decorators) { if enforce_parentheses { diff --git a/crates/ruff/src/rules/flake8_pytest_style/rules/parametrize.rs b/crates/ruff/src/rules/flake8_pytest_style/rules/parametrize.rs index e0472e245e885..2e4cb383c6b50 100644 --- a/crates/ruff/src/rules/flake8_pytest_style/rules/parametrize.rs +++ b/crates/ruff/src/rules/flake8_pytest_style/rules/parametrize.rs @@ -380,7 +380,7 @@ pub fn parametrize(checker: &mut Checker, decorators: &[Expr]) { if checker .settings .rules - .enabled(&Rule::ParametrizeNamesWrongType) + .enabled(Rule::ParametrizeNamesWrongType) { if let Some(names) = args.get(0) { check_names(checker, names); @@ -389,7 +389,7 @@ pub fn parametrize(checker: &mut Checker, decorators: &[Expr]) { if checker .settings .rules - .enabled(&Rule::ParametrizeValuesWrongType) + .enabled(Rule::ParametrizeValuesWrongType) { if let Some(names) = args.get(0) { if let Some(values) = args.get(1) { diff --git a/crates/ruff/src/rules/flake8_pytest_style/rules/raises.rs b/crates/ruff/src/rules/flake8_pytest_style/rules/raises.rs index 2b35945288828..c5b3dc3c3ade0 100644 --- a/crates/ruff/src/rules/flake8_pytest_style/rules/raises.rs +++ b/crates/ruff/src/rules/flake8_pytest_style/rules/raises.rs @@ -67,11 +67,7 @@ const fn is_non_trivial_with_body(body: &[Stmt]) -> bool { pub fn raises_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: &[Keyword]) { if is_pytest_raises(checker, func) { - if checker - .settings - .rules - .enabled(&Rule::RaisesWithoutException) - { + if checker.settings.rules.enabled(Rule::RaisesWithoutException) { if args.is_empty() && keywords.is_empty() { checker .diagnostics @@ -79,7 +75,7 @@ pub fn raises_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: } } - if checker.settings.rules.enabled(&Rule::RaisesTooBroad) { + if checker.settings.rules.enabled(Rule::RaisesTooBroad) { let match_keyword = keywords .iter() .find(|kw| kw.node.arg == Some("match".to_string())); diff --git a/crates/ruff/src/rules/flake8_quotes/rules.rs b/crates/ruff/src/rules/flake8_quotes/rules.rs index 9635289a1cb23..2b8ad3fd81e30 100644 --- a/crates/ruff/src/rules/flake8_quotes/rules.rs +++ b/crates/ruff/src/rules/flake8_quotes/rules.rs @@ -281,7 +281,7 @@ fn docstring( }, Range::new(start, end), ); - if autofix.into() && settings.rules.should_fix(&Rule::BadQuotesDocstring) { + if autofix.into() && settings.rules.should_fix(Rule::BadQuotesDocstring) { let quote_count = if trivia.is_multiline { 3 } else { 1 }; let string_contents = &trivia.raw_text[quote_count..trivia.raw_text.len() - quote_count]; let quote = good_docstring("es_settings.docstring_quotes).repeat(quote_count); @@ -356,7 +356,7 @@ fn strings( Range::new(*start, *end), ); - if autofix.into() && settings.rules.should_fix(&Rule::BadQuotesMultilineString) { + if autofix.into() && settings.rules.should_fix(Rule::BadQuotesMultilineString) { let string_contents = &trivia.raw_text[3..trivia.raw_text.len() - 3]; let quote = good_multiline("es_settings.multiline_quotes); let mut fixed_contents = String::with_capacity( @@ -386,7 +386,7 @@ fn strings( { let mut diagnostic = Diagnostic::new(AvoidableEscapedQuote, Range::new(*start, *end)); - if autofix.into() && settings.rules.should_fix(&Rule::AvoidableEscapedQuote) { + if autofix.into() && settings.rules.should_fix(Rule::AvoidableEscapedQuote) { let quote = bad_single("es_settings.inline_quotes); let mut fixed_contents = @@ -445,7 +445,7 @@ fn strings( }, Range::new(*start, *end), ); - if autofix.into() && settings.rules.should_fix(&Rule::BadQuotesInlineString) { + if autofix.into() && settings.rules.should_fix(Rule::BadQuotesInlineString) { let quote = good_single("es_settings.inline_quotes); let mut fixed_contents = String::with_capacity(trivia.prefix.len() + string_contents.len() + 2); diff --git a/crates/ruff/src/rules/flake8_return/rules.rs b/crates/ruff/src/rules/flake8_return/rules.rs index 9295d8c5e7f3c..eb5aa616eeef7 100644 --- a/crates/ruff/src/rules/flake8_return/rules.rs +++ b/crates/ruff/src/rules/flake8_return/rules.rs @@ -514,13 +514,13 @@ pub fn function(checker: &mut Checker, body: &[Stmt]) { return; } - if checker.settings.rules.enabled(&Rule::SuperfluousElseReturn) - || checker.settings.rules.enabled(&Rule::SuperfluousElseRaise) + if checker.settings.rules.enabled(Rule::SuperfluousElseReturn) + || checker.settings.rules.enabled(Rule::SuperfluousElseRaise) || checker .settings .rules - .enabled(&Rule::SuperfluousElseContinue) - || checker.settings.rules.enabled(&Rule::SuperfluousElseBreak) + .enabled(Rule::SuperfluousElseContinue) + || checker.settings.rules.enabled(Rule::SuperfluousElseBreak) { if superfluous_elif(checker, &stack) { return; @@ -536,20 +536,20 @@ pub fn function(checker: &mut Checker, body: &[Stmt]) { } if !result_exists(&stack.returns) { - if checker.settings.rules.enabled(&Rule::UnnecessaryReturnNone) { + if checker.settings.rules.enabled(Rule::UnnecessaryReturnNone) { unnecessary_return_none(checker, &stack); } return; } - if checker.settings.rules.enabled(&Rule::ImplicitReturnValue) { + if checker.settings.rules.enabled(Rule::ImplicitReturnValue) { implicit_return_value(checker, &stack); } - if checker.settings.rules.enabled(&Rule::ImplicitReturn) { + if checker.settings.rules.enabled(Rule::ImplicitReturn) { implicit_return(checker, last_stmt); } - if checker.settings.rules.enabled(&Rule::UnnecessaryAssign) { + if checker.settings.rules.enabled(Rule::UnnecessaryAssign) { for (_, expr) in &stack.returns { if let Some(expr) = expr { unnecessary_assign(checker, &stack, expr); diff --git a/crates/ruff/src/rules/flake8_simplify/rules/reimplemented_builtin.rs b/crates/ruff/src/rules/flake8_simplify/rules/reimplemented_builtin.rs index 80c7b634173d0..f5a372889b8ca 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/reimplemented_builtin.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/reimplemented_builtin.rs @@ -201,7 +201,7 @@ pub fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt, sibling: .or_else(|| sibling.and_then(|sibling| return_values_for_siblings(stmt, sibling))) { if loop_info.return_value && !loop_info.next_return_value { - if checker.settings.rules.enabled(&Rule::ReimplementedBuiltin) { + if checker.settings.rules.enabled(Rule::ReimplementedBuiltin) { let contents = return_stmt( "any", loop_info.test, @@ -233,7 +233,7 @@ pub fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt, sibling: } if !loop_info.return_value && loop_info.next_return_value { - if checker.settings.rules.enabled(&Rule::ReimplementedBuiltin) { + if checker.settings.rules.enabled(Rule::ReimplementedBuiltin) { // Invert the condition. let test = { if let ExprKind::UnaryOp { diff --git a/crates/ruff/src/rules/flake8_unused_arguments/types.rs b/crates/ruff/src/rules/flake8_unused_arguments/types.rs index fcad369c005b3..05bd2535cf75c 100644 --- a/crates/ruff/src/rules/flake8_unused_arguments/types.rs +++ b/crates/ruff/src/rules/flake8_unused_arguments/types.rs @@ -24,13 +24,13 @@ impl Argumentable { } } - pub const fn rule_code(&self) -> &Rule { + pub const fn rule_code(&self) -> Rule { match self { - Self::Function => &Rule::UnusedFunctionArgument, - Self::Method => &Rule::UnusedMethodArgument, - Self::ClassMethod => &Rule::UnusedClassMethodArgument, - Self::StaticMethod => &Rule::UnusedStaticMethodArgument, - Self::Lambda => &Rule::UnusedLambdaArgument, + Self::Function => Rule::UnusedFunctionArgument, + Self::Method => Rule::UnusedMethodArgument, + Self::ClassMethod => Rule::UnusedClassMethodArgument, + Self::StaticMethod => Rule::UnusedStaticMethodArgument, + Self::Lambda => Rule::UnusedLambdaArgument, } } } diff --git a/crates/ruff/src/rules/isort/rules/add_required_imports.rs b/crates/ruff/src/rules/isort/rules/add_required_imports.rs index 2d0408e464e04..8912ed25ab557 100644 --- a/crates/ruff/src/rules/isort/rules/add_required_imports.rs +++ b/crates/ruff/src/rules/isort/rules/add_required_imports.rs @@ -168,7 +168,7 @@ fn add_required_import( MissingRequiredImport(required_import.clone()), Range::new(Location::default(), Location::default()), ); - if autofix.into() && settings.rules.should_fix(&Rule::MissingRequiredImport) { + if autofix.into() && settings.rules.should_fix(Rule::MissingRequiredImport) { // Determine the location at which the import should be inserted. let splice = helpers::find_splice_location(python_ast, locator); diff --git a/crates/ruff/src/rules/pandas_vet/mod.rs b/crates/ruff/src/rules/pandas_vet/mod.rs index 486536a137e40..5a37b8321e206 100644 --- a/crates/ruff/src/rules/pandas_vet/mod.rs +++ b/crates/ruff/src/rules/pandas_vet/mod.rs @@ -47,7 +47,7 @@ mod tests { ); let actual: Vec = diagnostics .into_iter() - .map(|diagnostic| diagnostic.kind.rule().clone()) + .map(|diagnostic| diagnostic.kind.rule()) .collect(); assert_eq!(actual, expected); } diff --git a/crates/ruff/src/rules/pandas_vet/rules/check_attr.rs b/crates/ruff/src/rules/pandas_vet/rules/check_attr.rs index 30e1a9b30e383..edfff77903a80 100644 --- a/crates/ruff/src/rules/pandas_vet/rules/check_attr.rs +++ b/crates/ruff/src/rules/pandas_vet/rules/check_attr.rs @@ -52,10 +52,10 @@ impl Violation for UseOfDotValues { pub fn check_attr(checker: &mut Checker, attr: &str, value: &Expr, attr_expr: &Expr) { let rules = &checker.settings.rules; let violation: DiagnosticKind = match attr { - "ix" if rules.enabled(&Rule::UseOfDotIx) => UseOfDotIx.into(), - "at" if rules.enabled(&Rule::UseOfDotAt) => UseOfDotAt.into(), - "iat" if rules.enabled(&Rule::UseOfDotIat) => UseOfDotIat.into(), - "values" if rules.enabled(&Rule::UseOfDotValues) => UseOfDotValues.into(), + "ix" if rules.enabled(Rule::UseOfDotIx) => UseOfDotIx.into(), + "at" if rules.enabled(Rule::UseOfDotAt) => UseOfDotAt.into(), + "iat" if rules.enabled(Rule::UseOfDotIat) => UseOfDotIat.into(), + "values" if rules.enabled(Rule::UseOfDotValues) => UseOfDotValues.into(), _ => return, }; diff --git a/crates/ruff/src/rules/pandas_vet/rules/check_call.rs b/crates/ruff/src/rules/pandas_vet/rules/check_call.rs index 96c648ee6ce2c..d74aaba1574d0 100644 --- a/crates/ruff/src/rules/pandas_vet/rules/check_call.rs +++ b/crates/ruff/src/rules/pandas_vet/rules/check_call.rs @@ -65,13 +65,13 @@ pub fn check_call(checker: &mut Checker, func: &Expr) { let rules = &checker.settings.rules; let ExprKind::Attribute { value, attr, .. } = &func.node else {return}; let violation: DiagnosticKind = match attr.as_str() { - "isnull" if rules.enabled(&Rule::UseOfDotIsNull) => UseOfDotIsNull.into(), - "notnull" if rules.enabled(&Rule::UseOfDotNotNull) => UseOfDotNotNull.into(), - "pivot" | "unstack" if rules.enabled(&Rule::UseOfDotPivotOrUnstack) => { + "isnull" if rules.enabled(Rule::UseOfDotIsNull) => UseOfDotIsNull.into(), + "notnull" if rules.enabled(Rule::UseOfDotNotNull) => UseOfDotNotNull.into(), + "pivot" | "unstack" if rules.enabled(Rule::UseOfDotPivotOrUnstack) => { UseOfDotPivotOrUnstack.into() } - "read_table" if rules.enabled(&Rule::UseOfDotReadTable) => UseOfDotReadTable.into(), - "stack" if rules.enabled(&Rule::UseOfDotStack) => UseOfDotStack.into(), + "read_table" if rules.enabled(Rule::UseOfDotReadTable) => UseOfDotReadTable.into(), + "stack" if rules.enabled(Rule::UseOfDotStack) => UseOfDotStack.into(), _ => return, }; diff --git a/crates/ruff/src/rules/pycodestyle/rules/compound_statements.rs b/crates/ruff/src/rules/pycodestyle/rules/compound_statements.rs index e8dfb09a3f2ef..b1220374330e7 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/compound_statements.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/compound_statements.rs @@ -162,7 +162,7 @@ pub fn compound_statements( Tok::Newline => { if let Some((start, end)) = semi { let mut diagnostic = Diagnostic::new(UselessSemicolon, Range::new(start, end)); - if autofix.into() && settings.rules.should_fix(&Rule::UselessSemicolon) { + if autofix.into() && settings.rules.should_fix(Rule::UselessSemicolon) { diagnostic.amend(Fix::deletion(start, end)); }; diagnostics.push(diagnostic); diff --git a/crates/ruff/src/rules/pycodestyle/rules/trailing_whitespace.rs b/crates/ruff/src/rules/pycodestyle/rules/trailing_whitespace.rs index d6eeeb9785c44..635069bd569b2 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/trailing_whitespace.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/trailing_whitespace.rs @@ -88,22 +88,20 @@ pub fn trailing_whitespace( let end = Location::new(lineno + 1, line_char_count); if whitespace_count == line_char_count { - if settings.rules.enabled(&Rule::BlankLineContainsWhitespace) { + if settings.rules.enabled(Rule::BlankLineContainsWhitespace) { let mut diagnostic = Diagnostic::new(BlankLineContainsWhitespace, Range::new(start, end)); if matches!(autofix, flags::Autofix::Enabled) - && settings - .rules - .should_fix(&Rule::BlankLineContainsWhitespace) + && settings.rules.should_fix(Rule::BlankLineContainsWhitespace) { diagnostic.amend(Fix::deletion(start, end)); } return Some(diagnostic); } - } else if settings.rules.enabled(&Rule::TrailingWhitespace) { + } else if settings.rules.enabled(Rule::TrailingWhitespace) { let mut diagnostic = Diagnostic::new(TrailingWhitespace, Range::new(start, end)); if matches!(autofix, flags::Autofix::Enabled) - && settings.rules.should_fix(&Rule::TrailingWhitespace) + && settings.rules.should_fix(Rule::TrailingWhitespace) { diagnostic.amend(Fix::deletion(start, end)); } diff --git a/crates/ruff/src/rules/pydocstyle/rules/blank_before_after_class.rs b/crates/ruff/src/rules/pydocstyle/rules/blank_before_after_class.rs index df7ff18fdbed1..79a643b2f0456 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/blank_before_after_class.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/blank_before_after_class.rs @@ -65,11 +65,8 @@ pub fn blank_before_after_class(checker: &mut Checker, docstring: &Docstring) { if checker .settings .rules - .enabled(&Rule::OneBlankLineBeforeClass) - || checker - .settings - .rules - .enabled(&Rule::NoBlankLineBeforeClass) + .enabled(Rule::OneBlankLineBeforeClass) + || checker.settings.rules.enabled(Rule::NoBlankLineBeforeClass) { let before = checker .locator @@ -81,11 +78,7 @@ pub fn blank_before_after_class(checker: &mut Checker, docstring: &Docstring) { .skip(1) .take_while(|line| line.trim().is_empty()) .count(); - if checker - .settings - .rules - .enabled(&Rule::NoBlankLineBeforeClass) - { + if checker.settings.rules.enabled(Rule::NoBlankLineBeforeClass) { if blank_lines_before != 0 { let mut diagnostic = Diagnostic::new( NoBlankLineBeforeClass { @@ -106,7 +99,7 @@ pub fn blank_before_after_class(checker: &mut Checker, docstring: &Docstring) { if checker .settings .rules - .enabled(&Rule::OneBlankLineBeforeClass) + .enabled(Rule::OneBlankLineBeforeClass) { if blank_lines_before != 1 { let mut diagnostic = Diagnostic::new( @@ -128,11 +121,7 @@ pub fn blank_before_after_class(checker: &mut Checker, docstring: &Docstring) { } } - if checker - .settings - .rules - .enabled(&Rule::OneBlankLineAfterClass) - { + if checker.settings.rules.enabled(Rule::OneBlankLineAfterClass) { let after = checker.locator.slice(Range::new( docstring.expr.end_location.unwrap(), parent.end_location.unwrap(), diff --git a/crates/ruff/src/rules/pydocstyle/rules/blank_before_after_function.rs b/crates/ruff/src/rules/pydocstyle/rules/blank_before_after_function.rs index 2d58910fbe9ba..c575095a49d80 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/blank_before_after_function.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/blank_before_after_function.rs @@ -61,7 +61,7 @@ pub fn blank_before_after_function(checker: &mut Checker, docstring: &Docstring) if checker .settings .rules - .enabled(&Rule::NoBlankLineBeforeFunction) + .enabled(Rule::NoBlankLineBeforeFunction) { let before = checker .locator @@ -94,7 +94,7 @@ pub fn blank_before_after_function(checker: &mut Checker, docstring: &Docstring) if checker .settings .rules - .enabled(&Rule::NoBlankLineAfterFunction) + .enabled(Rule::NoBlankLineAfterFunction) { let after = checker.locator.slice(Range::new( docstring.expr.end_location.unwrap(), diff --git a/crates/ruff/src/rules/pydocstyle/rules/indent.rs b/crates/ruff/src/rules/pydocstyle/rules/indent.rs index dc1a74ec18097..9e5509d446f64 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/indent.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/indent.rs @@ -80,7 +80,7 @@ pub fn indent(checker: &mut Checker, docstring: &Docstring) { // yet. has_seen_tab = has_seen_tab || line_indent.contains('\t'); - if checker.settings.rules.enabled(&Rule::NoUnderIndentation) { + if checker.settings.rules.enabled(Rule::NoUnderIndentation) { // We report under-indentation on every line. This isn't great, but enables // autofix. if (i == lines.len() - 1 || !is_blank) @@ -119,7 +119,7 @@ pub fn indent(checker: &mut Checker, docstring: &Docstring) { } } - if checker.settings.rules.enabled(&Rule::IndentWithSpaces) { + if checker.settings.rules.enabled(Rule::IndentWithSpaces) { if has_seen_tab { checker.diagnostics.push(Diagnostic::new( IndentWithSpaces, @@ -128,7 +128,7 @@ pub fn indent(checker: &mut Checker, docstring: &Docstring) { } } - if checker.settings.rules.enabled(&Rule::NoOverIndentation) { + if checker.settings.rules.enabled(Rule::NoOverIndentation) { // If every line (except the last) is over-indented... if is_over_indented { for i in over_indented_lines { diff --git a/crates/ruff/src/rules/pydocstyle/rules/multi_line_summary_start.rs b/crates/ruff/src/rules/pydocstyle/rules/multi_line_summary_start.rs index 30fc1d9f397a9..8245611bbc136 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/multi_line_summary_start.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/multi_line_summary_start.rs @@ -56,7 +56,7 @@ pub fn multi_line_summary_start(checker: &mut Checker, docstring: &Docstring) { if checker .settings .rules - .enabled(&Rule::MultiLineSummaryFirstLine) + .enabled(Rule::MultiLineSummaryFirstLine) { let mut diagnostic = Diagnostic::new(MultiLineSummaryFirstLine, Range::from(docstring.expr)); @@ -81,7 +81,7 @@ pub fn multi_line_summary_start(checker: &mut Checker, docstring: &Docstring) { if checker .settings .rules - .enabled(&Rule::MultiLineSummarySecondLine) + .enabled(Rule::MultiLineSummarySecondLine) { let mut diagnostic = Diagnostic::new(MultiLineSummarySecondLine, Range::from(docstring.expr)); diff --git a/crates/ruff/src/rules/pydocstyle/rules/not_empty.rs b/crates/ruff/src/rules/pydocstyle/rules/not_empty.rs index 07199d324fc88..f8f7c2bd19f5e 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/not_empty.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/not_empty.rs @@ -22,7 +22,7 @@ pub fn not_empty(checker: &mut Checker, docstring: &Docstring) -> bool { return true; } - if checker.settings.rules.enabled(&Rule::EmptyDocstring) { + if checker.settings.rules.enabled(Rule::EmptyDocstring) { checker .diagnostics .push(Diagnostic::new(EmptyDocstring, Range::from(docstring.expr))); diff --git a/crates/ruff/src/rules/pydocstyle/rules/not_missing.rs b/crates/ruff/src/rules/pydocstyle/rules/not_missing.rs index f724e4439ee37..c4e4798fa8d2e 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/not_missing.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/not_missing.rs @@ -104,7 +104,7 @@ pub fn not_missing( match definition.kind { DefinitionKind::Module => { - if checker.settings.rules.enabled(&Rule::PublicModule) { + if checker.settings.rules.enabled(Rule::PublicModule) { checker.diagnostics.push(Diagnostic::new( PublicModule, Range::new(Location::new(1, 0), Location::new(1, 0)), @@ -113,7 +113,7 @@ pub fn not_missing( false } DefinitionKind::Package => { - if checker.settings.rules.enabled(&Rule::PublicPackage) { + if checker.settings.rules.enabled(Rule::PublicPackage) { checker.diagnostics.push(Diagnostic::new( PublicPackage, Range::new(Location::new(1, 0), Location::new(1, 0)), @@ -122,7 +122,7 @@ pub fn not_missing( false } DefinitionKind::Class(stmt) => { - if checker.settings.rules.enabled(&Rule::PublicClass) { + if checker.settings.rules.enabled(Rule::PublicClass) { checker.diagnostics.push(Diagnostic::new( PublicClass, identifier_range(stmt, checker.locator), @@ -131,7 +131,7 @@ pub fn not_missing( false } DefinitionKind::NestedClass(stmt) => { - if checker.settings.rules.enabled(&Rule::PublicNestedClass) { + if checker.settings.rules.enabled(Rule::PublicNestedClass) { checker.diagnostics.push(Diagnostic::new( PublicNestedClass, identifier_range(stmt, checker.locator), @@ -143,7 +143,7 @@ pub fn not_missing( if is_overload(&checker.ctx, cast::decorator_list(stmt)) { true } else { - if checker.settings.rules.enabled(&Rule::PublicFunction) { + if checker.settings.rules.enabled(Rule::PublicFunction) { checker.diagnostics.push(Diagnostic::new( PublicFunction, identifier_range(stmt, checker.locator), @@ -158,7 +158,7 @@ pub fn not_missing( { true } else if is_init(cast::name(stmt)) { - if checker.settings.rules.enabled(&Rule::PublicInit) { + if checker.settings.rules.enabled(Rule::PublicInit) { checker.diagnostics.push(Diagnostic::new( PublicInit, identifier_range(stmt, checker.locator), @@ -166,7 +166,7 @@ pub fn not_missing( } true } else if is_new(cast::name(stmt)) || is_call(cast::name(stmt)) { - if checker.settings.rules.enabled(&Rule::PublicMethod) { + if checker.settings.rules.enabled(Rule::PublicMethod) { checker.diagnostics.push(Diagnostic::new( PublicMethod, identifier_range(stmt, checker.locator), @@ -174,7 +174,7 @@ pub fn not_missing( } true } else if is_magic(cast::name(stmt)) { - if checker.settings.rules.enabled(&Rule::MagicMethod) { + if checker.settings.rules.enabled(Rule::MagicMethod) { checker.diagnostics.push(Diagnostic::new( MagicMethod, identifier_range(stmt, checker.locator), @@ -182,7 +182,7 @@ pub fn not_missing( } true } else { - if checker.settings.rules.enabled(&Rule::PublicMethod) { + if checker.settings.rules.enabled(Rule::PublicMethod) { checker.diagnostics.push(Diagnostic::new( PublicMethod, identifier_range(stmt, checker.locator), diff --git a/crates/ruff/src/rules/pydocstyle/rules/sections.rs b/crates/ruff/src/rules/pydocstyle/rules/sections.rs index b5d51378d13ea..80d359b59d455 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/sections.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/sections.rs @@ -353,7 +353,7 @@ fn blanks_and_section_underline( if checker .settings .rules - .enabled(&Rule::DashedUnderlineAfterSection) + .enabled(Rule::DashedUnderlineAfterSection) { let mut diagnostic = Diagnostic::new( DashedUnderlineAfterSection { @@ -379,7 +379,7 @@ fn blanks_and_section_underline( } checker.diagnostics.push(diagnostic); } - if checker.settings.rules.enabled(&Rule::EmptyDocstringSection) { + if checker.settings.rules.enabled(Rule::EmptyDocstringSection) { checker.diagnostics.push(Diagnostic::new( EmptyDocstringSection { name: context.section_name.to_string(), @@ -400,7 +400,7 @@ fn blanks_and_section_underline( if checker .settings .rules - .enabled(&Rule::SectionUnderlineAfterName) + .enabled(Rule::SectionUnderlineAfterName) { let mut diagnostic = Diagnostic::new( SectionUnderlineAfterName { @@ -438,7 +438,7 @@ fn blanks_and_section_underline( if checker .settings .rules - .enabled(&Rule::SectionUnderlineMatchesSectionLength) + .enabled(Rule::SectionUnderlineMatchesSectionLength) { let mut diagnostic = Diagnostic::new( SectionUnderlineMatchesSectionLength { @@ -480,7 +480,7 @@ fn blanks_and_section_underline( if checker .settings .rules - .enabled(&Rule::SectionUnderlineNotOverIndented) + .enabled(Rule::SectionUnderlineNotOverIndented) { let leading_space = whitespace::leading_space(non_empty_line); if leading_space.len() > docstring.indentation.len() { @@ -525,7 +525,7 @@ fn blanks_and_section_underline( .take_while(|line| line.trim().is_empty()) .count(); if blank_lines_after_dashes == rest_of_lines.len() { - if checker.settings.rules.enabled(&Rule::EmptyDocstringSection) { + if checker.settings.rules.enabled(Rule::EmptyDocstringSection) { checker.diagnostics.push(Diagnostic::new( EmptyDocstringSection { name: context.section_name.to_string(), @@ -537,7 +537,7 @@ fn blanks_and_section_underline( if checker .settings .rules - .enabled(&Rule::NoBlankLinesBetweenHeaderAndContent) + .enabled(Rule::NoBlankLinesBetweenHeaderAndContent) { let mut diagnostic = Diagnostic::new( NoBlankLinesBetweenHeaderAndContent { @@ -570,7 +570,7 @@ fn blanks_and_section_underline( } } } else { - if checker.settings.rules.enabled(&Rule::EmptyDocstringSection) { + if checker.settings.rules.enabled(Rule::EmptyDocstringSection) { checker.diagnostics.push(Diagnostic::new( EmptyDocstringSection { name: context.section_name.to_string(), @@ -583,7 +583,7 @@ fn blanks_and_section_underline( if checker .settings .rules - .enabled(&Rule::DashedUnderlineAfterSection) + .enabled(Rule::DashedUnderlineAfterSection) { let mut diagnostic = Diagnostic::new( DashedUnderlineAfterSection { @@ -613,7 +613,7 @@ fn blanks_and_section_underline( if checker .settings .rules - .enabled(&Rule::NoBlankLinesBetweenHeaderAndContent) + .enabled(Rule::NoBlankLinesBetweenHeaderAndContent) { let mut diagnostic = Diagnostic::new( NoBlankLinesBetweenHeaderAndContent { @@ -644,7 +644,7 @@ fn blanks_and_section_underline( } fn common_section(checker: &mut Checker, docstring: &Docstring, context: &SectionContext) { - if checker.settings.rules.enabled(&Rule::CapitalizeSectionName) { + if checker.settings.rules.enabled(Rule::CapitalizeSectionName) { let capitalized_section_name = context.kind.as_str(); if context.section_name != capitalized_section_name { let mut diagnostic = Diagnostic::new( @@ -677,11 +677,7 @@ fn common_section(checker: &mut Checker, docstring: &Docstring, context: &Sectio } } - if checker - .settings - .rules - .enabled(&Rule::SectionNotOverIndented) - { + if checker.settings.rules.enabled(Rule::SectionNotOverIndented) { let leading_space = whitespace::leading_space(context.line); if leading_space.len() > docstring.indentation.len() { let mut diagnostic = Diagnostic::new( @@ -715,7 +711,7 @@ fn common_section(checker: &mut Checker, docstring: &Docstring, context: &Sectio if checker .settings .rules - .enabled(&Rule::BlankLineAfterLastSection) + .enabled(Rule::BlankLineAfterLastSection) { let mut diagnostic = Diagnostic::new( BlankLineAfterLastSection { @@ -739,7 +735,7 @@ fn common_section(checker: &mut Checker, docstring: &Docstring, context: &Sectio checker.diagnostics.push(diagnostic); } } else { - if checker.settings.rules.enabled(&Rule::BlankLineAfterSection) { + if checker.settings.rules.enabled(Rule::BlankLineAfterSection) { let mut diagnostic = Diagnostic::new( BlankLineAfterSection { name: context.section_name.to_string(), @@ -764,11 +760,7 @@ fn common_section(checker: &mut Checker, docstring: &Docstring, context: &Sectio } } - if checker - .settings - .rules - .enabled(&Rule::BlankLineBeforeSection) - { + if checker.settings.rules.enabled(Rule::BlankLineBeforeSection) { if !context.previous_line.is_empty() { let mut diagnostic = Diagnostic::new( BlankLineBeforeSection { @@ -959,7 +951,7 @@ fn numpy_section(checker: &mut Checker, docstring: &Docstring, context: &Section if checker .settings .rules - .enabled(&Rule::NewLineAfterSectionName) + .enabled(Rule::NewLineAfterSectionName) { let suffix = context .line @@ -997,7 +989,7 @@ fn numpy_section(checker: &mut Checker, docstring: &Docstring, context: &Section } } - if checker.settings.rules.enabled(&Rule::UndocumentedParam) { + if checker.settings.rules.enabled(Rule::UndocumentedParam) { if matches!(context.kind, SectionKind::Parameters) { parameters_section(checker, docstring, context); } @@ -1007,11 +999,7 @@ fn numpy_section(checker: &mut Checker, docstring: &Docstring, context: &Section fn google_section(checker: &mut Checker, docstring: &Docstring, context: &SectionContext) { common_section(checker, docstring, context); - if checker - .settings - .rules - .enabled(&Rule::SectionNameEndsInColon) - { + if checker.settings.rules.enabled(Rule::SectionNameEndsInColon) { let suffix = context .line .trim() @@ -1049,7 +1037,7 @@ fn google_section(checker: &mut Checker, docstring: &Docstring, context: &Sectio } } - if checker.settings.rules.enabled(&Rule::UndocumentedParam) { + if checker.settings.rules.enabled(Rule::UndocumentedParam) { if matches!(context.kind, SectionKind::Args | SectionKind::Arguments) { args_section(checker, docstring, context); } diff --git a/crates/ruff/src/rules/pyflakes/mod.rs b/crates/ruff/src/rules/pyflakes/mod.rs index faa194e216f98..eb0e4e2c1c2a8 100644 --- a/crates/ruff/src/rules/pyflakes/mod.rs +++ b/crates/ruff/src/rules/pyflakes/mod.rs @@ -272,7 +272,7 @@ mod tests { diagnostics.sort_by_key(|diagnostic| diagnostic.location); let actual = diagnostics .iter() - .map(|diagnostic| diagnostic.kind.rule().clone()) + .map(|diagnostic| diagnostic.kind.rule()) .collect::>(); assert_eq!(actual, expected); } diff --git a/crates/ruff/src/rules/pyflakes/rules/repeated_keys.rs b/crates/ruff/src/rules/pyflakes/rules/repeated_keys.rs index 9760826b0dbbf..e1aef49c2d2b3 100644 --- a/crates/ruff/src/rules/pyflakes/rules/repeated_keys.rs +++ b/crates/ruff/src/rules/pyflakes/rules/repeated_keys.rs @@ -97,7 +97,7 @@ pub fn repeated_keys(checker: &mut Checker, keys: &[Option], values: &[Exp if checker .settings .rules - .enabled(&Rule::MultiValueRepeatedKeyLiteral) + .enabled(Rule::MultiValueRepeatedKeyLiteral) { let comparable_value: ComparableExpr = (&values[i]).into(); let is_duplicate_value = seen_values.contains(&comparable_value); @@ -125,7 +125,7 @@ pub fn repeated_keys(checker: &mut Checker, keys: &[Option], values: &[Exp if checker .settings .rules - .enabled(&Rule::MultiValueRepeatedKeyVariable) + .enabled(Rule::MultiValueRepeatedKeyVariable) { let comparable_value: ComparableExpr = (&values[i]).into(); let is_duplicate_value = seen_values.contains(&comparable_value); diff --git a/crates/ruff/src/rules/pylint/rules/logging.rs b/crates/ruff/src/rules/pylint/rules/logging.rs index 6966b24a2ee8e..7b18a65de9df2 100644 --- a/crates/ruff/src/rules/pylint/rules/logging.rs +++ b/crates/ruff/src/rules/pylint/rules/logging.rs @@ -123,7 +123,7 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: let message_args = call_args.args.len() - 1; - if checker.settings.rules.enabled(&Rule::LoggingTooManyArgs) { + if checker.settings.rules.enabled(Rule::LoggingTooManyArgs) { if summary.num_positional < message_args { checker .diagnostics @@ -131,7 +131,7 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: } } - if checker.settings.rules.enabled(&Rule::LoggingTooFewArgs) { + if checker.settings.rules.enabled(Rule::LoggingTooFewArgs) { if message_args > 0 && call_args.kwargs.is_empty() && summary.num_positional > message_args diff --git a/crates/ruff/src/rules/pyupgrade/rules/deprecated_import.rs b/crates/ruff/src/rules/pyupgrade/rules/deprecated_import.rs index 4fc881752bf47..90343fd4c08ee 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/deprecated_import.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/deprecated_import.rs @@ -550,7 +550,7 @@ pub fn deprecated_import( }, Range::from(stmt), ); - if checker.patch(&Rule::DeprecatedImport) { + if checker.patch(Rule::DeprecatedImport) { if let Some(content) = fix { diagnostic.amend(Fix::replacement( content, diff --git a/crates/ruff/src/rules/pyupgrade/rules/extraneous_parentheses.rs b/crates/ruff/src/rules/pyupgrade/rules/extraneous_parentheses.rs index 30d168ee457e3..c56f3386d86fd 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/extraneous_parentheses.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/extraneous_parentheses.rs @@ -137,7 +137,7 @@ pub fn extraneous_parentheses( }; let mut diagnostic = Diagnostic::new(ExtraneousParentheses, Range::new(*start, *end)); - if autofix.into() && settings.rules.should_fix(&Rule::ExtraneousParentheses) { + if autofix.into() && settings.rules.should_fix(Rule::ExtraneousParentheses) { let contents = locator.slice(Range::new(*start, *end)); diagnostic.amend(Fix::replacement( contents[1..contents.len() - 1].to_string(), diff --git a/crates/ruff/src/rules/pyupgrade/rules/quoted_annotation.rs b/crates/ruff/src/rules/pyupgrade/rules/quoted_annotation.rs index d2fb506a5bd33..b4c4288bfe86f 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/quoted_annotation.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/quoted_annotation.rs @@ -22,7 +22,7 @@ impl AlwaysAutofixableViolation for QuotedAnnotation { /// UP037 pub fn quoted_annotation(checker: &mut Checker, annotation: &str, range: Range) { let mut diagnostic = Diagnostic::new(QuotedAnnotation, range); - if checker.patch(&Rule::QuotedAnnotation) { + if checker.patch(Rule::QuotedAnnotation) { diagnostic.amend(Fix::replacement( annotation.to_string(), range.location, diff --git a/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs b/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs index 1a588f8fa36c7..f5ffa40ea7650 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs @@ -193,7 +193,7 @@ pub fn redundant_open_modes(checker: &mut Checker, expr: &Expr) { &keyword.node.value, mode.replacement_value(), checker.locator, - checker.patch(&Rule::RedundantOpenModes), + checker.patch(Rule::RedundantOpenModes), )); } } @@ -210,7 +210,7 @@ pub fn redundant_open_modes(checker: &mut Checker, expr: &Expr) { mode_param, mode.replacement_value(), checker.locator, - checker.patch(&Rule::RedundantOpenModes), + checker.patch(Rule::RedundantOpenModes), )); } } diff --git a/crates/ruff/src/rules/pyupgrade/rules/rewrite_mock_import.rs b/crates/ruff/src/rules/pyupgrade/rules/rewrite_mock_import.rs index 35595405e4822..b1b91225916c9 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/rewrite_mock_import.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/rewrite_mock_import.rs @@ -262,7 +262,7 @@ pub fn rewrite_mock_import(checker: &mut Checker, stmt: &Stmt) { .any(|name| name.node.name == "mock" || name.node.name == "mock.mock") { // Generate the fix, if needed, which is shared between all `mock` imports. - let content = if checker.patch(&Rule::RewriteMockImport) { + let content = if checker.patch(Rule::RewriteMockImport) { if let Some(indent) = indentation(checker.locator, stmt) { match format_import(stmt, indent, checker.locator, checker.stylist) { Ok(content) => Some(content), diff --git a/crates/ruff/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs b/crates/ruff/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs index a5546911ad8cf..839f1c99db79f 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs @@ -161,7 +161,7 @@ pub fn unnecessary_encode_utf8( expr, variable, checker.locator, - checker.patch(&Rule::UnnecessaryEncodeUTF8), + checker.patch(Rule::UnnecessaryEncodeUTF8), )); } else { // "unicode text©".encode("utf-8") @@ -169,7 +169,7 @@ pub fn unnecessary_encode_utf8( expr, args, kwargs, - checker.patch(&Rule::UnnecessaryEncodeUTF8), + checker.patch(Rule::UnnecessaryEncodeUTF8), ) { checker.diagnostics.push(diagnostic); } @@ -183,7 +183,7 @@ pub fn unnecessary_encode_utf8( expr, args, kwargs, - checker.patch(&Rule::UnnecessaryEncodeUTF8), + checker.patch(Rule::UnnecessaryEncodeUTF8), ) { checker.diagnostics.push(diagnostic); } diff --git a/crates/ruff/src/settings/mod.rs b/crates/ruff/src/settings/mod.rs index 5091d73b18b6a..a27c6b58feb6a 100644 --- a/crates/ruff/src/settings/mod.rs +++ b/crates/ruff/src/settings/mod.rs @@ -386,16 +386,16 @@ impl From<&Configuration> for RuleTable { .and_then(|pydocstyle| pydocstyle.convention) { for rule in convention.rules_to_be_ignored() { - rules.disable(rule); + rules.disable(*rule); } } // Validate that we didn't enable any incompatible rules. Use this awkward // approach to give each pair it's own `warn_user_once`. for (preferred, expendable, message) in INCOMPATIBLE_CODES { - if rules.enabled(preferred) && rules.enabled(expendable) { + if rules.enabled(*preferred) && rules.enabled(*expendable) { warn_user_once_by_id!(expendable.as_ref(), "{}", message); - rules.disable(expendable); + rules.disable(*expendable); } } @@ -440,7 +440,7 @@ mod tests { ..Configuration::default() }) .iter_enabled() - .cloned() + .copied() .collect() } diff --git a/crates/ruff/src/settings/rule_table.rs b/crates/ruff/src/settings/rule_table.rs index d1cc0e23e935f..c3e3e251bfc88 100644 --- a/crates/ruff/src/settings/rule_table.rs +++ b/crates/ruff/src/settings/rule_table.rs @@ -22,13 +22,15 @@ impl RuleTable { } /// Returns whether the given rule should be checked. - pub fn enabled(&self, code: &Rule) -> bool { - self.enabled.contains_key(code) + #[inline] + pub fn enabled(&self, code: Rule) -> bool { + self.enabled.contains_key(&code) } /// Returns whether violations of the given rule should be autofixed. - pub fn should_fix(&self, code: &Rule) -> bool { - *self.enabled.get(code).unwrap_or(&false) + #[inline] + pub fn should_fix(&self, code: Rule) -> bool { + *self.enabled.get(&code).unwrap_or(&false) } /// Returns an iterator over all enabled rules. @@ -37,13 +39,15 @@ impl RuleTable { } /// Enables the given rule. + #[inline] pub fn enable(&mut self, code: Rule, should_fix: bool) { self.enabled.insert(code, should_fix); } /// Disables the given rule. - pub fn disable(&mut self, rule: &Rule) { - self.enabled.remove(rule); + #[inline] + pub fn disable(&mut self, rule: Rule) { + self.enabled.remove(&rule); } } diff --git a/crates/ruff_cli/src/commands/rule.rs b/crates/ruff_cli/src/commands/rule.rs index ca1895ce084b0..4b85263884325 100644 --- a/crates/ruff_cli/src/commands/rule.rs +++ b/crates/ruff_cli/src/commands/rule.rs @@ -16,7 +16,7 @@ struct Explanation<'a> { } /// Explain a `Rule` to the user. -pub fn rule(rule: &Rule, format: HelpFormat) -> Result<()> { +pub fn rule(rule: Rule, format: HelpFormat) -> Result<()> { let (linter, _) = Linter::parse_code(&rule.noqa_code().to_string()).unwrap(); let mut stdout = BufWriter::new(io::stdout().lock()); let mut output = String::new(); diff --git a/crates/ruff_cli/src/commands/run.rs b/crates/ruff_cli/src/commands/run.rs index 3b79f7b22a825..4769289962807 100644 --- a/crates/ruff_cli/src/commands/run.rs +++ b/crates/ruff_cli/src/commands/run.rs @@ -105,7 +105,7 @@ pub fn run( ":".bold() ); let settings = resolver.resolve(path, pyproject_strategy); - if settings.rules.enabled(&Rule::IOError) { + if settings.rules.enabled(Rule::IOError) { Diagnostics::new(vec![Message::from_diagnostic( Diagnostic::new( IOError { message }, diff --git a/crates/ruff_cli/src/lib.rs b/crates/ruff_cli/src/lib.rs index b95889c16c49c..0fce84ded8423 100644 --- a/crates/ruff_cli/src/lib.rs +++ b/crates/ruff_cli/src/lib.rs @@ -73,7 +73,7 @@ quoting the executed command, along with the relevant file contents and `pyproje set_up_logging(&log_level)?; match command { - Command::Rule { rule, format } => commands::rule::rule(&rule, format)?, + Command::Rule { rule, format } => commands::rule::rule(rule, format)?, Command::Config { option } => return Ok(commands::config::config(option.as_deref())), Command::Linter { format } => commands::linter::linter(format)?, Command::Clean => commands::clean::clean(log_level)?, diff --git a/crates/ruff_cli/src/printer.rs b/crates/ruff_cli/src/printer.rs index 2f52aa8d89558..36ac3c54248e3 100644 --- a/crates/ruff_cli/src/printer.rs +++ b/crates/ruff_cli/src/printer.rs @@ -46,7 +46,7 @@ struct ExpandedFix<'a> { #[derive(Serialize)] struct ExpandedMessage<'a> { - code: SerializeRuleAsCode<'a>, + code: SerializeRuleAsCode, message: String, fix: Option>, location: Location, @@ -63,9 +63,9 @@ struct ExpandedStatistics { fixable: bool, } -struct SerializeRuleAsCode<'a>(&'a Rule); +struct SerializeRuleAsCode(Rule); -impl Serialize for SerializeRuleAsCode<'_> { +impl Serialize for SerializeRuleAsCode { fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, @@ -74,8 +74,8 @@ impl Serialize for SerializeRuleAsCode<'_> { } } -impl<'a> From<&'a Rule> for SerializeRuleAsCode<'a> { - fn from(rule: &'a Rule) -> Self { +impl From for SerializeRuleAsCode { + fn from(rule: Rule) -> Self { Self(rule) } } @@ -405,13 +405,13 @@ impl Printer { } pub fn write_statistics(&self, diagnostics: &Diagnostics) -> Result<()> { - let violations: Vec<&Rule> = diagnostics + let violations: Vec = diagnostics .messages .iter() .map(|message| message.kind.rule()) .sorted() .dedup() - .collect::>(); + .collect(); if violations.is_empty() { return Ok(()); } @@ -537,11 +537,11 @@ impl Printer { } } -fn group_messages_by_filename(messages: &[Message]) -> BTreeMap<&String, Vec<&Message>> { +fn group_messages_by_filename(messages: &[Message]) -> BTreeMap<&str, Vec<&Message>> { let mut grouped_messages = BTreeMap::default(); for message in messages { grouped_messages - .entry(&message.filename) + .entry(message.filename.as_str()) .or_insert_with(Vec::new) .push(message); } diff --git a/crates/ruff_dev/src/generate_rules_table.rs b/crates/ruff_dev/src/generate_rules_table.rs index 17f260300249e..cf4c25b4c69f8 100644 --- a/crates/ruff_dev/src/generate_rules_table.rs +++ b/crates/ruff_dev/src/generate_rules_table.rs @@ -23,7 +23,7 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator, table_out.push_str(&format!( "| {}{} | {} | {} | {} |", linter.common_prefix(), - linter.code_for_rule(&rule).unwrap(), + linter.code_for_rule(rule).unwrap(), rule.explanation() .is_some() .then_some(format_args!("[{rule_name}](rules/{rule_name}.md)")) diff --git a/crates/ruff_macros/src/map_codes.rs b/crates/ruff_macros/src/map_codes.rs index 32036ac1f422c..e56130422777d 100644 --- a/crates/ruff_macros/src/map_codes.rs +++ b/crates/ruff_macros/src/map_codes.rs @@ -167,7 +167,7 @@ pub fn map_codes(func: &ItemFn) -> syn::Result { }); #[allow(clippy::type_complexity)] - let mut rule_to_codes: HashMap<&Path, Vec<(&Ident, &String, &Vec)>> = HashMap::new(); + let mut rule_to_codes: HashMap<&Path, Vec<(&Ident, &str, &Vec)>> = HashMap::new(); let mut linter_code_for_rule_match_arms = quote!(); for (linter, map) in &linters { @@ -227,7 +227,7 @@ pub fn map_codes(func: &ItemFn) -> syn::Result { } impl crate::registry::Linter { - pub fn code_for_rule(&self, rule: &Rule) -> Option<&'static str> { + pub fn code_for_rule(&self, rule: Rule) -> Option<&'static str> { match (self, rule) { #linter_code_for_rule_match_arms _ => None, diff --git a/crates/ruff_macros/src/register_rules.rs b/crates/ruff_macros/src/register_rules.rs index b8e67d4695f2a..3ead9548d6b82 100644 --- a/crates/ruff_macros/src/register_rules.rs +++ b/crates/ruff_macros/src/register_rules.rs @@ -24,8 +24,7 @@ pub fn register_rules(input: &Input) -> proc_macro2::TokenStream { rule_explanation_match_arms.extend(quote! {#(#attr)* Self::#name => #path::explanation(),}); // Enable conversion from `DiagnosticKind` to `Rule`. - from_impls_for_diagnostic_kind - .extend(quote! {#(#attr)* stringify!(#name) => &Rule::#name,}); + from_impls_for_diagnostic_kind.extend(quote! {#(#attr)* stringify!(#name) => Rule::#name,}); } quote! { @@ -34,6 +33,7 @@ pub fn register_rules(input: &Input) -> proc_macro2::TokenStream { Debug, PartialEq, Eq, + Copy, Clone, Hash, PartialOrd, @@ -57,17 +57,13 @@ pub fn register_rules(input: &Input) -> proc_macro2::TokenStream { } /// Returns the autofix status of this rule. - pub fn autofixable(&self) -> Option { + pub const fn autofixable(&self) -> Option { match self { #rule_autofixable_match_arms } } } - pub trait AsRule { - fn rule(&self) -> &'static Rule; - } - impl AsRule for ruff_diagnostics::DiagnosticKind { - fn rule(&self) -> &'static Rule { + fn rule(&self) -> Rule { match self.name.as_str() { #from_impls_for_diagnostic_kind _ => unreachable!("invalid rule name: {}", self.name), From e99e1fae2b3a176f7f475a5ec5c14ed88d97d52b Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Fri, 17 Mar 2023 02:19:48 +0800 Subject: [PATCH 29/49] ci: add `python/typeshed` to ecosystem check (#3559) --- scripts/check_ecosystem.py | 44 +++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/scripts/check_ecosystem.py b/scripts/check_ecosystem.py index b285befb53702..dd19b452eb95f 100755 --- a/scripts/check_ecosystem.py +++ b/scripts/check_ecosystem.py @@ -29,6 +29,9 @@ class Repository(NamedTuple): org: str repo: str ref: str + select: str = "ALL" + ignore: str = "" + exclude: str = "" @asynccontextmanager async def clone(self: Self) -> "AsyncIterator[Path]": @@ -59,6 +62,7 @@ async def clone(self: Self) -> "AsyncIterator[Path]": "bokeh": Repository("bokeh", "bokeh", "branch-3.2"), "scikit-build": Repository("scikit-build", "scikit-build", "main"), "airflow": Repository("apache", "airflow", "main"), + "typeshed": Repository("python", "typeshed", "main", select="PYI"), } SUMMARY_LINE_RE = re.compile(r"^(Found \d+ error.*)|(.*potentially fixable with.*)$") @@ -68,15 +72,23 @@ class RuffError(Exception): """An error reported by ruff.""" -async def check(*, ruff: Path, path: Path) -> "Sequence[str]": +async def check( + *, + ruff: Path, + path: Path, + select: str, + ignore: str = "", + exclude: str = "", +) -> "Sequence[str]": """Run the given ruff binary against the specified path.""" + ruff_args = ["check", "--no-cache", "--exit-zero", "--select", select] + if ignore: + ruff_args.extend(["--ignore", ignore]) + if exclude: + ruff_args.extend(["--exclude", exclude]) proc = await create_subprocess_exec( ruff.absolute(), - "check", - "--no-cache", - "--exit-zero", - "--select", - "ALL", + *ruff_args, ".", stdout=PIPE, stderr=PIPE, @@ -123,8 +135,24 @@ async def compare(ruff1: Path, ruff2: Path, repo: Repository) -> Diff | None: async with repo.clone() as path: try: async with asyncio.TaskGroup() as tg: - check1 = tg.create_task(check(ruff=ruff1, path=path)) - check2 = tg.create_task(check(ruff=ruff2, path=path)) + check1 = tg.create_task( + check( + ruff=ruff1, + path=path, + select=repo.select, + ignore=repo.ignore, + exclude=repo.exclude, + ), + ) + check2 = tg.create_task( + check( + ruff=ruff2, + path=path, + select=repo.select, + ignore=repo.ignore, + exclude=repo.exclude, + ), + ) except ExceptionGroup as e: raise e.exceptions[0] from e From 72febf98b7b3db33cf835ca160921700a07d9914 Mon Sep 17 00:00:00 2001 From: Nyakku Shigure Date: Fri, 17 Mar 2023 02:20:11 +0800 Subject: [PATCH 30/49] add PaddlePaddle to `Who's Using Ruff?` (#3562) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6e765d21d3adb..cda888a15b361 100644 --- a/README.md +++ b/README.md @@ -309,6 +309,7 @@ Ruff is used in a number of major open-source projects, including: - [Starlite](https://github.com/starlite-api/starlite) - [telemetry-airflow (Mozilla)](https://github.com/mozilla/telemetry-airflow) - [Stable Baselines3](https://github.com/DLR-RM/stable-baselines3) +- [PaddlePaddle](https://github.com/PaddlePaddle/Paddle) ## License From 667130a4c32db676f4e93ed89b63e86293a3f48c Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 16 Mar 2023 19:32:17 -0400 Subject: [PATCH 31/49] Add some additional users to the users list (#3565) --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index cda888a15b361..509255cc8ecce 100644 --- a/README.md +++ b/README.md @@ -310,6 +310,9 @@ Ruff is used in a number of major open-source projects, including: - [telemetry-airflow (Mozilla)](https://github.com/mozilla/telemetry-airflow) - [Stable Baselines3](https://github.com/DLR-RM/stable-baselines3) - [PaddlePaddle](https://github.com/PaddlePaddle/Paddle) +- [nox](https://github.com/wntrblm/nox) +- [Neon](https://github.com/neondatabase/neon) +- [The Algorithms](https://github.com/TheAlgorithms/Python) ## License From bbc87b717734d08edff396915b286dd17c297bc2 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 16 Mar 2023 16:46:42 -0700 Subject: [PATCH 32/49] ci(check_ecosystem): add scikit-build-core (#3563) --- scripts/check_ecosystem.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/check_ecosystem.py b/scripts/check_ecosystem.py index dd19b452eb95f..153eeb7e906d0 100755 --- a/scripts/check_ecosystem.py +++ b/scripts/check_ecosystem.py @@ -61,6 +61,7 @@ async def clone(self: Self) -> "AsyncIterator[Path]": "zulip": Repository("zulip", "zulip", "main"), "bokeh": Repository("bokeh", "bokeh", "branch-3.2"), "scikit-build": Repository("scikit-build", "scikit-build", "main"), + "scikit-build-core": Repository("scikit-build", "scikit-build-core", "main"), "airflow": Repository("apache", "airflow", "main"), "typeshed": Repository("python", "typeshed", "main", select="PYI"), } From e0df62b841500bd60b27ada7e9169aeb48036bc2 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 16 Mar 2023 20:54:29 -0400 Subject: [PATCH 33/49] Rewrite mock import with starred imports (#3566) --- .../test/fixtures/pyupgrade/UP026.py | 31 ++- .../pyupgrade/rules/rewrite_mock_import.rs | 91 ++++--- ...ff__rules__pyupgrade__tests__UP026.py.snap | 226 ++++++++++-------- 3 files changed, 194 insertions(+), 154 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/pyupgrade/UP026.py b/crates/ruff/resources/test/fixtures/pyupgrade/UP026.py index badd19a210b24..b61b4c299ca8e 100644 --- a/crates/ruff/resources/test/fixtures/pyupgrade/UP026.py +++ b/crates/ruff/resources/test/fixtures/pyupgrade/UP026.py @@ -1,24 +1,29 @@ -# These should be changed +# Error (`from unittest import mock`) if True: import mock +# Error (`from unittest import mock`) if True: import mock, sys -# This goes to from unitest import mock +# Error (`from unittest.mock import *`) +if True: + from mock import * + +# Error (`from unittest import mock`) import mock.mock -# Mock should go on a new line as `from unittest import mock` +# Error (`from unittest import mock`) import contextlib, mock, sys -# Mock should go on a new line as `from unittest import mock` +# Error (`from unittest import mock`) import mock, sys x = "This code should be preserved one line below the mock" -# Mock should go on a new line as `from unittest import mock` +# Error (`from unittest import mock`) from mock import mock -# Should keep trailing comma +# Error (keep trailing comma) from mock import ( mock, a, @@ -32,7 +37,7 @@ mock, ) -# Should not get a trailing comma +# Error (avoid trailing comma) from mock import ( mock, a, @@ -57,16 +62,16 @@ c ) -# These should not change: +# OK import os, io -# Mock should go on a new line as `from unittest import mock` +# Error (`from unittest import mock`) import mock, mock -# Mock should go on a new line as `from unittest import mock as foo` +# Error (`from unittest import mock as foo`) import mock as foo -# Mock should go on a new line as `from unittest import mock as foo` +# Error (`from unittest import mock as foo`) from mock import mock as foo if True: @@ -81,8 +86,8 @@ from mock import mock as foo, mock as bar, mock -# This should be unchanged. +# OK. x = mock.Mock() -# This should change to `mock.Mock`(). +# Error (`mock.Mock()`). x = mock.mock.Mock() diff --git a/crates/ruff/src/rules/pyupgrade/rules/rewrite_mock_import.rs b/crates/ruff/src/rules/pyupgrade/rules/rewrite_mock_import.rs index b1b91225916c9..924dcc4ae0d96 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/rewrite_mock_import.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/rewrite_mock_import.rs @@ -93,22 +93,6 @@ fn clean_import_aliases(aliases: Vec) -> (Vec, Vec bool { - for alias in aliases { - let ImportAlias { name, .. } = &alias; - // Ex) `import mock.mock` - if let NameOrAttribute::A(attribute_struct) = name { - if let Expression::Name(name_struct) = &*attribute_struct.value { - if name_struct.value == "mock" && attribute_struct.attr.value == "mock" { - return true; - } - } - } - } - false -} - fn format_mocks(aliases: Vec>, indent: &str, stylist: &Stylist) -> String { let mut content = String::new(); for alias in aliases { @@ -180,20 +164,12 @@ fn format_import_from( let mut tree = match_module(module_text).unwrap(); let mut import = match_import_from(&mut tree)?; - let ImportFrom { - names: ImportNames::Aliases(aliases), + if let ImportFrom { + names: ImportNames::Star(..), .. - } = import.clone() else { - unreachable!("Expected ImportNames::Aliases"); - }; - - let has_mock_member = includes_mock_member(&aliases); - let (clean_aliases, mock_aliases) = clean_import_aliases(aliases); - - Ok(if clean_aliases.is_empty() { - format_mocks(mock_aliases, indent, stylist) - } else { - import.names = ImportNames::Aliases(clean_aliases); + } = import + { + // Ex) `from mock import *` import.module = Some(NameOrAttribute::A(Box::new(Attribute { value: Box::new(Expression::Name(Box::new(Name { value: "unittest", @@ -212,22 +188,61 @@ fn format_import_from( lpar: vec![], rpar: vec![], }))); - let mut state = CodegenState { default_newline: stylist.line_ending(), default_indent: stylist.indentation(), ..CodegenState::default() }; tree.codegen(&mut state); + Ok(state.to_string()) + } else if let ImportFrom { + names: ImportNames::Aliases(aliases), + .. + } = import + { + // Ex) `from mock import mock` + let (clean_aliases, mock_aliases) = clean_import_aliases(aliases.clone()); + Ok(if clean_aliases.is_empty() { + format_mocks(mock_aliases, indent, stylist) + } else { + import.names = ImportNames::Aliases(clean_aliases); + import.module = Some(NameOrAttribute::A(Box::new(Attribute { + value: Box::new(Expression::Name(Box::new(Name { + value: "unittest", + lpar: vec![], + rpar: vec![], + }))), + attr: Name { + value: "mock", + lpar: vec![], + rpar: vec![], + }, + dot: Dot { + whitespace_before: ParenthesizableWhitespace::default(), + whitespace_after: ParenthesizableWhitespace::default(), + }, + lpar: vec![], + rpar: vec![], + }))); - let mut content = state.to_string(); - if has_mock_member { - content.push_str(stylist.line_ending()); - content.push_str(indent); - content.push_str(&format_mocks(mock_aliases, indent, stylist)); - } - content - }) + let mut state = CodegenState { + default_newline: stylist.line_ending(), + default_indent: stylist.indentation(), + ..CodegenState::default() + }; + tree.codegen(&mut state); + + let mut content = state.to_string(); + if !mock_aliases.is_empty() { + content.push_str(stylist.line_ending()); + content.push_str(indent); + content.push_str(&format_mocks(mock_aliases, indent, stylist)); + } + content + }) + } else { + unreachable!("Expected ImportNames::Aliases | ImportNames::Star"); + } } /// UP026 diff --git a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP026.py.snap b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP026.py.snap index 3a5641049c81d..49a288bdd1e0b 100644 --- a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP026.py.snap +++ b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP026.py.snap @@ -28,18 +28,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 6 + row: 7 column: 11 end_location: - row: 6 + row: 7 column: 15 fix: content: "import sys\n from unittest import mock" location: - row: 6 + row: 7 column: 4 end_location: - row: 6 + row: 7 column: 20 parent: ~ - kind: @@ -48,18 +48,38 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 9 + row: 11 + column: 4 + end_location: + row: 11 + column: 22 + fix: + content: from unittest.mock import * + location: + row: 11 + column: 4 + end_location: + row: 11 + column: 22 + parent: ~ +- kind: + name: RewriteMockImport + body: "`mock` is deprecated, use `unittest.mock`" + suggestion: "Import from `unittest.mock` instead" + fixable: true + location: + row: 14 column: 7 end_location: - row: 9 + row: 14 column: 16 fix: content: from unittest import mock location: - row: 9 + row: 14 column: 0 end_location: - row: 9 + row: 14 column: 16 parent: ~ - kind: @@ -68,18 +88,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 12 + row: 17 column: 19 end_location: - row: 12 + row: 17 column: 23 fix: content: "import contextlib, sys\nfrom unittest import mock" location: - row: 12 + row: 17 column: 0 end_location: - row: 12 + row: 17 column: 28 parent: ~ - kind: @@ -88,18 +108,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 15 + row: 20 column: 7 end_location: - row: 15 + row: 20 column: 11 fix: content: "import sys\nfrom unittest import mock" location: - row: 15 + row: 20 column: 0 end_location: - row: 15 + row: 20 column: 16 parent: ~ - kind: @@ -108,18 +128,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 19 + row: 24 column: 0 end_location: - row: 19 + row: 24 column: 21 fix: content: from unittest import mock location: - row: 19 + row: 24 column: 0 end_location: - row: 19 + row: 24 column: 21 parent: ~ - kind: @@ -128,18 +148,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 22 + row: 27 column: 0 end_location: - row: 27 + row: 32 column: 1 fix: - content: "from unittest.mock import (\n a,\n b,\n c,\n)" + content: "from unittest.mock import (\n a,\n b,\n c,\n)\nfrom unittest import mock" location: - row: 22 + row: 27 column: 0 end_location: - row: 27 + row: 32 column: 1 parent: ~ - kind: @@ -148,18 +168,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 28 + row: 33 column: 0 end_location: - row: 33 + row: 38 column: 1 fix: - content: "from unittest.mock import (\n a,\n b,\n c,\n)" + content: "from unittest.mock import (\n a,\n b,\n c,\n)\nfrom unittest import mock" location: - row: 28 + row: 33 column: 0 end_location: - row: 33 + row: 38 column: 1 parent: ~ - kind: @@ -168,18 +188,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 36 + row: 41 column: 0 end_location: - row: 41 + row: 46 column: 1 fix: - content: "from unittest.mock import (\n a,\n b,\n c\n)" + content: "from unittest.mock import (\n a,\n b,\n c\n)\nfrom unittest import mock" location: - row: 36 + row: 41 column: 0 end_location: - row: 41 + row: 46 column: 1 parent: ~ - kind: @@ -188,18 +208,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 42 + row: 47 column: 0 end_location: - row: 47 + row: 52 column: 1 fix: - content: "from unittest.mock import (\n a,\n b,\n c\n)" + content: "from unittest.mock import (\n a,\n b,\n c\n)\nfrom unittest import mock" location: - row: 42 + row: 47 column: 0 end_location: - row: 47 + row: 52 column: 1 parent: ~ - kind: @@ -208,18 +228,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 48 + row: 53 column: 0 end_location: - row: 48 + row: 53 column: 30 fix: - content: "from unittest.mock import a, b, c" + content: "from unittest.mock import a, b, c\nfrom unittest import mock" location: - row: 48 + row: 53 column: 0 end_location: - row: 48 + row: 53 column: 30 parent: ~ - kind: @@ -228,18 +248,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 49 + row: 54 column: 0 end_location: - row: 49 + row: 54 column: 30 fix: - content: "from unittest.mock import a, b, c" + content: "from unittest.mock import a, b, c\nfrom unittest import mock" location: - row: 49 + row: 54 column: 0 end_location: - row: 49 + row: 54 column: 30 parent: ~ - kind: @@ -248,18 +268,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 53 + row: 58 column: 8 end_location: - row: 58 + row: 63 column: 9 fix: - content: "from unittest.mock import (\n a,\n b,\n c\n )" + content: "from unittest.mock import (\n a,\n b,\n c\n )\n from unittest import mock" location: - row: 53 + row: 58 column: 8 end_location: - row: 58 + row: 63 column: 9 parent: ~ - kind: @@ -268,18 +288,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 64 + row: 69 column: 7 end_location: - row: 64 + row: 69 column: 11 fix: content: "from unittest import mock\nfrom unittest import mock" location: - row: 64 + row: 69 column: 0 end_location: - row: 64 + row: 69 column: 17 parent: ~ - kind: @@ -288,18 +308,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 64 + row: 69 column: 13 end_location: - row: 64 + row: 69 column: 17 fix: content: "from unittest import mock\nfrom unittest import mock" location: - row: 64 + row: 69 column: 0 end_location: - row: 64 + row: 69 column: 17 parent: ~ - kind: @@ -308,18 +328,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 67 + row: 72 column: 7 end_location: - row: 67 + row: 72 column: 18 fix: content: from unittest import mock as foo location: - row: 67 + row: 72 column: 0 end_location: - row: 67 + row: 72 column: 18 parent: ~ - kind: @@ -328,18 +348,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 70 + row: 75 column: 0 end_location: - row: 70 + row: 75 column: 28 fix: content: from unittest import mock as foo location: - row: 70 + row: 75 column: 0 end_location: - row: 70 + row: 75 column: 28 parent: ~ - kind: @@ -348,18 +368,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 74 + row: 79 column: 11 end_location: - row: 74 + row: 79 column: 22 fix: content: "from unittest import mock as foo\n from unittest import mock as bar\n from unittest import mock" location: - row: 74 + row: 79 column: 4 end_location: - row: 74 + row: 79 column: 41 parent: ~ - kind: @@ -368,18 +388,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 74 + row: 79 column: 24 end_location: - row: 74 + row: 79 column: 35 fix: content: "from unittest import mock as foo\n from unittest import mock as bar\n from unittest import mock" location: - row: 74 + row: 79 column: 4 end_location: - row: 74 + row: 79 column: 41 parent: ~ - kind: @@ -388,18 +408,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 74 + row: 79 column: 37 end_location: - row: 74 + row: 79 column: 41 fix: content: "from unittest import mock as foo\n from unittest import mock as bar\n from unittest import mock" location: - row: 74 + row: 79 column: 4 end_location: - row: 74 + row: 79 column: 41 parent: ~ - kind: @@ -408,18 +428,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 77 + row: 82 column: 11 end_location: - row: 77 + row: 82 column: 22 fix: content: "import os\n from unittest import mock as foo\n from unittest import mock as bar\n from unittest import mock" location: - row: 77 + row: 82 column: 4 end_location: - row: 77 + row: 82 column: 45 parent: ~ - kind: @@ -428,18 +448,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 77 + row: 82 column: 24 end_location: - row: 77 + row: 82 column: 35 fix: content: "import os\n from unittest import mock as foo\n from unittest import mock as bar\n from unittest import mock" location: - row: 77 + row: 82 column: 4 end_location: - row: 77 + row: 82 column: 45 parent: ~ - kind: @@ -448,18 +468,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 77 + row: 82 column: 37 end_location: - row: 77 + row: 82 column: 41 fix: content: "import os\n from unittest import mock as foo\n from unittest import mock as bar\n from unittest import mock" location: - row: 77 + row: 82 column: 4 end_location: - row: 77 + row: 82 column: 45 parent: ~ - kind: @@ -468,18 +488,18 @@ expression: diagnostics suggestion: "Import from `unittest.mock` instead" fixable: true location: - row: 81 + row: 86 column: 4 end_location: - row: 81 + row: 86 column: 51 fix: content: "from unittest import mock as foo\n from unittest import mock as bar\n from unittest import mock" location: - row: 81 + row: 86 column: 4 end_location: - row: 81 + row: 86 column: 51 parent: ~ - kind: @@ -488,18 +508,18 @@ expression: diagnostics suggestion: "Replace `mock.mock` with `mock`" fixable: true location: - row: 88 + row: 93 column: 4 end_location: - row: 88 + row: 93 column: 13 fix: content: mock location: - row: 88 + row: 93 column: 4 end_location: - row: 88 + row: 93 column: 13 parent: ~ From d9ed0aae6999f7c89f5ecf2c79a3ca99f2ed798a Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 16 Mar 2023 19:34:56 -0700 Subject: [PATCH 34/49] ci(check_ecosystem): add cibuildwheel (#3567) --- scripts/check_ecosystem.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/check_ecosystem.py b/scripts/check_ecosystem.py index 153eeb7e906d0..ccbf1d76299b3 100755 --- a/scripts/check_ecosystem.py +++ b/scripts/check_ecosystem.py @@ -62,6 +62,7 @@ async def clone(self: Self) -> "AsyncIterator[Path]": "bokeh": Repository("bokeh", "bokeh", "branch-3.2"), "scikit-build": Repository("scikit-build", "scikit-build", "main"), "scikit-build-core": Repository("scikit-build", "scikit-build-core", "main"), + "cibuildwheel": Repository("pypa", "cibuildwheel", "main"), "airflow": Repository("apache", "airflow", "main"), "typeshed": Repository("python", "typeshed", "main", select="PYI"), } From f5e5caaa25cabbf4b740f7c8dc6df00b043b8711 Mon Sep 17 00:00:00 2001 From: Jonathan Plasse <13716151+JonathanPlasse@users.noreply.github.com> Date: Fri, 17 Mar 2023 03:36:25 +0100 Subject: [PATCH 35/49] Fix autofix conflict between `D209` and `D400` (#3564) --- .../test/fixtures/pydocstyle/D209_D400.py | 3 ++ crates/ruff/src/autofix/mod.rs | 16 ++++++- crates/ruff/src/rules/pydocstyle/mod.rs | 10 +++++ ...__rules__pydocstyle__tests__d209_d400.snap | 45 +++++++++++++++++++ 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 crates/ruff/resources/test/fixtures/pydocstyle/D209_D400.py create mode 100644 crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__d209_d400.snap diff --git a/crates/ruff/resources/test/fixtures/pydocstyle/D209_D400.py b/crates/ruff/resources/test/fixtures/pydocstyle/D209_D400.py new file mode 100644 index 0000000000000..a7e6732451f97 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pydocstyle/D209_D400.py @@ -0,0 +1,3 @@ +def lorem(): + """lorem ipsum dolor sit amet consectetur adipiscing elit + sed do eiusmod tempor incididunt ut labore et dolore magna aliqua""" diff --git a/crates/ruff/src/autofix/mod.rs b/crates/ruff/src/autofix/mod.rs index a88a474915b0f..d6e2e4bb84d4e 100644 --- a/crates/ruff/src/autofix/mod.rs +++ b/crates/ruff/src/autofix/mod.rs @@ -9,7 +9,7 @@ use ruff_python_ast::source_code::Locator; use ruff_python_ast::types::Range; use crate::linter::FixTable; -use crate::registry::AsRule; +use crate::registry::{AsRule, Rule}; pub mod helpers; @@ -39,7 +39,7 @@ fn apply_fixes<'a>( .as_ref() .map(|fix| (diagnostic.kind.rule(), fix)) }) - .sorted_by_key(|(.., fix)| fix.location) + .sorted_by(|(rule1, fix1), (rule2, fix2)| cmp_fix(*rule1, *rule2, fix1, fix2)) { // If we already applied an identical fix as part of another correction, skip // any re-application. @@ -92,6 +92,18 @@ pub(crate) fn apply_fix(fix: &Fix, locator: &Locator) -> String { output } +/// Compare two fixes. +fn cmp_fix(rule1: Rule, rule2: Rule, fix1: &Fix, fix2: &Fix) -> std::cmp::Ordering { + fix1.location + .cmp(&fix2.location) + .then_with(|| match (&rule1, &rule2) { + // Apply `EndsInPeriod` fixes before `NewLineAfterLastParagraph` fixes. + (Rule::EndsInPeriod, Rule::NewLineAfterLastParagraph) => std::cmp::Ordering::Less, + (Rule::NewLineAfterLastParagraph, Rule::EndsInPeriod) => std::cmp::Ordering::Greater, + _ => std::cmp::Ordering::Equal, + }) +} + #[cfg(test)] mod tests { use rustpython_parser::ast::Location; diff --git a/crates/ruff/src/rules/pydocstyle/mod.rs b/crates/ruff/src/rules/pydocstyle/mod.rs index 139becc9cf566..85eec194a4750 100644 --- a/crates/ruff/src/rules/pydocstyle/mod.rs +++ b/crates/ruff/src/rules/pydocstyle/mod.rs @@ -153,4 +153,14 @@ mod tests { assert_yaml_snapshot!(diagnostics); Ok(()) } + + #[test] + fn d209_d400() -> Result<()> { + let diagnostics = test_path( + Path::new("pydocstyle/D209_D400.py"), + &settings::Settings::for_rules([Rule::NewLineAfterLastParagraph, Rule::EndsInPeriod]), + )?; + assert_yaml_snapshot!(diagnostics); + Ok(()) + } } diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__d209_d400.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__d209_d400.snap new file mode 100644 index 0000000000000..01db548f2ac7c --- /dev/null +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__d209_d400.snap @@ -0,0 +1,45 @@ +--- +source: crates/ruff/src/rules/pydocstyle/mod.rs +expression: diagnostics +--- +- kind: + name: NewLineAfterLastParagraph + body: Multi-line docstring closing quotes should be on a separate line + suggestion: Move closing quotes to new line + fixable: true + location: + row: 2 + column: 4 + end_location: + row: 3 + column: 72 + fix: + content: "\n " + location: + row: 3 + column: 69 + end_location: + row: 3 + column: 69 + parent: ~ +- kind: + name: EndsInPeriod + body: First line should end with a period + suggestion: Add period + fixable: true + location: + row: 2 + column: 4 + end_location: + row: 3 + column: 72 + fix: + content: "." + location: + row: 3 + column: 69 + end_location: + row: 3 + column: 69 + parent: ~ + From 73df2676354953637e01dd492b6ed3b6a6bd4f7f Mon Sep 17 00:00:00 2001 From: Jacob Latonis Date: Thu, 16 Mar 2023 21:47:49 -0500 Subject: [PATCH 36/49] [`pylint`]: Implement `continue-in-finally` (`E0116`) (#3541) --- .../fixtures/pylint/continue_in_finally.py | 95 ++++++++++ crates/ruff/src/checkers/ast/mod.rs | 8 + crates/ruff/src/codes.rs | 1 + crates/ruff/src/registry.rs | 1 + crates/ruff/src/rules/pylint/mod.rs | 15 ++ .../rules/pylint/rules/continue_in_finally.rs | 80 ++++++++ crates/ruff/src/rules/pylint/rules/mod.rs | 2 + ...tests__PLE0116_continue_in_finally.py.snap | 6 + ...s__pylint__tests__continue_in_finally.snap | 174 ++++++++++++++++++ ruff.schema.json | 1 + 10 files changed, 383 insertions(+) create mode 100644 crates/ruff/resources/test/fixtures/pylint/continue_in_finally.py create mode 100644 crates/ruff/src/rules/pylint/rules/continue_in_finally.rs create mode 100644 crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE0116_continue_in_finally.py.snap create mode 100644 crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__continue_in_finally.snap diff --git a/crates/ruff/resources/test/fixtures/pylint/continue_in_finally.py b/crates/ruff/resources/test/fixtures/pylint/continue_in_finally.py new file mode 100644 index 0000000000000..282026e5532f0 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pylint/continue_in_finally.py @@ -0,0 +1,95 @@ +while True: + try: + pass + finally: + continue # [continue-in-finally] + +while True: + try: + pass + except Exception: + continue + finally: + try: + pass + finally: + continue # [continue-in-finally] + pass + +while True: + try: + pass + finally: + test = "aa" + match test: + case "aa": + continue # [continue-in-finally] + +while True: + try: + pass + finally: + with "aa" as f: + continue # [continue-in-finally] + +while True: + try: + pass + finally: + if True: + continue # [continue-in-finally] + continue # [continue-in-finally] + + def test(): + while True: + continue + try: + pass + finally: + continue # [continue-in-finally] + + +while True: + try: + pass + finally: + continue # [continue-in-finally] + + def test(): + while True: + continue + + +while True: + try: + pass + finally: + for i in range(12): + continue + continue # [continue-in-finally] + + while True: + pass + else: + continue # [continue-in-finally] + + def test(): + continue + while True: + continue + + +while True: + try: + pass + finally: + if True: + pass + elif False: + continue # [continue-in-finally] + else: + continue # [continue-in-finally] + for i in range(10): + pass + else: + continue # [continue-in-finally] diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 633f9033832ec..9b6088b1a7e09 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -2041,9 +2041,17 @@ where } self.ctx.handled_exceptions.push(handled_exceptions); + if self.settings.rules.enabled(Rule::JumpStatementInFinally) { flake8_bugbear::rules::jump_statement_in_finally(self, finalbody); } + + if self.settings.rules.enabled(Rule::ContinueInFinally) { + if self.settings.target_version <= PythonVersion::Py38 { + pylint::rules::continue_in_finally(self, finalbody); + } + } + self.visit_body(body); self.ctx.handled_exceptions.pop(); diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 48c45a7544433..d74df55579376 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -166,6 +166,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option { (Pylint, "C3002") => Rule::UnnecessaryDirectLambdaCall, (Pylint, "E0100") => Rule::YieldInInit, (Pylint, "E0101") => Rule::ReturnInInit, + (Pylint, "E0116") => Rule::ContinueInFinally, (Pylint, "E0117") => Rule::NonlocalWithoutBinding, (Pylint, "E0118") => Rule::UsedPriorGlobalDeclaration, (Pylint, "E0604") => Rule::InvalidAllObject, diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 32453d9cefa7f..5b448804aca28 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -152,6 +152,7 @@ ruff_macros::register_rules!( rules::pylint::rules::BidirectionalUnicode, rules::pylint::rules::BadStrStripCall, rules::pylint::rules::CollapsibleElseIf, + rules::pylint::rules::ContinueInFinally, rules::pylint::rules::UselessImportAlias, rules::pylint::rules::UnnecessaryDirectLambdaCall, rules::pylint::rules::NonlocalWithoutBinding, diff --git a/crates/ruff/src/rules/pylint/mod.rs b/crates/ruff/src/rules/pylint/mod.rs index 052f3db1886bf..635d163e78861 100644 --- a/crates/ruff/src/rules/pylint/mod.rs +++ b/crates/ruff/src/rules/pylint/mod.rs @@ -14,6 +14,7 @@ mod tests { use crate::registry::Rule; use crate::rules::pylint; + use crate::settings::types::PythonVersion; use crate::settings::Settings; use crate::test::test_path; @@ -37,6 +38,7 @@ mod tests { #[test_case(Rule::ConsiderUsingSysExit, Path::new("consider_using_sys_exit_4.py"); "PLR1722_4")] #[test_case(Rule::ConsiderUsingSysExit, Path::new("consider_using_sys_exit_5.py"); "PLR1722_5")] #[test_case(Rule::ConsiderUsingSysExit, Path::new("consider_using_sys_exit_6.py"); "PLR1722_6")] + #[test_case(Rule::ContinueInFinally, Path::new("continue_in_finally.py"); "PLE0116")] #[test_case(Rule::MagicValueComparison, Path::new("magic_value_comparison.py"); "PLR2004")] #[test_case(Rule::UselessElseOnLoop, Path::new("useless_else_on_loop.py"); "PLW0120")] #[test_case(Rule::GlobalVariableNotAssigned, Path::new("global_variable_not_assigned.py"); "PLW0602")] @@ -65,6 +67,19 @@ mod tests { Ok(()) } + #[test] + fn continue_in_finally() -> Result<()> { + let diagnostics = test_path( + Path::new("pylint/continue_in_finally.py"), + &Settings { + target_version: PythonVersion::Py37, + ..Settings::for_rules(vec![Rule::ContinueInFinally]) + }, + )?; + assert_yaml_snapshot!(diagnostics); + Ok(()) + } + #[test] fn allow_magic_value_types() -> Result<()> { let diagnostics = test_path( diff --git a/crates/ruff/src/rules/pylint/rules/continue_in_finally.rs b/crates/ruff/src/rules/pylint/rules/continue_in_finally.rs new file mode 100644 index 0000000000000..997e5a72ea7df --- /dev/null +++ b/crates/ruff/src/rules/pylint/rules/continue_in_finally.rs @@ -0,0 +1,80 @@ +use rustpython_parser::ast::{Stmt, StmtKind}; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::types::Range; + +use crate::checkers::ast::Checker; + +/// ## What it does +/// Checks for `continue` statements inside `finally` +/// +/// ## Why is this bad? +/// `continue` statements were not allowed within `finally` clauses prior to +/// Python 3.8. Using a `continue` statement within a `finally` clause can +/// cause a `SyntaxError`. +/// +/// ## Example +/// ```python +/// while True: +/// try: +/// pass +/// finally: +/// continue +/// ``` +/// +/// Use instead: +/// ```python +/// while True: +/// try: +/// pass +/// except Exception: +/// pass +/// else: +/// continue +/// ``` +#[violation] +pub struct ContinueInFinally; + +impl Violation for ContinueInFinally { + #[derive_message_formats] + fn message(&self) -> String { + format!("`continue` not supported inside `finally` clause") + } +} + +fn traverse_body(checker: &mut Checker, body: &[Stmt]) { + for stmt in body { + if matches!(stmt.node, StmtKind::Continue { .. }) { + checker + .diagnostics + .push(Diagnostic::new(ContinueInFinally, Range::from(stmt))); + } + + match &stmt.node { + StmtKind::If { body, orelse, .. } + | StmtKind::Try { body, orelse, .. } + | StmtKind::TryStar { body, orelse, .. } => { + traverse_body(checker, body); + traverse_body(checker, orelse); + } + StmtKind::For { orelse, .. } + | StmtKind::AsyncFor { orelse, .. } + | StmtKind::While { orelse, .. } => traverse_body(checker, orelse), + StmtKind::With { body, .. } | StmtKind::AsyncWith { body, .. } => { + traverse_body(checker, body); + } + StmtKind::Match { cases, .. } => { + for case in cases { + traverse_body(checker, &case.body); + } + } + _ => {} + } + } +} + +/// PLE0116 +pub fn continue_in_finally(checker: &mut Checker, body: &[Stmt]) { + traverse_body(checker, body); +} diff --git a/crates/ruff/src/rules/pylint/rules/mod.rs b/crates/ruff/src/rules/pylint/rules/mod.rs index bd942aa4a7109..8e55817cbd7b4 100644 --- a/crates/ruff/src/rules/pylint/rules/mod.rs +++ b/crates/ruff/src/rules/pylint/rules/mod.rs @@ -6,6 +6,7 @@ pub use collapsible_else_if::{collapsible_else_if, CollapsibleElseIf}; pub use compare_to_empty_string::{compare_to_empty_string, CompareToEmptyString}; pub use comparison_of_constant::{comparison_of_constant, ComparisonOfConstant}; pub use consider_using_sys_exit::{consider_using_sys_exit, ConsiderUsingSysExit}; +pub use continue_in_finally::{continue_in_finally, ContinueInFinally}; pub use global_statement::{global_statement, GlobalStatement}; pub use global_variable_not_assigned::GlobalVariableNotAssigned; pub use invalid_all_format::{invalid_all_format, InvalidAllFormat}; @@ -42,6 +43,7 @@ mod collapsible_else_if; mod compare_to_empty_string; mod comparison_of_constant; mod consider_using_sys_exit; +mod continue_in_finally; mod global_statement; mod global_variable_not_assigned; mod invalid_all_format; diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE0116_continue_in_finally.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE0116_continue_in_finally.py.snap new file mode 100644 index 0000000000000..4ca43e3980d22 --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE0116_continue_in_finally.py.snap @@ -0,0 +1,6 @@ +--- +source: crates/ruff/src/rules/pylint/mod.rs +expression: diagnostics +--- +[] + diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__continue_in_finally.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__continue_in_finally.snap new file mode 100644 index 0000000000000..37b29557b8a27 --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__continue_in_finally.snap @@ -0,0 +1,174 @@ +--- +source: crates/ruff/src/rules/pylint/mod.rs +expression: diagnostics +--- +- kind: + name: ContinueInFinally + body: "`continue` not supported inside `finally` clause" + suggestion: ~ + fixable: false + location: + row: 5 + column: 8 + end_location: + row: 5 + column: 16 + fix: ~ + parent: ~ +- kind: + name: ContinueInFinally + body: "`continue` not supported inside `finally` clause" + suggestion: ~ + fixable: false + location: + row: 16 + column: 12 + end_location: + row: 16 + column: 20 + fix: ~ + parent: ~ +- kind: + name: ContinueInFinally + body: "`continue` not supported inside `finally` clause" + suggestion: ~ + fixable: false + location: + row: 26 + column: 16 + end_location: + row: 26 + column: 24 + fix: ~ + parent: ~ +- kind: + name: ContinueInFinally + body: "`continue` not supported inside `finally` clause" + suggestion: ~ + fixable: false + location: + row: 33 + column: 12 + end_location: + row: 33 + column: 20 + fix: ~ + parent: ~ +- kind: + name: ContinueInFinally + body: "`continue` not supported inside `finally` clause" + suggestion: ~ + fixable: false + location: + row: 40 + column: 12 + end_location: + row: 40 + column: 20 + fix: ~ + parent: ~ +- kind: + name: ContinueInFinally + body: "`continue` not supported inside `finally` clause" + suggestion: ~ + fixable: false + location: + row: 41 + column: 8 + end_location: + row: 41 + column: 16 + fix: ~ + parent: ~ +- kind: + name: ContinueInFinally + body: "`continue` not supported inside `finally` clause" + suggestion: ~ + fixable: false + location: + row: 49 + column: 16 + end_location: + row: 49 + column: 24 + fix: ~ + parent: ~ +- kind: + name: ContinueInFinally + body: "`continue` not supported inside `finally` clause" + suggestion: ~ + fixable: false + location: + row: 56 + column: 8 + end_location: + row: 56 + column: 16 + fix: ~ + parent: ~ +- kind: + name: ContinueInFinally + body: "`continue` not supported inside `finally` clause" + suggestion: ~ + fixable: false + location: + row: 69 + column: 8 + end_location: + row: 69 + column: 16 + fix: ~ + parent: ~ +- kind: + name: ContinueInFinally + body: "`continue` not supported inside `finally` clause" + suggestion: ~ + fixable: false + location: + row: 74 + column: 12 + end_location: + row: 74 + column: 20 + fix: ~ + parent: ~ +- kind: + name: ContinueInFinally + body: "`continue` not supported inside `finally` clause" + suggestion: ~ + fixable: false + location: + row: 89 + column: 12 + end_location: + row: 89 + column: 20 + fix: ~ + parent: ~ +- kind: + name: ContinueInFinally + body: "`continue` not supported inside `finally` clause" + suggestion: ~ + fixable: false + location: + row: 91 + column: 12 + end_location: + row: 91 + column: 20 + fix: ~ + parent: ~ +- kind: + name: ContinueInFinally + body: "`continue` not supported inside `finally` clause" + suggestion: ~ + fixable: false + location: + row: 95 + column: 16 + end_location: + row: 95 + column: 24 + fix: ~ + parent: ~ + diff --git a/ruff.schema.json b/ruff.schema.json index eb617a6f594dd..ac985090be08f 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1832,6 +1832,7 @@ "PLE0100", "PLE0101", "PLE011", + "PLE0116", "PLE0117", "PLE0118", "PLE06", From 373a77e8c2ba61a0296e57b223bf59d3ac5097ce Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 16 Mar 2023 22:52:05 -0400 Subject: [PATCH 37/49] Avoid C1901 violations within subscripts (#3517) --- .../test/fixtures/pylint/compare_to_empty_string.py | 4 ++++ .../src/rules/pylint/rules/compare_to_empty_string.rs | 8 ++++++++ crates/ruff_python_ast/src/context.rs | 5 +++++ 3 files changed, 17 insertions(+) diff --git a/crates/ruff/resources/test/fixtures/pylint/compare_to_empty_string.py b/crates/ruff/resources/test/fixtures/pylint/compare_to_empty_string.py index 91b29262721b5..0fe9431420c00 100644 --- a/crates/ruff/resources/test/fixtures/pylint/compare_to_empty_string.py +++ b/crates/ruff/resources/test/fixtures/pylint/compare_to_empty_string.py @@ -17,3 +17,7 @@ def errors(): def ok(): if x and not y: print("x is not an empty string, but y is an empty string") + + +data.loc[data["a"] != ""] +data.loc[data["a"] != "", :] diff --git a/crates/ruff/src/rules/pylint/rules/compare_to_empty_string.rs b/crates/ruff/src/rules/pylint/rules/compare_to_empty_string.rs index 06e23b065f8e1..82d68c97382cb 100644 --- a/crates/ruff/src/rules/pylint/rules/compare_to_empty_string.rs +++ b/crates/ruff/src/rules/pylint/rules/compare_to_empty_string.rs @@ -74,6 +74,14 @@ pub fn compare_to_empty_string( ops: &[Cmpop], comparators: &[Expr], ) { + // Omit string comparison rules within subscripts. This is mostly commonly used within + // DataFrame and np.ndarray indexing. + for parent in checker.ctx.expr_ancestors() { + if matches!(parent.node, ExprKind::Subscript { .. }) { + return; + } + } + let mut first = true; for ((lhs, rhs), op) in std::iter::once(left) .chain(comparators.iter()) diff --git a/crates/ruff_python_ast/src/context.rs b/crates/ruff_python_ast/src/context.rs index bf00878a2c53b..341ac047b823a 100644 --- a/crates/ruff_python_ast/src/context.rs +++ b/crates/ruff_python_ast/src/context.rs @@ -251,6 +251,11 @@ impl<'a> Context<'a> { self.exprs.iter().rev().nth(2) } + /// Return an [`Iterator`] over the current `Expr` parents. + pub fn expr_ancestors(&self) -> impl Iterator> { + self.exprs.iter().rev().skip(1) + } + /// Return the `Stmt` that immediately follows the current `Stmt`, if any. pub fn current_sibling_stmt(&self) -> Option<&'a Stmt> { self.body.get(self.body_index + 1) From 33d2457909d476c61f041c25e1eee267b04c1607 Mon Sep 17 00:00:00 2001 From: Evan Rittenhouse Date: Thu, 16 Mar 2023 22:50:45 -0500 Subject: [PATCH 38/49] Prefer `itertools.pairwise()` over `zip()` for successive pairs (`RUF007`) (#3501) --- .../resources/test/fixtures/ruff/RUF007.py | 19 +++ crates/ruff/src/checkers/ast/mod.rs | 6 + crates/ruff/src/codes.rs | 1 + crates/ruff/src/registry.rs | 1 + crates/ruff/src/rules/ruff/mod.rs | 10 ++ crates/ruff/src/rules/ruff/rules/mod.rs | 12 +- .../rules/ruff/rules/pairwise_over_zipped.rs | 132 ++++++++++++++++++ ...uff__tests__ruff_pairwise_over_zipped.snap | 96 +++++++++++++ ruff.schema.json | 1 + 9 files changed, 273 insertions(+), 5 deletions(-) create mode 100644 crates/ruff/resources/test/fixtures/ruff/RUF007.py create mode 100644 crates/ruff/src/rules/ruff/rules/pairwise_over_zipped.rs create mode 100644 crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__ruff_pairwise_over_zipped.snap diff --git a/crates/ruff/resources/test/fixtures/ruff/RUF007.py b/crates/ruff/resources/test/fixtures/ruff/RUF007.py new file mode 100644 index 0000000000000..45f506b72903f --- /dev/null +++ b/crates/ruff/resources/test/fixtures/ruff/RUF007.py @@ -0,0 +1,19 @@ +input = [1, 2, 3] +otherInput = [2, 3, 4] + +# OK +zip(input, otherInput) # different inputs +zip(input, otherInput[1:]) # different inputs +zip(input, input[2:]) # not successive +zip(input[:-1], input[2:]) # not successive +list(zip(input, otherInput)) # nested call +zip(input, input[1::2]) # not successive + +# Errors +zip(input, input[1:]) +zip(input, input[1::1]) +zip(input[:-1], input[1:]) +zip(input[1:], input[2:]) +zip(input[1:-1], input[2:]) +list(zip(input, input[1:])) +list(zip(input[:-1], input[1:])) diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 9b6088b1a7e09..279387f783cca 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -2866,6 +2866,12 @@ where flake8_pytest_style::rules::fail_call(self, func, args, keywords); } + if self.settings.rules.enabled(Rule::PairwiseOverZipped) { + if self.settings.target_version >= PythonVersion::Py310 { + ruff::rules::pairwise_over_zipped(self, func, args); + } + } + // flake8-simplify if self .settings diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index d74df55579376..f4d2f5592ba45 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -661,6 +661,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option { (Ruff, "003") => Rule::AmbiguousUnicodeCharacterComment, (Ruff, "005") => Rule::UnpackInsteadOfConcatenatingToCollectionLiteral, (Ruff, "006") => Rule::AsyncioDanglingTask, + (Ruff, "007") => Rule::PairwiseOverZipped, (Ruff, "100") => Rule::UnusedNOQA, // flake8-django diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 5b448804aca28..04e5bfc147c3c 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -602,6 +602,7 @@ ruff_macros::register_rules!( rules::ruff::rules::UnpackInsteadOfConcatenatingToCollectionLiteral, rules::ruff::rules::AsyncioDanglingTask, rules::ruff::rules::UnusedNOQA, + rules::ruff::rules::PairwiseOverZipped, // flake8-django rules::flake8_django::rules::NullableModelStringField, rules::flake8_django::rules::LocalsInRenderFunction, diff --git a/crates/ruff/src/rules/ruff/mod.rs b/crates/ruff/src/rules/ruff/mod.rs index 4fa134da431d1..bfee0c2be6908 100644 --- a/crates/ruff/src/rules/ruff/mod.rs +++ b/crates/ruff/src/rules/ruff/mod.rs @@ -142,4 +142,14 @@ mod tests { assert_yaml_snapshot!(diagnostics); Ok(()) } + + #[test] + fn ruff_pairwise_over_zipped() -> Result<()> { + let diagnostics = test_path( + Path::new("ruff/RUF007.py"), + &settings::Settings::for_rules(vec![Rule::PairwiseOverZipped]), + )?; + assert_yaml_snapshot!(diagnostics); + Ok(()) + } } diff --git a/crates/ruff/src/rules/ruff/rules/mod.rs b/crates/ruff/src/rules/ruff/rules/mod.rs index 1cc2d35faec7e..0460ae4a7726b 100644 --- a/crates/ruff/src/rules/ruff/rules/mod.rs +++ b/crates/ruff/src/rules/ruff/rules/mod.rs @@ -1,19 +1,21 @@ +mod ambiguous_unicode_character; +mod asyncio_dangling_task; +mod pairwise_over_zipped; +mod unpack_instead_of_concatenating_to_collection_literal; +mod unused_noqa; + pub use ambiguous_unicode_character::{ ambiguous_unicode_character, AmbiguousUnicodeCharacterComment, AmbiguousUnicodeCharacterDocstring, AmbiguousUnicodeCharacterString, }; pub use asyncio_dangling_task::{asyncio_dangling_task, AsyncioDanglingTask}; +pub use pairwise_over_zipped::{pairwise_over_zipped, PairwiseOverZipped}; pub use unpack_instead_of_concatenating_to_collection_literal::{ unpack_instead_of_concatenating_to_collection_literal, UnpackInsteadOfConcatenatingToCollectionLiteral, }; pub use unused_noqa::{UnusedCodes, UnusedNOQA}; -mod ambiguous_unicode_character; -mod asyncio_dangling_task; -mod unpack_instead_of_concatenating_to_collection_literal; -mod unused_noqa; - #[derive(Clone, Copy)] pub enum Context { String, diff --git a/crates/ruff/src/rules/ruff/rules/pairwise_over_zipped.rs b/crates/ruff/src/rules/ruff/rules/pairwise_over_zipped.rs new file mode 100644 index 0000000000000..651fdf693dd17 --- /dev/null +++ b/crates/ruff/src/rules/ruff/rules/pairwise_over_zipped.rs @@ -0,0 +1,132 @@ +use num_traits::ToPrimitive; +use rustpython_parser::ast::{Constant, Expr, ExprKind, Unaryop}; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; + +use crate::checkers::ast::Checker; +use crate::Range; + +#[violation] +pub struct PairwiseOverZipped; + +impl Violation for PairwiseOverZipped { + #[derive_message_formats] + fn message(&self) -> String { + format!("Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs") + } +} + +#[derive(Debug)] +struct SliceInfo { + arg_name: String, + slice_start: Option, +} + +impl SliceInfo { + pub fn new(arg_name: String, slice_start: Option) -> Self { + Self { + arg_name, + slice_start, + } + } +} + +/// Return the argument name, lower bound, and upper bound for an expression, if it's a slice. +fn match_slice_info(expr: &Expr) -> Option { + let ExprKind::Subscript { value, slice, .. } = &expr.node else { + return None; + }; + + let ExprKind::Name { id: arg_id, .. } = &value.node else { + return None; + }; + + let ExprKind::Slice { lower, step, .. } = &slice.node else { + return None; + }; + + // Avoid false positives for slices with a step. + if let Some(step) = step { + if let Some(step) = to_bound(step) { + if step != 1 { + return None; + } + } else { + return None; + } + } + + Some(SliceInfo::new( + arg_id.to_string(), + lower.as_ref().and_then(|expr| to_bound(expr)), + )) +} + +fn to_bound(expr: &Expr) -> Option { + match &expr.node { + ExprKind::Constant { + value: Constant::Int(value), + .. + } => value.to_i64(), + ExprKind::UnaryOp { + op: Unaryop::USub | Unaryop::Invert, + operand, + } => { + if let ExprKind::Constant { + value: Constant::Int(value), + .. + } = &operand.node + { + value.to_i64().map(|v| -v) + } else { + None + } + } + _ => None, + } +} + +/// RUF007 +pub fn pairwise_over_zipped(checker: &mut Checker, func: &Expr, args: &[Expr]) { + let ExprKind::Name { id, .. } = &func.node else { + return; + }; + + if !(args.len() > 1 && id == "zip" && checker.ctx.is_builtin(id)) { + return; + }; + + // Allow the first argument to be a `Name` or `Subscript`. + let Some(first_arg_info) = ({ + if let ExprKind::Name { id, .. } = &args[0].node { + Some(SliceInfo::new(id.to_string(), None)) + } else { + match_slice_info(&args[0]) + } + }) else { + return; + }; + + // Require second argument to be a `Subscript`. + let ExprKind::Subscript { .. } = &args[1].node else { + return; + }; + let Some(second_arg_info) = match_slice_info(&args[1]) else { + return; + }; + + // Verify that the arguments match the same name. + if first_arg_info.arg_name != second_arg_info.arg_name { + return; + } + + // Verify that the arguments are successive. + if second_arg_info.slice_start.unwrap_or(0) - first_arg_info.slice_start.unwrap_or(0) != 1 { + return; + } + + checker + .diagnostics + .push(Diagnostic::new(PairwiseOverZipped, Range::from(func))); +} diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__ruff_pairwise_over_zipped.snap b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__ruff_pairwise_over_zipped.snap new file mode 100644 index 0000000000000..944d3bb391585 --- /dev/null +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__ruff_pairwise_over_zipped.snap @@ -0,0 +1,96 @@ +--- +source: crates/ruff/src/rules/ruff/mod.rs +expression: diagnostics +--- +- kind: + name: PairwiseOverZipped + body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs" + suggestion: ~ + fixable: false + location: + row: 13 + column: 0 + end_location: + row: 13 + column: 3 + fix: ~ + parent: ~ +- kind: + name: PairwiseOverZipped + body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs" + suggestion: ~ + fixable: false + location: + row: 14 + column: 0 + end_location: + row: 14 + column: 3 + fix: ~ + parent: ~ +- kind: + name: PairwiseOverZipped + body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs" + suggestion: ~ + fixable: false + location: + row: 15 + column: 0 + end_location: + row: 15 + column: 3 + fix: ~ + parent: ~ +- kind: + name: PairwiseOverZipped + body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs" + suggestion: ~ + fixable: false + location: + row: 16 + column: 0 + end_location: + row: 16 + column: 3 + fix: ~ + parent: ~ +- kind: + name: PairwiseOverZipped + body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs" + suggestion: ~ + fixable: false + location: + row: 17 + column: 0 + end_location: + row: 17 + column: 3 + fix: ~ + parent: ~ +- kind: + name: PairwiseOverZipped + body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs" + suggestion: ~ + fixable: false + location: + row: 18 + column: 5 + end_location: + row: 18 + column: 8 + fix: ~ + parent: ~ +- kind: + name: PairwiseOverZipped + body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs" + suggestion: ~ + fixable: false + location: + row: 19 + column: 5 + end_location: + row: 19 + column: 8 + fix: ~ + parent: ~ + diff --git a/ruff.schema.json b/ruff.schema.json index ac985090be08f..2d5f187c93d00 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2013,6 +2013,7 @@ "RUF003", "RUF005", "RUF006", + "RUF007", "RUF1", "RUF10", "RUF100", From 92179e6369f9b46e57242c56d60bb5d5ee05d8e7 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 17 Mar 2023 17:12:27 +0100 Subject: [PATCH 39/49] Scope and Binding IDs (#3572) --- crates/ruff/src/checkers/ast/deferred.rs | 3 +- crates/ruff/src/checkers/ast/mod.rs | 296 +++++++----------- .../rules/cached_instance_method.rs | 2 +- .../rules/unused_loop_control_variable.rs | 2 +- .../flake8_simplify/rules/ast_unary_op.rs | 4 +- .../src/rules/flake8_type_checking/helpers.rs | 4 +- .../rules/flake8_unused_arguments/rules.rs | 17 +- .../pycodestyle/rules/lambda_assignment.rs | 2 +- .../pyflakes/rules/return_outside_function.rs | 2 +- .../rules/pyflakes/rules/undefined_local.rs | 5 +- .../rules/pyflakes/rules/unused_annotation.rs | 3 +- .../rules/pyflakes/rules/unused_variable.rs | 4 +- .../pyflakes/rules/yield_outside_function.rs | 2 +- crates/ruff/src/rules/pylint/helpers.rs | 4 +- .../rules/pylint/rules/await_outside_async.rs | 2 +- .../pylint/rules/consider_using_sys_exit.rs | 4 +- .../rules/pylint/rules/global_statement.rs | 2 +- .../rules/used_prior_global_declaration.rs | 2 +- .../rules/super_call_with_parameters.rs | 2 +- .../rules/useless_object_inheritance.rs | 5 +- crates/ruff_python_ast/src/context.rs | 199 ++++++++++-- crates/ruff_python_ast/src/types.rs | 88 +++++- 22 files changed, 404 insertions(+), 250 deletions(-) diff --git a/crates/ruff/src/checkers/ast/deferred.rs b/crates/ruff/src/checkers/ast/deferred.rs index 5e643499ed974..ccef124bd3282 100644 --- a/crates/ruff/src/checkers/ast/deferred.rs +++ b/crates/ruff/src/checkers/ast/deferred.rs @@ -1,3 +1,4 @@ +use ruff_python_ast::context::ScopeStack; use rustpython_parser::ast::{Expr, Stmt}; use ruff_python_ast::types::Range; @@ -7,7 +8,7 @@ use ruff_python_ast::visibility::{Visibility, VisibleScope}; use crate::checkers::ast::AnnotationContext; use crate::docstrings::definition::Definition; -type Context<'a> = (Vec, Vec>); +type Context<'a> = (ScopeStack, Vec>); /// A collection of AST nodes that are deferred for later analysis. /// Used to, e.g., store functions, whose bodies shouldn't be analyzed until all diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 279387f783cca..80830531c9e1d 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -14,7 +14,7 @@ use rustpython_parser::ast::{ }; use ruff_diagnostics::Diagnostic; -use ruff_python_ast::context::Context; +use ruff_python_ast::context::{Context, ScopeStack}; use ruff_python_ast::helpers::{ binding_range, extract_handled_exceptions, to_module_path, Exceptions, }; @@ -22,8 +22,8 @@ use ruff_python_ast::operations::{extract_all_names, AllNamesFlags}; use ruff_python_ast::relocate::relocate_expr; use ruff_python_ast::source_code::{Indexer, Locator, Stylist}; use ruff_python_ast::types::{ - Binding, BindingKind, ClassDef, ExecutionContext, FunctionDef, Lambda, Node, Range, - RefEquality, Scope, ScopeKind, + Binding, BindingId, BindingKind, ClassDef, ExecutionContext, FunctionDef, Lambda, Node, Range, + RefEquality, Scope, ScopeId, ScopeKind, }; use ruff_python_ast::typing::{match_annotated_subscript, Callable, SubscriptKind}; use ruff_python_ast::visitor::{walk_excepthandler, walk_pattern, Visitor}; @@ -53,8 +53,6 @@ use crate::{autofix, docstrings, noqa}; mod deferred; -const GLOBAL_SCOPE_INDEX: usize = 0; - type AnnotationContext = (bool, bool); #[allow(clippy::struct_excessive_bools)] @@ -193,16 +191,15 @@ where // Pre-visit. match &stmt.node { StmtKind::Global { names } => { - let scope_index = *self.ctx.scope_stack.last().expect("No current scope found"); + let scope_index = self.ctx.scope_id(); let ranges: Vec = helpers::find_names(stmt, self.locator).collect(); - if scope_index != GLOBAL_SCOPE_INDEX { + if !scope_index.is_global() { // Add the binding to the current scope. let context = self.ctx.execution_context(); let scope = &mut self.ctx.scopes[scope_index]; let usage = Some((scope.id, Range::from(stmt))); for (name, range) in names.iter().zip(ranges.iter()) { - let index = self.ctx.bindings.len(); - self.ctx.bindings.push(Binding { + let id = self.ctx.bindings.push(Binding { kind: BindingKind::Global, runtime_usage: None, synthetic_usage: usage, @@ -211,7 +208,7 @@ where source: Some(RefEquality(stmt)), context, }); - scope.bindings.insert(name, index); + scope.bindings.insert(name, id); } } @@ -223,16 +220,15 @@ where } } StmtKind::Nonlocal { names } => { - let scope_index = *self.ctx.scope_stack.last().expect("No current scope found"); + let scope_index = self.ctx.scope_id(); let ranges: Vec = helpers::find_names(stmt, self.locator).collect(); - if scope_index != GLOBAL_SCOPE_INDEX { + if !scope_index.is_global() { let context = self.ctx.execution_context(); let scope = &mut self.ctx.scopes[scope_index]; let usage = Some((scope.id, Range::from(stmt))); for (name, range) in names.iter().zip(ranges.iter()) { // Add a binding to the current scope. - let index = self.ctx.bindings.len(); - self.ctx.bindings.push(Binding { + let id = self.ctx.bindings.push(Binding { kind: BindingKind::Nonlocal, runtime_usage: None, synthetic_usage: usage, @@ -241,14 +237,18 @@ where source: Some(RefEquality(stmt)), context, }); - scope.bindings.insert(name, index); + scope.bindings.insert(name, id); } // Mark the binding in the defining scopes as used too. (Skip the global scope // and the current scope.) for (name, range) in names.iter().zip(ranges.iter()) { let mut exists = false; - for index in self.ctx.scope_stack.iter().skip(1).rev().skip(1) { + let mut scopes_iter = self.ctx.scope_stack.iter(); + // Skip the global scope + scopes_iter.next_back(); + + for index in scopes_iter.skip(1) { if let Some(index) = self.ctx.scopes[*index].bindings.get(&name.as_str()) { @@ -354,7 +354,7 @@ where if let Some(diagnostic) = pep8_naming::rules::invalid_first_argument_name_for_class_method( self, - self.ctx.current_scope(), + self.ctx.scope(), name, decorator_list, args, @@ -372,7 +372,7 @@ where if let Some(diagnostic) = pep8_naming::rules::invalid_first_argument_name_for_method( self, - self.ctx.current_scope(), + self.ctx.scope(), name, decorator_list, args, @@ -393,7 +393,7 @@ where if self.settings.rules.enabled(Rule::DunderFunctionName) { if let Some(diagnostic) = pep8_naming::rules::dunder_function_name( - self.ctx.current_scope(), + self.ctx.scope(), stmt, name, self.locator, @@ -592,7 +592,7 @@ where // See: https://docs.python.org/3/reference/simple_stmts.html#annotated-assignment-statements let runtime_annotation = !self.ctx.annotations_future_enabled && matches!( - self.ctx.current_scope().kind, + self.ctx.scope().kind, ScopeKind::Class(..) | ScopeKind::Module ); @@ -849,15 +849,7 @@ where kind: BindingKind::FutureImportation, runtime_usage: None, // Always mark `__future__` imports as used. - synthetic_usage: Some(( - self.ctx.scopes[*(self - .ctx - .scope_stack - .last() - .expect("No current scope found"))] - .id, - Range::from(alias), - )), + synthetic_usage: Some((self.ctx.scope_id(), Range::from(alias))), typing_usage: None, range: Range::from(alias), source: Some(self.ctx.current_stmt().clone()), @@ -884,15 +876,7 @@ where kind: BindingKind::SubmoduleImportation(name, full_name), runtime_usage: None, synthetic_usage: if is_handled { - Some(( - self.ctx.scopes[*(self - .ctx - .scope_stack - .last() - .expect("No current scope found"))] - .id, - Range::from(alias), - )) + Some((self.ctx.scope_id(), Range::from(alias))) } else { None }, @@ -922,15 +906,7 @@ where kind: BindingKind::Importation(name, full_name), runtime_usage: None, synthetic_usage: if is_handled || is_explicit_reexport { - Some(( - self.ctx.scopes[*(self - .ctx - .scope_stack - .last() - .expect("No current scope found"))] - .id, - Range::from(alias), - )) + Some((self.ctx.scope_id(), Range::from(alias))) } else { None }, @@ -1191,15 +1167,7 @@ where kind: BindingKind::FutureImportation, runtime_usage: None, // Always mark `__future__` imports as used. - synthetic_usage: Some(( - self.ctx.scopes[*(self - .ctx - .scope_stack - .last() - .expect("No current scope found"))] - .id, - Range::from(alias), - )), + synthetic_usage: Some((self.ctx.scope_id(), Range::from(alias))), typing_usage: None, range: Range::from(alias), source: Some(self.ctx.current_stmt().clone()), @@ -1230,15 +1198,7 @@ where kind: BindingKind::StarImportation(*level, module.clone()), runtime_usage: None, synthetic_usage: if is_handled { - Some(( - self.ctx.scopes[*(self - .ctx - .scope_stack - .last() - .expect("No current scope found"))] - .id, - Range::from(alias), - )) + Some((self.ctx.scope_id(), Range::from(alias))) } else { None }, @@ -1250,8 +1210,7 @@ where ); if self.settings.rules.enabled(Rule::ImportStarNotPermitted) { - let scope = &self.ctx.scopes - [*(self.ctx.scope_stack.last().expect("No current scope found"))]; + let scope = self.ctx.scope(); if !matches!(scope.kind, ScopeKind::Module) { self.diagnostics.push(Diagnostic::new( pyflakes::rules::ImportStarNotPermitted { @@ -1277,8 +1236,7 @@ where )); } - let scope = &mut self.ctx.scopes - [*(self.ctx.scope_stack.last().expect("No current scope found"))]; + let scope = self.ctx.scope_mut(); scope.import_starred = true; } else { if let Some(asname) = &alias.node.asname { @@ -1308,15 +1266,7 @@ where kind: BindingKind::FromImportation(name, full_name), runtime_usage: None, synthetic_usage: if is_handled || is_explicit_reexport { - Some(( - self.ctx.scopes[*(self - .ctx - .scope_stack - .last() - .expect("No current scope found"))] - .id, - Range::from(alias), - )) + Some((self.ctx.scope_id(), Range::from(alias))) } else { None }, @@ -1921,13 +1871,14 @@ where // If any global bindings don't already exist in the global scope, add it. let globals = operations::extract_globals(body); for (name, stmt) in operations::extract_globals(body) { - if self.ctx.scopes[GLOBAL_SCOPE_INDEX] + if self + .ctx + .global_scope() .bindings .get(name) .map_or(true, |index| self.ctx.bindings[*index].kind.is_annotation()) { - let index = self.ctx.bindings.len(); - self.ctx.bindings.push(Binding { + let id = self.ctx.bindings.push(Binding { kind: BindingKind::Assignment, runtime_usage: None, synthetic_usage: None, @@ -1936,21 +1887,18 @@ where source: Some(RefEquality(stmt)), context: self.ctx.execution_context(), }); - self.ctx.scopes[GLOBAL_SCOPE_INDEX] - .bindings - .insert(name, index); + self.ctx.global_scope_mut().bindings.insert(name, id); } } - self.ctx - .push_scope(Scope::new(ScopeKind::Function(FunctionDef { - name, - body, - args, - decorator_list, - async_: matches!(stmt.node, StmtKind::AsyncFunctionDef { .. }), - globals, - }))); + self.ctx.push_scope(ScopeKind::Function(FunctionDef { + name, + body, + args, + decorator_list, + async_: matches!(stmt.node, StmtKind::AsyncFunctionDef { .. }), + globals, + })); self.deferred.functions.push(( stmt, @@ -1986,13 +1934,14 @@ where // If any global bindings don't already exist in the global scope, add it. let globals = operations::extract_globals(body); for (name, stmt) in &globals { - if self.ctx.scopes[GLOBAL_SCOPE_INDEX] + if self + .ctx + .global_scope() .bindings .get(name) .map_or(true, |index| self.ctx.bindings[*index].kind.is_annotation()) { - let index = self.ctx.bindings.len(); - self.ctx.bindings.push(Binding { + let id = self.ctx.bindings.push(Binding { kind: BindingKind::Assignment, runtime_usage: None, synthetic_usage: None, @@ -2001,19 +1950,17 @@ where source: Some(RefEquality(stmt)), context: self.ctx.execution_context(), }); - self.ctx.scopes[GLOBAL_SCOPE_INDEX] - .bindings - .insert(name, index); + self.ctx.global_scope_mut().bindings.insert(name, id); } } - self.ctx.push_scope(Scope::new(ScopeKind::Class(ClassDef { + self.ctx.push_scope(ScopeKind::Class(ClassDef { name, bases, keywords, decorator_list, globals, - }))); + })); self.visit_body(body); } @@ -2074,7 +2021,7 @@ where // available at runtime. // See: https://docs.python.org/3/reference/simple_stmts.html#annotated-assignment-statements let runtime_annotation = if self.ctx.annotations_future_enabled { - if matches!(self.ctx.current_scope().kind, ScopeKind::Class(..)) { + if matches!(self.ctx.scope().kind, ScopeKind::Class(..)) { let baseclasses = &self .settings .flake8_type_checking @@ -2093,7 +2040,7 @@ where } } else { matches!( - self.ctx.current_scope().kind, + self.ctx.scope().kind, ScopeKind::Class(..) | ScopeKind::Module ) }; @@ -2718,8 +2665,7 @@ where } if let ExprKind::Name { id, ctx } = &func.node { if id == "locals" && matches!(ctx, ExprContext::Load) { - let scope = &mut self.ctx.scopes - [*(self.ctx.scope_stack.last().expect("No current scope found"))]; + let scope = self.ctx.scope_mut(); scope.uses_locals = true; } } @@ -3365,7 +3311,7 @@ where self.visit_expr(expr); } self.ctx - .push_scope(Scope::new(ScopeKind::Lambda(Lambda { args, body }))); + .push_scope(ScopeKind::Lambda(Lambda { args, body })); } ExprKind::IfExp { test, body, orelse } => { if self.settings.rules.enabled(Rule::IfExprWithTrueFalse) { @@ -3391,13 +3337,13 @@ where if self.settings.rules.enabled(Rule::FunctionUsesLoopVariable) { flake8_bugbear::rules::function_uses_loop_variable(self, &Node::Expr(expr)); } - self.ctx.push_scope(Scope::new(ScopeKind::Generator)); + self.ctx.push_scope(ScopeKind::Generator); } ExprKind::GeneratorExp { .. } | ExprKind::DictComp { .. } => { if self.settings.rules.enabled(Rule::FunctionUsesLoopVariable) { flake8_bugbear::rules::function_uses_loop_variable(self, &Node::Expr(expr)); } - self.ctx.push_scope(Scope::new(ScopeKind::Generator)); + self.ctx.push_scope(ScopeKind::Generator); } ExprKind::BoolOp { op, values } => { if self.settings.rules.enabled(Rule::ConsiderMergingIsinstance) { @@ -3767,12 +3713,7 @@ where let name_range = helpers::excepthandler_name_range(excepthandler, self.locator).unwrap(); - if self - .ctx - .current_scope() - .bindings - .contains_key(&name.as_str()) - { + if self.ctx.scope().bindings.contains_key(&name.as_str()) { self.handle_node_store( name, &Expr::new( @@ -3786,12 +3727,7 @@ where ); } - let definition = self - .ctx - .current_scope() - .bindings - .get(&name.as_str()) - .copied(); + let definition = self.ctx.scope().bindings.get(&name.as_str()).copied(); self.handle_node_store( name, &Expr::new( @@ -3807,8 +3743,7 @@ where walk_excepthandler(self, excepthandler); if let Some(index) = { - let scope = &mut self.ctx.scopes - [*(self.ctx.scope_stack.last().expect("No current scope found"))]; + let scope = self.ctx.scope_mut(); &scope.bindings.remove(&name.as_str()) } { if !self.ctx.bindings[*index].used() { @@ -3842,8 +3777,7 @@ where } if let Some(index) = definition { - let scope = &mut self.ctx.scopes - [*(self.ctx.scope_stack.last().expect("No current scope found"))]; + let scope = self.ctx.scope_mut(); scope.bindings.insert(name, index); } } @@ -3998,18 +3932,16 @@ impl<'a> Checker<'a> { where 'b: 'a, { - let binding_index = self.ctx.bindings.len(); - + let binding_id = self.ctx.bindings.next_id(); if let Some((stack_index, scope_index)) = self .ctx .scope_stack .iter() - .rev() .enumerate() .find(|(_, scope_index)| self.ctx.scopes[**scope_index].bindings.contains_key(&name)) { - let existing_binding_index = self.ctx.scopes[*scope_index].bindings.get(&name).unwrap(); - let existing = &self.ctx.bindings[*existing_binding_index]; + let existing_binding_index = self.ctx.scopes[*scope_index].bindings[&name]; + let existing = &self.ctx.bindings[existing_binding_index]; let in_current_scope = stack_index == 0; if !existing.kind.is_builtin() && existing.source.as_ref().map_or(true, |left| { @@ -4072,14 +4004,14 @@ impl<'a> Checker<'a> { } else if existing_is_import && binding.redefines(existing) { self.ctx .redefinitions - .entry(*existing_binding_index) + .entry(existing_binding_index) .or_insert_with(Vec::new) - .push(binding_index); + .push(binding_id); } } } - let scope = self.ctx.current_scope(); + let scope = self.ctx.scope(); let binding = if let Some(index) = scope.bindings.get(&name) { let existing = &self.ctx.bindings[*index]; match &existing.kind { @@ -4111,10 +4043,9 @@ impl<'a> Checker<'a> { // Don't treat annotations as assignments if there is an existing value // in scope. - let scope = - &mut self.ctx.scopes[*(self.ctx.scope_stack.last().expect("No current scope found"))]; + let scope = self.ctx.scope_mut(); if !(binding.kind.is_annotation() && scope.bindings.contains_key(name)) { - if let Some(rebound_index) = scope.bindings.insert(name, binding_index) { + if let Some(rebound_index) = scope.bindings.insert(name, binding_id) { scope .rebounds .entry(name) @@ -4128,7 +4059,7 @@ impl<'a> Checker<'a> { fn bind_builtins(&mut self) { let scope = - &mut self.ctx.scopes[*(self.ctx.scope_stack.last().expect("No current scope found"))]; + &mut self.ctx.scopes[self.ctx.scope_stack.top().expect("No current scope found")]; for builtin in BUILTINS .iter() @@ -4136,17 +4067,16 @@ impl<'a> Checker<'a> { .copied() .chain(self.settings.builtins.iter().map(String::as_str)) { - let index = self.ctx.bindings.len(); - self.ctx.bindings.push(Binding { + let id = self.ctx.bindings.push(Binding { kind: BindingKind::Builtin, range: Range::default(), runtime_usage: None, - synthetic_usage: Some((0, Range::default())), + synthetic_usage: Some((ScopeId::global(), Range::default())), typing_usage: None, source: None, context: ExecutionContext::Runtime, }); - scope.bindings.insert(builtin, index); + scope.bindings.insert(builtin, id); } } @@ -4154,13 +4084,13 @@ impl<'a> Checker<'a> { let ExprKind::Name { id, .. } = &expr.node else { return; }; - let scope_id = self.ctx.current_scope().id; + let scope_id = self.ctx.scope_id(); let mut first_iter = true; let mut in_generator = false; let mut import_starred = false; - for scope_index in self.ctx.scope_stack.iter().rev() { + for scope_index in self.ctx.scope_stack.iter() { let scope = &self.ctx.scopes[*scope_index]; if matches!(scope.kind, ScopeKind::Class(_)) { @@ -4241,7 +4171,7 @@ impl<'a> Checker<'a> { if import_starred { if self.settings.rules.enabled(Rule::ImportStarUsage) { let mut from_list = vec![]; - for scope_index in self.ctx.scope_stack.iter().rev() { + for scope_index in self.ctx.scope_stack.iter() { let scope = &self.ctx.scopes[*scope_index]; for binding in scope .bindings @@ -4277,7 +4207,7 @@ impl<'a> Checker<'a> { // Allow "__module__" and "__qualname__" in class scopes. if (id == "__module__" || id == "__qualname__") - && matches!(self.ctx.current_scope().kind, ScopeKind::Class(..)) + && matches!(self.ctx.scope().kind, ScopeKind::Class(..)) { return; } @@ -4310,6 +4240,7 @@ impl<'a> Checker<'a> { .ctx .scope_stack .iter() + .rev() .map(|index| &self.ctx.scopes[*index]) .collect(); if let Some(diagnostic) = @@ -4324,11 +4255,11 @@ impl<'a> Checker<'a> { .rules .enabled(Rule::NonLowercaseVariableInFunction) { - if matches!(self.ctx.current_scope().kind, ScopeKind::Function(..)) { + if matches!(self.ctx.scope().kind, ScopeKind::Function(..)) { // Ignore globals. if !self .ctx - .current_scope() + .scope() .bindings .get(id) .map_or(false, |index| self.ctx.bindings[*index].kind.is_global()) @@ -4343,7 +4274,7 @@ impl<'a> Checker<'a> { .rules .enabled(Rule::MixedCaseVariableInClassScope) { - if matches!(self.ctx.current_scope().kind, ScopeKind::Class(..)) { + if matches!(self.ctx.scope().kind, ScopeKind::Class(..)) { pep8_naming::rules::mixed_case_variable_in_class_scope(self, expr, parent, id); } } @@ -4353,7 +4284,7 @@ impl<'a> Checker<'a> { .rules .enabled(Rule::MixedCaseVariableInGlobalScope) { - if matches!(self.ctx.current_scope().kind, ScopeKind::Module) { + if matches!(self.ctx.scope().kind, ScopeKind::Module) { pep8_naming::rules::mixed_case_variable_in_global_scope(self, expr, parent, id); } } @@ -4410,7 +4341,7 @@ impl<'a> Checker<'a> { return; } - let current = self.ctx.current_scope(); + let current = self.ctx.scope(); if id == "__all__" && matches!(current.kind, ScopeKind::Module) && matches!( @@ -4501,8 +4432,7 @@ impl<'a> Checker<'a> { return; } - let scope = - &mut self.ctx.scopes[*(self.ctx.scope_stack.last().expect("No current scope found"))]; + let scope = self.ctx.scope_mut(); if scope.bindings.remove(&id.as_str()).is_some() { return; } @@ -4646,8 +4576,9 @@ impl<'a> Checker<'a> { fn check_deferred_assignments(&mut self) { self.deferred.assignments.reverse(); while let Some((scopes, ..)) = self.deferred.assignments.pop() { - let scope_index = scopes[scopes.len() - 1]; - let parent_scope_index = scopes[scopes.len() - 2]; + let mut scopes_iter = scopes.iter(); + let scope_index = *scopes_iter.next().unwrap(); + let parent_scope_index = *scopes_iter.next().unwrap(); // pyflakes if self.settings.rules.enabled(Rule::UnusedVariable) { @@ -4728,28 +4659,33 @@ impl<'a> Checker<'a> { } // Mark anything referenced in `__all__` as used. - let global_scope = &self.ctx.scopes[GLOBAL_SCOPE_INDEX]; - let all_names: Option<(&Vec, Range)> = global_scope - .bindings - .get("__all__") - .map(|index| &self.ctx.bindings[*index]) - .and_then(|binding| match &binding.kind { - BindingKind::Export(names) => Some((names, binding.range)), - _ => None, - }); - let all_bindings: Option<(Vec, Range)> = all_names.map(|(names, range)| { - ( - names - .iter() - .filter_map(|name| global_scope.bindings.get(name.as_str()).copied()) - .collect(), - range, - ) - }); + + let all_bindings: Option<(Vec, Range)> = { + let global_scope = self.ctx.global_scope(); + let all_names: Option<(&Vec, Range)> = global_scope + .bindings + .get("__all__") + .map(|index| &self.ctx.bindings[*index]) + .and_then(|binding| match &binding.kind { + BindingKind::Export(names) => Some((names, binding.range)), + _ => None, + }); + + all_names.map(|(names, range)| { + ( + names + .iter() + .filter_map(|name| global_scope.bindings.get(name.as_str()).copied()) + .collect(), + range, + ) + }) + }; + if let Some((bindings, range)) = all_bindings { for index in bindings { self.ctx.bindings[index].mark_used( - GLOBAL_SCOPE_INDEX, + ScopeId::global(), range, ExecutionContext::Runtime, ); @@ -4757,7 +4693,9 @@ impl<'a> Checker<'a> { } // Extract `__all__` names from the global scope. - let all_names: Option<(Vec<&str>, Range)> = global_scope + let all_names: Option<(Vec<&str>, Range)> = self + .ctx + .global_scope() .bindings .get("__all__") .map(|index| &self.ctx.bindings[*index]) @@ -4786,7 +4724,7 @@ impl<'a> Checker<'a> { .filter(|binding| { flake8_type_checking::helpers::is_valid_runtime_import(binding) }) - .collect::>() + .collect() }) .collect::>() } @@ -4799,7 +4737,7 @@ impl<'a> Checker<'a> { let scope = &self.ctx.scopes[*index]; // F822 - if *index == GLOBAL_SCOPE_INDEX { + if index.is_global() { if self.settings.rules.enabled(Rule::UndefinedExport) { if let Some((names, range)) = &all_names { diagnostics.extend(pyflakes::rules::undefined_export( @@ -4915,8 +4853,9 @@ impl<'a> Checker<'a> { } else { stack .iter() + .rev() .chain(iter::once(index)) - .flat_map(|index| runtime_imports[*index].iter()) + .flat_map(|index| runtime_imports[usize::from(*index)].iter()) .copied() .collect() }; @@ -5356,7 +5295,7 @@ impl<'a> Checker<'a> { } fn check_builtin_shadowing(&mut self, name: &str, located: &Located, is_attribute: bool) { - if is_attribute && matches!(self.ctx.current_scope().kind, ScopeKind::Class(_)) { + if is_attribute && matches!(self.ctx.scope().kind, ScopeKind::Class(_)) { if self.settings.rules.enabled(Rule::BuiltinAttributeShadowing) { if let Some(diagnostic) = flake8_builtins::rules::builtin_shadowing( name, @@ -5420,7 +5359,6 @@ pub fn check_ast( stylist, indexer, ); - checker.ctx.push_scope(Scope::new(ScopeKind::Module)); checker.bind_builtins(); // Check for module docstring. @@ -5445,7 +5383,7 @@ pub fn check_ast( checker.check_definitions(); // Reset the scope to module-level, and check all consumed scopes. - checker.ctx.scope_stack = vec![GLOBAL_SCOPE_INDEX]; + checker.ctx.scope_stack = ScopeStack::default(); checker.ctx.pop_scope(); checker.check_dead_scopes(); diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/cached_instance_method.rs b/crates/ruff/src/rules/flake8_bugbear/rules/cached_instance_method.rs index bc0f5d40efa21..6aea88d051ebc 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/cached_instance_method.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/cached_instance_method.rs @@ -30,7 +30,7 @@ fn is_cache_func(checker: &Checker, expr: &Expr) -> bool { /// B019 pub fn cached_instance_method(checker: &mut Checker, decorator_list: &[Expr]) { - if !matches!(checker.ctx.current_scope().kind, ScopeKind::Class(_)) { + if !matches!(checker.ctx.scope().kind, ScopeKind::Class(_)) { return; } for decorator in decorator_list { diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs b/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs index 556b542de0c77..2f83bbc2fb12a 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs @@ -163,7 +163,7 @@ pub fn unused_loop_control_variable( if let Some(rename) = rename { if certainty.into() && checker.patch(diagnostic.kind.rule()) { // Find the `BindingKind::LoopVar` corresponding to the name. - let scope = checker.ctx.current_scope(); + let scope = checker.ctx.scope(); let binding = scope .bindings .get(name) diff --git a/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs b/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs index a512777929f9d..b6c9989ed932a 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs @@ -93,7 +93,7 @@ pub fn negation_with_equal_op(checker: &mut Checker, expr: &Expr, op: &Unaryop, } // Avoid flagging issues in dunder implementations. - if let ScopeKind::Function(def) = &checker.ctx.current_scope().kind { + if let ScopeKind::Function(def) = &checker.ctx.scope().kind { if DUNDER_METHODS.contains(&def.name) { return; } @@ -144,7 +144,7 @@ pub fn negation_with_not_equal_op( } // Avoid flagging issues in dunder implementations. - if let ScopeKind::Function(def) = &checker.ctx.current_scope().kind { + if let ScopeKind::Function(def) = &checker.ctx.scope().kind { if DUNDER_METHODS.contains(&def.name) { return; } diff --git a/crates/ruff/src/rules/flake8_type_checking/helpers.rs b/crates/ruff/src/rules/flake8_type_checking/helpers.rs index f24c6a8214cf9..6b520fee7c769 100644 --- a/crates/ruff/src/rules/flake8_type_checking/helpers.rs +++ b/crates/ruff/src/rules/flake8_type_checking/helpers.rs @@ -71,7 +71,7 @@ pub fn runtime_evaluated( } fn runtime_evaluated_base_class(context: &Context, base_classes: &[String]) -> bool { - if let ScopeKind::Class(class_def) = &context.current_scope().kind { + if let ScopeKind::Class(class_def) = &context.scope().kind { for base in class_def.bases.iter() { if let Some(call_path) = context.resolve_call_path(base) { if base_classes @@ -87,7 +87,7 @@ fn runtime_evaluated_base_class(context: &Context, base_classes: &[String]) -> b } fn runtime_evaluated_decorators(context: &Context, decorators: &[String]) -> bool { - if let ScopeKind::Class(class_def) = &context.current_scope().kind { + if let ScopeKind::Class(class_def) = &context.scope().kind { for decorator in class_def.decorator_list.iter() { if let Some(call_path) = context.resolve_call_path(map_callable(decorator)) { if decorators diff --git a/crates/ruff/src/rules/flake8_unused_arguments/rules.rs b/crates/ruff/src/rules/flake8_unused_arguments/rules.rs index 54cb212f758a8..c4f94eacfd045 100644 --- a/crates/ruff/src/rules/flake8_unused_arguments/rules.rs +++ b/crates/ruff/src/rules/flake8_unused_arguments/rules.rs @@ -6,9 +6,10 @@ use rustpython_parser::ast::{Arg, Arguments}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::context::Bindings; use ruff_python_ast::function_type; use ruff_python_ast::function_type::FunctionType; -use ruff_python_ast::types::{Binding, FunctionDef, Lambda, Scope, ScopeKind}; +use ruff_python_ast::types::{BindingId, FunctionDef, Lambda, Scope, ScopeKind}; use ruff_python_ast::visibility; use crate::checkers::ast::Checker; @@ -85,8 +86,8 @@ impl Violation for UnusedLambdaArgument { fn function( argumentable: &Argumentable, args: &Arguments, - values: &FxHashMap<&str, usize>, - bindings: &[Binding], + values: &FxHashMap<&str, BindingId>, + bindings: &Bindings, dummy_variable_rgx: &Regex, ignore_variadic_names: bool, ) -> Vec { @@ -112,8 +113,8 @@ fn function( fn method( argumentable: &Argumentable, args: &Arguments, - values: &FxHashMap<&str, usize>, - bindings: &[Binding], + values: &FxHashMap<&str, BindingId>, + bindings: &Bindings, dummy_variable_rgx: &Regex, ignore_variadic_names: bool, ) -> Vec { @@ -139,8 +140,8 @@ fn method( fn call<'a>( argumentable: &Argumentable, args: impl Iterator, - values: &FxHashMap<&str, usize>, - bindings: &[Binding], + values: &FxHashMap<&str, BindingId>, + bindings: &Bindings, dummy_variable_rgx: &Regex, ) -> Vec { let mut diagnostics: Vec = vec![]; @@ -168,7 +169,7 @@ pub fn unused_arguments( checker: &Checker, parent: &Scope, scope: &Scope, - bindings: &[Binding], + bindings: &Bindings, ) -> Vec { match &scope.kind { ScopeKind::Function(FunctionDef { diff --git a/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs b/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs index 021b80467edd0..10cda5f65c9e7 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs @@ -65,7 +65,7 @@ pub fn lambda_assignment(checker: &mut Checker, target: &Expr, value: &Expr, stm // package like dataclasses, which wouldn't consider the // rewritten function definition to be equivalent. // See https://github.com/charliermarsh/ruff/issues/3046 - let fixable = !matches!(checker.ctx.current_scope().kind, ScopeKind::Class(_)); + let fixable = !matches!(checker.ctx.scope().kind, ScopeKind::Class(_)); let mut diagnostic = Diagnostic::new( LambdaAssignment { diff --git a/crates/ruff/src/rules/pyflakes/rules/return_outside_function.rs b/crates/ruff/src/rules/pyflakes/rules/return_outside_function.rs index 0224455f8ccf4..2ec768426a9f4 100644 --- a/crates/ruff/src/rules/pyflakes/rules/return_outside_function.rs +++ b/crates/ruff/src/rules/pyflakes/rules/return_outside_function.rs @@ -17,7 +17,7 @@ impl Violation for ReturnOutsideFunction { } pub fn return_outside_function(checker: &mut Checker, stmt: &Stmt) { - if let Some(&index) = checker.ctx.scope_stack.last() { + if let Some(index) = checker.ctx.scope_stack.top() { if matches!( checker.ctx.scopes[index].kind, ScopeKind::Class(_) | ScopeKind::Module diff --git a/crates/ruff/src/rules/pyflakes/rules/undefined_local.rs b/crates/ruff/src/rules/pyflakes/rules/undefined_local.rs index 02b2f8e2e6800..e63af15388228 100644 --- a/crates/ruff/src/rules/pyflakes/rules/undefined_local.rs +++ b/crates/ruff/src/rules/pyflakes/rules/undefined_local.rs @@ -2,7 +2,8 @@ use std::string::ToString; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::{Binding, Scope, ScopeKind}; +use ruff_python_ast::context::Bindings; +use ruff_python_ast::types::{Scope, ScopeKind}; #[violation] pub struct UndefinedLocal { @@ -18,7 +19,7 @@ impl Violation for UndefinedLocal { } /// F821 -pub fn undefined_local(name: &str, scopes: &[&Scope], bindings: &[Binding]) -> Option { +pub fn undefined_local(name: &str, scopes: &[&Scope], bindings: &Bindings) -> Option { let current = &scopes.last().expect("No current scope found"); if matches!(current.kind, ScopeKind::Function(_)) && !current.bindings.contains_key(name) { for scope in scopes.iter().rev().skip(1) { diff --git a/crates/ruff/src/rules/pyflakes/rules/unused_annotation.rs b/crates/ruff/src/rules/pyflakes/rules/unused_annotation.rs index b82258389363f..f7a994b7e76ed 100644 --- a/crates/ruff/src/rules/pyflakes/rules/unused_annotation.rs +++ b/crates/ruff/src/rules/pyflakes/rules/unused_annotation.rs @@ -1,5 +1,6 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::types::ScopeId; use crate::checkers::ast::Checker; @@ -17,7 +18,7 @@ impl Violation for UnusedAnnotation { } /// F842 -pub fn unused_annotation(checker: &mut Checker, scope: usize) { +pub fn unused_annotation(checker: &mut Checker, scope: ScopeId) { let scope = &checker.ctx.scopes[scope]; for (name, binding) in scope .bindings diff --git a/crates/ruff/src/rules/pyflakes/rules/unused_variable.rs b/crates/ruff/src/rules/pyflakes/rules/unused_variable.rs index f99cacb527d3d..109b875ca83f7 100644 --- a/crates/ruff/src/rules/pyflakes/rules/unused_variable.rs +++ b/crates/ruff/src/rules/pyflakes/rules/unused_variable.rs @@ -7,7 +7,7 @@ use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::contains_effect; use ruff_python_ast::source_code::Locator; -use ruff_python_ast::types::{Range, RefEquality, ScopeKind}; +use ruff_python_ast::types::{Range, RefEquality, ScopeId, ScopeKind}; use crate::autofix::helpers::delete_stmt; use crate::checkers::ast::Checker; @@ -312,7 +312,7 @@ fn remove_unused_variable( } /// F841 -pub fn unused_variable(checker: &mut Checker, scope: usize) { +pub fn unused_variable(checker: &mut Checker, scope: ScopeId) { let scope = &checker.ctx.scopes[scope]; if scope.uses_locals && matches!(scope.kind, ScopeKind::Function(..)) { return; diff --git a/crates/ruff/src/rules/pyflakes/rules/yield_outside_function.rs b/crates/ruff/src/rules/pyflakes/rules/yield_outside_function.rs index fcf83bceee881..96133bc936f52 100644 --- a/crates/ruff/src/rules/pyflakes/rules/yield_outside_function.rs +++ b/crates/ruff/src/rules/pyflakes/rules/yield_outside_function.rs @@ -40,7 +40,7 @@ impl Violation for YieldOutsideFunction { pub fn yield_outside_function(checker: &mut Checker, expr: &Expr) { if matches!( - checker.ctx.current_scope().kind, + checker.ctx.scope().kind, ScopeKind::Class(_) | ScopeKind::Module ) { let keyword = match expr.node { diff --git a/crates/ruff/src/rules/pylint/helpers.rs b/crates/ruff/src/rules/pylint/helpers.rs index 64f31492a4eb3..e1fb0b413a6bd 100644 --- a/crates/ruff/src/rules/pylint/helpers.rs +++ b/crates/ruff/src/rules/pylint/helpers.rs @@ -5,7 +5,7 @@ use ruff_python_ast::types::{FunctionDef, ScopeKind}; use crate::checkers::ast::Checker; pub fn in_dunder_init(checker: &Checker) -> bool { - let scope = checker.ctx.current_scope(); + let scope = checker.ctx.scope(); let ScopeKind::Function(FunctionDef { name, decorator_list, @@ -16,7 +16,7 @@ pub fn in_dunder_init(checker: &Checker) -> bool { if name != "__init__" { return false; } - let Some(parent) = checker.ctx.current_scope_parent() else { + let Some(parent) = checker.ctx.parent_scope() else { return false; }; diff --git a/crates/ruff/src/rules/pylint/rules/await_outside_async.rs b/crates/ruff/src/rules/pylint/rules/await_outside_async.rs index 4748e2777dc3b..4c1728ad43d87 100644 --- a/crates/ruff/src/rules/pylint/rules/await_outside_async.rs +++ b/crates/ruff/src/rules/pylint/rules/await_outside_async.rs @@ -20,7 +20,7 @@ impl Violation for AwaitOutsideAsync { pub fn await_outside_async(checker: &mut Checker, expr: &Expr) { if !checker .ctx - .current_scopes() + .scopes() .find_map(|scope| { if let ScopeKind::Function(FunctionDef { async_, .. }) = &scope.kind { Some(*async_) diff --git a/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs b/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs index 6878110030d7d..1822a9dc95ae8 100644 --- a/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs +++ b/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs @@ -28,7 +28,7 @@ impl Violation for ConsiderUsingSysExit { /// Return `true` if the `module` was imported using a star import (e.g., `from /// sys import *`). fn is_module_star_imported(checker: &Checker, module: &str) -> bool { - checker.ctx.current_scopes().any(|scope| { + checker.ctx.scopes().any(|scope| { scope.bindings.values().any(|index| { if let BindingKind::StarImportation(_, name) = &checker.ctx.bindings[*index].kind { name.as_ref().map(|name| name == module).unwrap_or_default() @@ -42,7 +42,7 @@ fn is_module_star_imported(checker: &Checker, module: &str) -> bool { /// Return the appropriate `sys.exit` reference based on the current set of /// imports, or `None` is `sys.exit` hasn't been imported. fn get_member_import_name_alias(checker: &Checker, module: &str, member: &str) -> Option { - checker.ctx.current_scopes().find_map(|scope| { + checker.ctx.scopes().find_map(|scope| { scope .bindings .values() diff --git a/crates/ruff/src/rules/pylint/rules/global_statement.rs b/crates/ruff/src/rules/pylint/rules/global_statement.rs index 2ff492ca59ed3..530510feab58d 100644 --- a/crates/ruff/src/rules/pylint/rules/global_statement.rs +++ b/crates/ruff/src/rules/pylint/rules/global_statement.rs @@ -52,7 +52,7 @@ impl Violation for GlobalStatement { /// PLW0603 pub fn global_statement(checker: &mut Checker, name: &str) { - let scope = checker.ctx.current_scope(); + let scope = checker.ctx.scope(); if let Some(index) = scope.bindings.get(name) { let binding = &checker.ctx.bindings[*index]; if binding.kind.is_global() { diff --git a/crates/ruff/src/rules/pylint/rules/used_prior_global_declaration.rs b/crates/ruff/src/rules/pylint/rules/used_prior_global_declaration.rs index 22126b046810a..46beebe2d0211 100644 --- a/crates/ruff/src/rules/pylint/rules/used_prior_global_declaration.rs +++ b/crates/ruff/src/rules/pylint/rules/used_prior_global_declaration.rs @@ -21,7 +21,7 @@ impl Violation for UsedPriorGlobalDeclaration { } /// PLE0118 pub fn used_prior_global_declaration(checker: &mut Checker, name: &str, expr: &Expr) { - let globals = match &checker.ctx.current_scope().kind { + let globals = match &checker.ctx.scope().kind { ScopeKind::Class(class_def) => &class_def.globals, ScopeKind::Function(function_def) => &function_def.globals, _ => return, diff --git a/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs b/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs index d6a7a099e728c..4fdab4b178676 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs @@ -38,7 +38,7 @@ pub fn super_call_with_parameters(checker: &mut Checker, expr: &Expr, func: &Exp if !is_super_call_with_arguments(func, args) { return; } - let scope = checker.ctx.current_scope(); + let scope = checker.ctx.scope(); let parents: Vec<&Stmt> = checker.ctx.parents.iter().map(Into::into).collect(); // Check: are we in a Function scope? diff --git a/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs b/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs index fec76bcfaf3ba..e2bf86f1c45d1 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs @@ -2,6 +2,7 @@ use rustpython_parser::ast::{Expr, ExprKind, Keyword, Stmt}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::context::Bindings; use ruff_python_ast::types::{Binding, BindingKind, Range, Scope}; use crate::checkers::ast::Checker; @@ -26,7 +27,7 @@ impl AlwaysAutofixableViolation for UselessObjectInheritance { } } -fn rule(name: &str, bases: &[Expr], scope: &Scope, bindings: &[Binding]) -> Option { +fn rule(name: &str, bases: &[Expr], scope: &Scope, bindings: &Bindings) -> Option { for expr in bases { let ExprKind::Name { id, .. } = &expr.node else { continue; @@ -65,7 +66,7 @@ pub fn useless_object_inheritance( bases: &[Expr], keywords: &[Keyword], ) { - let Some(mut diagnostic) = rule(name, bases, checker.ctx.current_scope(), &checker.ctx.bindings) else { + let Some(mut diagnostic) = rule(name, bases, checker.ctx.scope(), &checker.ctx.bindings) else { return; }; if checker.patch(diagnostic.kind.rule()) { diff --git a/crates/ruff_python_ast/src/context.rs b/crates/ruff_python_ast/src/context.rs index 341ac047b823a..947d3b08a6b61 100644 --- a/crates/ruff_python_ast/src/context.rs +++ b/crates/ruff_python_ast/src/context.rs @@ -1,6 +1,7 @@ +use std::ops::{Deref, Index, IndexMut}; use std::path::Path; -use nohash_hasher::IntMap; +use nohash_hasher::{BuildNoHashHasher, IntMap}; use rustc_hash::FxHashMap; use rustpython_parser::ast::{Expr, Stmt}; use smallvec::smallvec; @@ -9,7 +10,10 @@ use ruff_python_stdlib::path::is_python_stub_file; use ruff_python_stdlib::typing::TYPING_EXTENSIONS; use crate::helpers::{collect_call_path, from_relative_import, Exceptions}; -use crate::types::{Binding, BindingKind, CallPath, ExecutionContext, RefEquality, Scope}; +use crate::types::{ + Binding, BindingId, BindingKind, CallPath, ExecutionContext, RefEquality, Scope, ScopeId, + ScopeKind, +}; use crate::visibility::{module_visibility, Modifier, VisibleScope}; #[allow(clippy::struct_excessive_bools)] @@ -22,13 +26,14 @@ pub struct Context<'a> { pub depths: FxHashMap, usize>, pub child_to_parent: FxHashMap, RefEquality<'a, Stmt>>, // A stack of all bindings created in any scope, at any point in execution. - pub bindings: Vec>, + pub bindings: Bindings<'a>, // Map from binding index to indexes of bindings that redefine it in other scopes. - pub redefinitions: IntMap>, + pub redefinitions: + std::collections::HashMap, BuildNoHashHasher>, pub exprs: Vec>, - pub scopes: Vec>, - pub scope_stack: Vec, - pub dead_scopes: Vec<(usize, Vec)>, + pub scopes: Scopes<'a>, + pub scope_stack: ScopeStack, + pub dead_scopes: Vec<(ScopeId, ScopeStack)>, // Body iteration; used to peek at siblings. pub body: &'a [Stmt], pub body_index: usize, @@ -60,11 +65,11 @@ impl<'a> Context<'a> { parents: Vec::default(), depths: FxHashMap::default(), child_to_parent: FxHashMap::default(), - bindings: Vec::default(), + bindings: Bindings::default(), redefinitions: IntMap::default(), exprs: Vec::default(), - scopes: Vec::default(), - scope_stack: Vec::default(), + scopes: Scopes::default(), + scope_stack: ScopeStack::default(), dead_scopes: Vec::default(), body: &[], body_index: 0, @@ -119,7 +124,7 @@ impl<'a> Context<'a> { /// Return the current `Binding` for a given `name`. pub fn find_binding(&self, member: &str) -> Option<&Binding> { - self.current_scopes() + self.scopes() .find_map(|scope| scope.bindings.get(member)) .map(|index| &self.bindings[*index]) } @@ -217,9 +222,10 @@ impl<'a> Context<'a> { .expect("Attempted to pop without expression"); } - pub fn push_scope(&mut self, scope: Scope<'a>) { - self.scope_stack.push(self.scopes.len()); - self.scopes.push(scope); + pub fn push_scope(&mut self, kind: ScopeKind<'a>) -> ScopeId { + let id = self.scopes.push_scope(kind); + self.scope_stack.push(id); + id } pub fn pop_scope(&mut self) { @@ -261,23 +267,41 @@ impl<'a> Context<'a> { self.body.get(self.body_index + 1) } - pub fn current_scope(&self) -> &Scope { - &self.scopes[*(self.scope_stack.last().expect("No current scope found"))] + /// Returns a reference to the global scope + pub fn global_scope(&self) -> &Scope<'a> { + self.scopes.global() } - pub fn current_scope_parent(&self) -> Option<&Scope> { + /// Returns a mutable reference to the global scope + pub fn global_scope_mut(&mut self) -> &mut Scope<'a> { + self.scopes.global_mut() + } + + /// Returns the current top most scope. + pub fn scope(&self) -> &Scope<'a> { + &self.scopes[self.scope_stack.top().expect("No current scope found")] + } + + /// Returns the id of the top-most scope + pub fn scope_id(&self) -> ScopeId { + self.scope_stack.top().expect("No current scope found") + } + + /// Returns a mutable reference to the current top most scope. + pub fn scope_mut(&mut self) -> &mut Scope<'a> { + let top_id = self.scope_stack.top().expect("No current scope found"); + &mut self.scopes[top_id] + } + + pub fn parent_scope(&self) -> Option<&Scope> { self.scope_stack .iter() - .rev() .nth(1) .map(|index| &self.scopes[*index]) } - pub fn current_scopes(&self) -> impl Iterator { - self.scope_stack - .iter() - .rev() - .map(|index| &self.scopes[*index]) + pub fn scopes(&self) -> impl Iterator { + self.scope_stack.iter().map(|index| &self.scopes[*index]) } pub const fn in_exception_handler(&self) -> bool { @@ -295,3 +319,132 @@ impl<'a> Context<'a> { } } } + +/// The scopes of a program indexed by [`ScopeId`] +#[derive(Debug)] +pub struct Scopes<'a>(Vec>); + +impl<'a> Scopes<'a> { + /// Returns a reference to the global scope + pub fn global(&self) -> &Scope<'a> { + &self[ScopeId::global()] + } + + /// Returns a mutable reference to the global scope + pub fn global_mut(&mut self) -> &mut Scope<'a> { + &mut self[ScopeId::global()] + } + + /// Pushes a new scope and returns its unique id + fn push_scope(&mut self, kind: ScopeKind<'a>) -> ScopeId { + let next_id = ScopeId::try_from(self.0.len()).unwrap(); + self.0.push(Scope::local(next_id, kind)); + next_id + } +} + +impl Default for Scopes<'_> { + fn default() -> Self { + Self(vec![Scope::global(ScopeKind::Module)]) + } +} + +impl<'a> Index for Scopes<'a> { + type Output = Scope<'a>; + + fn index(&self, index: ScopeId) -> &Self::Output { + &self.0[usize::from(index)] + } +} + +impl<'a> IndexMut for Scopes<'a> { + fn index_mut(&mut self, index: ScopeId) -> &mut Self::Output { + &mut self.0[usize::from(index)] + } +} + +impl<'a> Deref for Scopes<'a> { + type Target = [Scope<'a>]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Clone)] +pub struct ScopeStack(Vec); + +impl ScopeStack { + /// Pushes a new scope on the stack + pub fn push(&mut self, id: ScopeId) { + self.0.push(id); + } + + /// Pops the top most scope + pub fn pop(&mut self) -> Option { + self.0.pop() + } + + /// Returns the id of the top-most + pub fn top(&self) -> Option { + self.0.last().copied() + } + + /// Returns an iterator from the current scope to the top scope (reverse iterator) + pub fn iter(&self) -> std::iter::Rev> { + self.0.iter().rev() + } +} + +impl Default for ScopeStack { + fn default() -> Self { + Self(vec![ScopeId::global()]) + } +} + +/// The bindings in a program. +/// +/// Bindings are indexed by [`BindingId`] +#[derive(Debug, Clone, Default)] +pub struct Bindings<'a>(Vec>); + +impl<'a> Bindings<'a> { + /// Pushes a new binding and returns its id + pub fn push(&mut self, binding: Binding<'a>) -> BindingId { + let id = self.next_id(); + self.0.push(binding); + id + } + + /// Returns the id that will be assigned when pushing the next binding + pub fn next_id(&self) -> BindingId { + BindingId::try_from(self.0.len()).unwrap() + } +} + +impl<'a> Index for Bindings<'a> { + type Output = Binding<'a>; + + fn index(&self, index: BindingId) -> &Self::Output { + &self.0[usize::from(index)] + } +} + +impl<'a> IndexMut for Bindings<'a> { + fn index_mut(&mut self, index: BindingId) -> &mut Self::Output { + &mut self.0[usize::from(index)] + } +} + +impl<'a> Deref for Bindings<'a> { + type Target = [Binding<'a>]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> FromIterator> for Bindings<'a> { + fn from_iter>>(iter: T) -> Self { + Self(Vec::from_iter(iter)) + } +} diff --git a/crates/ruff_python_ast/src/types.rs b/crates/ruff_python_ast/src/types.rs index 0e158889d7b5b..1c27a7bf581d3 100644 --- a/crates/ruff_python_ast/src/types.rs +++ b/crates/ruff_python_ast/src/types.rs @@ -1,14 +1,9 @@ +use std::num::TryFromIntError; use std::ops::Deref; -use std::sync::atomic::{AtomicUsize, Ordering}; use rustc_hash::FxHashMap; use rustpython_parser::ast::{Arguments, Expr, Keyword, Located, Location, Stmt}; -fn id() -> usize { - static COUNTER: AtomicUsize = AtomicUsize::new(1); - COUNTER.fetch_add(1, Ordering::Relaxed) -} - #[derive(Clone)] pub enum Node<'a> { Stmt(&'a Stmt), @@ -84,24 +79,63 @@ pub enum ScopeKind<'a> { Lambda(Lambda<'a>), } +/// Id uniquely identifying a scope in a program. +/// +/// Using a `u32` is sufficient because Ruff only supports parsing documents with a size of max `u32::max` +/// and it is impossible to have more scopes than characters in the file (because defining a function or class +/// requires more than one character). +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct ScopeId(u32); + +impl ScopeId { + /// Returns the ID for the global scope + #[inline] + pub const fn global() -> Self { + ScopeId(0) + } + + /// Returns `true` if this is the id of the global scope + pub const fn is_global(&self) -> bool { + self.0 == 0 + } +} + +impl TryFrom for ScopeId { + type Error = TryFromIntError; + + fn try_from(value: usize) -> Result { + Ok(Self(u32::try_from(value)?)) + } +} + +impl From for usize { + fn from(value: ScopeId) -> Self { + value.0 as usize + } +} + #[derive(Debug)] pub struct Scope<'a> { - pub id: usize, + pub id: ScopeId, pub kind: ScopeKind<'a>, pub import_starred: bool, pub uses_locals: bool, /// A map from bound name to binding index, for live bindings. - pub bindings: FxHashMap<&'a str, usize>, + pub bindings: FxHashMap<&'a str, BindingId>, /// A map from bound name to binding index, for bindings that were created /// in the scope but rebound (and thus overridden) later on in the same /// scope. - pub rebounds: FxHashMap<&'a str, Vec>, + pub rebounds: FxHashMap<&'a str, Vec>, } impl<'a> Scope<'a> { - pub fn new(kind: ScopeKind<'a>) -> Self { + pub fn global(kind: ScopeKind<'a>) -> Self { + Self::local(ScopeId::global(), kind) + } + + pub fn local(id: ScopeId, kind: ScopeKind<'a>) -> Self { Scope { - id: id(), + id, kind, import_starred: false, uses_locals: false, @@ -148,6 +182,30 @@ pub enum BindingKind<'a> { SubmoduleImportation(&'a str, &'a str), } +/// ID uniquely identifying a [Binding] in a program. +/// +/// Using a `u32` to identify [Binding]s should is sufficient because Ruff only supports documents with a +/// size smaller than or equal to `u32::max`. A document with the size of `u32::max` must have fewer than `u32::max` +/// bindings because bindings must be separated by whitespace (and have an assignment). +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct BindingId(u32); + +impl From for usize { + fn from(value: BindingId) -> Self { + value.0 as usize + } +} + +impl TryFrom for BindingId { + type Error = TryFromIntError; + + fn try_from(value: usize) -> Result { + Ok(Self(u32::try_from(value)?)) + } +} + +impl nohash_hasher::IsEnabled for BindingId {} + #[derive(Debug, Clone)] pub struct Binding<'a> { pub kind: BindingKind<'a>, @@ -158,15 +216,15 @@ pub struct Binding<'a> { pub source: Option>, /// Tuple of (scope index, range) indicating the scope and range at which /// the binding was last used in a runtime context. - pub runtime_usage: Option<(usize, Range)>, + pub runtime_usage: Option<(ScopeId, Range)>, /// Tuple of (scope index, range) indicating the scope and range at which /// the binding was last used in a typing-time context. - pub typing_usage: Option<(usize, Range)>, + pub typing_usage: Option<(ScopeId, Range)>, /// Tuple of (scope index, range) indicating the scope and range at which /// the binding was last used in a synthetic context. This is used for /// (e.g.) `__future__` imports, explicit re-exports, and other bindings /// that should be considered used even if they're never referenced. - pub synthetic_usage: Option<(usize, Range)>, + pub synthetic_usage: Option<(ScopeId, Range)>, } #[derive(Copy, Debug, Clone)] @@ -176,7 +234,7 @@ pub enum ExecutionContext { } impl<'a> Binding<'a> { - pub fn mark_used(&mut self, scope: usize, range: Range, context: ExecutionContext) { + pub fn mark_used(&mut self, scope: ScopeId, range: Range, context: ExecutionContext) { match context { ExecutionContext::Runtime => self.runtime_usage = Some((scope, range)), ExecutionContext::Typing => self.typing_usage = Some((scope, range)), From dedf4cbdebd5c5622849aab5dea4833c2af198a5 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 17 Mar 2023 17:31:33 +0100 Subject: [PATCH 40/49] refactor: Move scope and binding types to `scope.rs` (#3573) --- crates/ruff/src/checkers/ast/deferred.rs | 2 +- crates/ruff/src/checkers/ast/mod.rs | 88 ++-- .../rules/cached_instance_method.rs | 3 +- .../rules/unused_loop_control_variable.rs | 1 - .../rules/private_member_access.rs | 3 +- .../flake8_simplify/rules/ast_unary_op.rs | 3 +- .../src/rules/flake8_type_checking/helpers.rs | 2 +- .../runtime_import_in_type_checking_block.rs | 2 +- .../rules/typing_only_runtime_import.rs | 2 +- .../rules/flake8_unused_arguments/rules.rs | 22 +- .../src/rules/pandas_vet/rules/check_attr.rs | 3 +- .../src/rules/pandas_vet/rules/check_call.rs | 3 +- .../pep8_naming/rules/dunder_function_name.rs | 2 +- ...id_first_argument_name_for_class_method.rs | 3 +- .../invalid_first_argument_name_for_method.rs | 3 +- .../pycodestyle/rules/lambda_assignment.rs | 3 +- .../pyflakes/rules/return_outside_function.rs | 3 +- .../rules/pyflakes/rules/undefined_export.rs | 5 +- .../rules/pyflakes/rules/undefined_local.rs | 7 +- .../rules/pyflakes/rules/unused_annotation.rs | 5 +- .../rules/pyflakes/rules/unused_variable.rs | 6 +- .../pyflakes/rules/yield_outside_function.rs | 3 +- crates/ruff/src/rules/pylint/helpers.rs | 2 +- .../rules/pylint/rules/await_outside_async.rs | 3 +- .../pylint/rules/consider_using_sys_exit.rs | 8 +- .../rules/pylint/rules/global_statement.rs | 2 +- .../rules/used_prior_global_declaration.rs | 3 +- .../rules/super_call_with_parameters.rs | 3 +- .../rules/useless_object_inheritance.rs | 9 +- crates/ruff_python_ast/src/context.rs | 139 +----- crates/ruff_python_ast/src/function_type.rs | 2 +- crates/ruff_python_ast/src/helpers.rs | 3 +- crates/ruff_python_ast/src/lib.rs | 1 + crates/ruff_python_ast/src/operations.rs | 4 +- crates/ruff_python_ast/src/scope.rs | 424 ++++++++++++++++++ crates/ruff_python_ast/src/types.rs | 265 +---------- 36 files changed, 535 insertions(+), 507 deletions(-) create mode 100644 crates/ruff_python_ast/src/scope.rs diff --git a/crates/ruff/src/checkers/ast/deferred.rs b/crates/ruff/src/checkers/ast/deferred.rs index ccef124bd3282..169d9b347b395 100644 --- a/crates/ruff/src/checkers/ast/deferred.rs +++ b/crates/ruff/src/checkers/ast/deferred.rs @@ -1,4 +1,4 @@ -use ruff_python_ast::context::ScopeStack; +use ruff_python_ast::scope::ScopeStack; use rustpython_parser::ast::{Expr, Stmt}; use ruff_python_ast::types::Range; diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 80830531c9e1d..72b5d101bcbf8 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -14,17 +14,18 @@ use rustpython_parser::ast::{ }; use ruff_diagnostics::Diagnostic; -use ruff_python_ast::context::{Context, ScopeStack}; +use ruff_python_ast::context::Context; use ruff_python_ast::helpers::{ binding_range, extract_handled_exceptions, to_module_path, Exceptions, }; use ruff_python_ast::operations::{extract_all_names, AllNamesFlags}; use ruff_python_ast::relocate::relocate_expr; -use ruff_python_ast::source_code::{Indexer, Locator, Stylist}; -use ruff_python_ast::types::{ - Binding, BindingId, BindingKind, ClassDef, ExecutionContext, FunctionDef, Lambda, Node, Range, - RefEquality, Scope, ScopeId, ScopeKind, +use ruff_python_ast::scope::{ + Binding, BindingId, BindingKind, ClassDef, ExecutionContext, FunctionDef, Lambda, Scope, + ScopeId, ScopeKind, ScopeStack, }; +use ruff_python_ast::source_code::{Indexer, Locator, Stylist}; +use ruff_python_ast::types::{Node, Range, RefEquality}; use ruff_python_ast::typing::{match_annotated_subscript, Callable, SubscriptKind}; use ruff_python_ast::visitor::{walk_excepthandler, walk_pattern, Visitor}; use ruff_python_ast::{ @@ -208,7 +209,7 @@ where source: Some(RefEquality(stmt)), context, }); - scope.bindings.insert(name, id); + scope.add(name, id); } } @@ -237,7 +238,7 @@ where source: Some(RefEquality(stmt)), context, }); - scope.bindings.insert(name, id); + scope.add(name, id); } // Mark the binding in the defining scopes as used too. (Skip the global scope @@ -249,9 +250,7 @@ where scopes_iter.next_back(); for index in scopes_iter.skip(1) { - if let Some(index) = - self.ctx.scopes[*index].bindings.get(&name.as_str()) - { + if let Some(index) = self.ctx.scopes[*index].get(name.as_str()) { exists = true; self.ctx.bindings[*index].runtime_usage = usage; } @@ -1874,7 +1873,6 @@ where if self .ctx .global_scope() - .bindings .get(name) .map_or(true, |index| self.ctx.bindings[*index].kind.is_annotation()) { @@ -1887,7 +1885,7 @@ where source: Some(RefEquality(stmt)), context: self.ctx.execution_context(), }); - self.ctx.global_scope_mut().bindings.insert(name, id); + self.ctx.global_scope_mut().add(name, id); } } @@ -1937,7 +1935,6 @@ where if self .ctx .global_scope() - .bindings .get(name) .map_or(true, |index| self.ctx.bindings[*index].kind.is_annotation()) { @@ -1950,7 +1947,7 @@ where source: Some(RefEquality(stmt)), context: self.ctx.execution_context(), }); - self.ctx.global_scope_mut().bindings.insert(name, id); + self.ctx.global_scope_mut().add(name, id); } } @@ -3713,7 +3710,7 @@ where let name_range = helpers::excepthandler_name_range(excepthandler, self.locator).unwrap(); - if self.ctx.scope().bindings.contains_key(&name.as_str()) { + if self.ctx.scope().defines(name.as_str()) { self.handle_node_store( name, &Expr::new( @@ -3727,7 +3724,7 @@ where ); } - let definition = self.ctx.scope().bindings.get(&name.as_str()).copied(); + let definition = self.ctx.scope().get(name.as_str()).copied(); self.handle_node_store( name, &Expr::new( @@ -3744,7 +3741,7 @@ where if let Some(index) = { let scope = self.ctx.scope_mut(); - &scope.bindings.remove(&name.as_str()) + &scope.remove(name.as_str()) } { if !self.ctx.bindings[*index].used() { if self.settings.rules.enabled(Rule::UnusedVariable) { @@ -3778,7 +3775,7 @@ where if let Some(index) = definition { let scope = self.ctx.scope_mut(); - scope.bindings.insert(name, index); + scope.add(name, index); } } None => walk_excepthandler(self, excepthandler), @@ -3933,14 +3930,17 @@ impl<'a> Checker<'a> { 'b: 'a, { let binding_id = self.ctx.bindings.next_id(); - if let Some((stack_index, scope_index)) = self + if let Some((stack_index, existing_binding_index)) = self .ctx .scope_stack .iter() .enumerate() - .find(|(_, scope_index)| self.ctx.scopes[**scope_index].bindings.contains_key(&name)) + .find_map(|(stack_index, scope_index)| { + self.ctx.scopes[*scope_index] + .get(name) + .map(|binding_id| (stack_index, *binding_id)) + }) { - let existing_binding_index = self.ctx.scopes[*scope_index].bindings[&name]; let existing = &self.ctx.bindings[existing_binding_index]; let in_current_scope = stack_index == 0; if !existing.kind.is_builtin() @@ -4012,7 +4012,7 @@ impl<'a> Checker<'a> { } let scope = self.ctx.scope(); - let binding = if let Some(index) = scope.bindings.get(&name) { + let binding = if let Some(index) = scope.get(name) { let existing = &self.ctx.bindings[*index]; match &existing.kind { BindingKind::Builtin => { @@ -4044,8 +4044,8 @@ impl<'a> Checker<'a> { // Don't treat annotations as assignments if there is an existing value // in scope. let scope = self.ctx.scope_mut(); - if !(binding.kind.is_annotation() && scope.bindings.contains_key(name)) { - if let Some(rebound_index) = scope.bindings.insert(name, binding_id) { + if !(binding.kind.is_annotation() && scope.defines(name)) { + if let Some(rebound_index) = scope.add(name, binding_id) { scope .rebounds .entry(name) @@ -4076,7 +4076,7 @@ impl<'a> Checker<'a> { source: None, context: ExecutionContext::Runtime, }); - scope.bindings.insert(builtin, id); + scope.add(builtin, id); } } @@ -4101,7 +4101,7 @@ impl<'a> Checker<'a> { } } - if let Some(index) = scope.bindings.get(&id.as_str()) { + if let Some(index) = scope.get(id.as_str()) { // Mark the binding as used. let context = self.ctx.execution_context(); self.ctx.bindings[*index].mark_used(scope_id, Range::from(expr), context); @@ -4131,7 +4131,7 @@ impl<'a> Checker<'a> { .unwrap_or_default(); if has_alias { // Mark the sub-importation as used. - if let Some(index) = scope.bindings.get(full_name) { + if let Some(index) = scope.get(full_name) { self.ctx.bindings[*index].mark_used( scope_id, Range::from(expr), @@ -4148,7 +4148,7 @@ impl<'a> Checker<'a> { .unwrap_or_default(); if has_alias { // Mark the sub-importation as used. - if let Some(index) = scope.bindings.get(full_name.as_str()) { + if let Some(index) = scope.get(full_name.as_str()) { self.ctx.bindings[*index].mark_used( scope_id, Range::from(expr), @@ -4173,11 +4173,7 @@ impl<'a> Checker<'a> { let mut from_list = vec![]; for scope_index in self.ctx.scope_stack.iter() { let scope = &self.ctx.scopes[*scope_index]; - for binding in scope - .bindings - .values() - .map(|index| &self.ctx.bindings[*index]) - { + for binding in scope.binding_ids().map(|index| &self.ctx.bindings[*index]) { if let BindingKind::StarImportation(level, module) = &binding.kind { from_list.push(helpers::format_import_from( level.as_ref(), @@ -4260,7 +4256,6 @@ impl<'a> Checker<'a> { if !self .ctx .scope() - .bindings .get(id) .map_or(false, |index| self.ctx.bindings[*index].kind.is_global()) { @@ -4433,7 +4428,7 @@ impl<'a> Checker<'a> { } let scope = self.ctx.scope_mut(); - if scope.bindings.remove(&id.as_str()).is_some() { + if scope.remove(id.as_str()).is_some() { return; } if !self.settings.rules.enabled(Rule::UndefinedName) { @@ -4663,7 +4658,6 @@ impl<'a> Checker<'a> { let all_bindings: Option<(Vec, Range)> = { let global_scope = self.ctx.global_scope(); let all_names: Option<(&Vec, Range)> = global_scope - .bindings .get("__all__") .map(|index| &self.ctx.bindings[*index]) .and_then(|binding| match &binding.kind { @@ -4675,7 +4669,7 @@ impl<'a> Checker<'a> { ( names .iter() - .filter_map(|name| global_scope.bindings.get(name.as_str()).copied()) + .filter_map(|name| global_scope.get(name.as_str()).copied()) .collect(), range, ) @@ -4696,7 +4690,6 @@ impl<'a> Checker<'a> { let all_names: Option<(Vec<&str>, Range)> = self .ctx .global_scope() - .bindings .get("__all__") .map(|index| &self.ctx.bindings[*index]) .and_then(|binding| match &binding.kind { @@ -4718,8 +4711,7 @@ impl<'a> Checker<'a> { .iter() .map(|scope| { scope - .bindings - .values() + .binding_ids() .map(|index| &self.ctx.bindings[*index]) .filter(|binding| { flake8_type_checking::helpers::is_valid_runtime_import(binding) @@ -4749,7 +4741,7 @@ impl<'a> Checker<'a> { // PLW0602 if self.settings.rules.enabled(Rule::GlobalVariableNotAssigned) { - for (name, index) in &scope.bindings { + for (name, index) in scope.bindings() { let binding = &self.ctx.bindings[*index]; if binding.kind.is_global() { if let Some(stmt) = &binding.source { @@ -4775,7 +4767,7 @@ impl<'a> Checker<'a> { // unused. Note that we only store references in `redefinitions` if // the bindings are in different scopes. if self.settings.rules.enabled(Rule::RedefinedWhileUnused) { - for (name, index) in &scope.bindings { + for (name, index) in scope.bindings() { let binding = &self.ctx.bindings[*index]; if matches!( @@ -4818,11 +4810,7 @@ impl<'a> Checker<'a> { if scope.import_starred { if let Some((names, range)) = &all_names { let mut from_list = vec![]; - for binding in scope - .bindings - .values() - .map(|index| &self.ctx.bindings[*index]) - { + for binding in scope.binding_ids().map(|index| &self.ctx.bindings[*index]) { if let BindingKind::StarImportation(level, module) = &binding.kind { from_list.push(helpers::format_import_from( level.as_ref(), @@ -4833,7 +4821,7 @@ impl<'a> Checker<'a> { from_list.sort(); for &name in names { - if !scope.bindings.contains_key(name) { + if !scope.defines(name) { diagnostics.push(Diagnostic::new( pyflakes::rules::ImportStarUsage { name: name.to_string(), @@ -4859,7 +4847,7 @@ impl<'a> Checker<'a> { .copied() .collect() }; - for (.., index) in &scope.bindings { + for index in scope.binding_ids() { let binding = &self.ctx.bindings[*index]; if let Some(diagnostic) = @@ -4895,7 +4883,7 @@ impl<'a> Checker<'a> { let mut ignored: FxHashMap> = FxHashMap::default(); - for index in scope.bindings.values() { + for index in scope.binding_ids() { let binding = &self.ctx.bindings[*index]; let full_name = match &binding.kind { diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/cached_instance_method.rs b/crates/ruff/src/rules/flake8_bugbear/rules/cached_instance_method.rs index 6aea88d051ebc..e849ef94ce8c6 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/cached_instance_method.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/cached_instance_method.rs @@ -2,7 +2,8 @@ use rustpython_parser::ast::{Expr, ExprKind}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::{Range, ScopeKind}; +use ruff_python_ast::scope::ScopeKind; +use ruff_python_ast::types::Range; use crate::checkers::ast::Checker; diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs b/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs index 2f83bbc2fb12a..9dc1bf6da5744 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs @@ -165,7 +165,6 @@ pub fn unused_loop_control_variable( // Find the `BindingKind::LoopVar` corresponding to the name. let scope = checker.ctx.scope(); let binding = scope - .bindings .get(name) .into_iter() .chain(scope.rebounds.get(name).into_iter().flatten()) diff --git a/crates/ruff/src/rules/flake8_self/rules/private_member_access.rs b/crates/ruff/src/rules/flake8_self/rules/private_member_access.rs index 3154fcdefc0fb..9d1300f75ee24 100644 --- a/crates/ruff/src/rules/flake8_self/rules/private_member_access.rs +++ b/crates/ruff/src/rules/flake8_self/rules/private_member_access.rs @@ -3,7 +3,8 @@ use rustpython_parser::ast::{Expr, ExprKind}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::collect_call_path; -use ruff_python_ast::types::{Range, ScopeKind}; +use ruff_python_ast::scope::ScopeKind; +use ruff_python_ast::types::Range; use crate::checkers::ast::Checker; diff --git a/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs b/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs index b6c9989ed932a..5913d9bf068b8 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs @@ -3,7 +3,8 @@ use rustpython_parser::ast::{Cmpop, Expr, ExprKind, Stmt, StmtKind, Unaryop}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::{create_expr, unparse_expr}; -use ruff_python_ast::types::{Range, ScopeKind}; +use ruff_python_ast::scope::ScopeKind; +use ruff_python_ast::types::Range; use crate::checkers::ast::Checker; use crate::registry::AsRule; diff --git a/crates/ruff/src/rules/flake8_type_checking/helpers.rs b/crates/ruff/src/rules/flake8_type_checking/helpers.rs index 6b520fee7c769..a03950d3d0b24 100644 --- a/crates/ruff/src/rules/flake8_type_checking/helpers.rs +++ b/crates/ruff/src/rules/flake8_type_checking/helpers.rs @@ -3,7 +3,7 @@ use rustpython_parser::ast::{Constant, Expr, ExprKind}; use ruff_python_ast::context::Context; use ruff_python_ast::helpers::{map_callable, to_call_path}; -use ruff_python_ast::types::{Binding, BindingKind, ExecutionContext, ScopeKind}; +use ruff_python_ast::scope::{Binding, BindingKind, ExecutionContext, ScopeKind}; /// Return `true` if [`Expr`] is a guard for a type-checking block. pub fn is_type_checking_block(context: &Context, test: &Expr) -> bool { diff --git a/crates/ruff/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs b/crates/ruff/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs index 12ee0a2560193..27422dc70bcbf 100644 --- a/crates/ruff/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs +++ b/crates/ruff/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs @@ -1,6 +1,6 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::{Binding, BindingKind, ExecutionContext}; +use ruff_python_ast::scope::{Binding, BindingKind, ExecutionContext}; #[violation] pub struct RuntimeImportInTypeCheckingBlock { diff --git a/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs b/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs index c376b4c2e3e88..b15642a275a9f 100644 --- a/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs +++ b/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs @@ -2,7 +2,7 @@ use std::path::Path; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::{Binding, BindingKind, ExecutionContext}; +use ruff_python_ast::scope::{Binding, BindingKind, ExecutionContext}; use crate::rules::isort::{categorize, ImportType}; use crate::settings::Settings; diff --git a/crates/ruff/src/rules/flake8_unused_arguments/rules.rs b/crates/ruff/src/rules/flake8_unused_arguments/rules.rs index c4f94eacfd045..01a4aaf7cf8eb 100644 --- a/crates/ruff/src/rules/flake8_unused_arguments/rules.rs +++ b/crates/ruff/src/rules/flake8_unused_arguments/rules.rs @@ -1,15 +1,13 @@ use std::iter; use regex::Regex; -use rustc_hash::FxHashMap; use rustpython_parser::ast::{Arg, Arguments}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::context::Bindings; use ruff_python_ast::function_type; use ruff_python_ast::function_type::FunctionType; -use ruff_python_ast::types::{BindingId, FunctionDef, Lambda, Scope, ScopeKind}; +use ruff_python_ast::scope::{Bindings, FunctionDef, Lambda, Scope, ScopeKind}; use ruff_python_ast::visibility; use crate::checkers::ast::Checker; @@ -86,7 +84,7 @@ impl Violation for UnusedLambdaArgument { fn function( argumentable: &Argumentable, args: &Arguments, - values: &FxHashMap<&str, BindingId>, + values: &Scope, bindings: &Bindings, dummy_variable_rgx: &Regex, ignore_variadic_names: bool, @@ -113,7 +111,7 @@ fn function( fn method( argumentable: &Argumentable, args: &Arguments, - values: &FxHashMap<&str, BindingId>, + values: &Scope, bindings: &Bindings, dummy_variable_rgx: &Regex, ignore_variadic_names: bool, @@ -140,14 +138,14 @@ fn method( fn call<'a>( argumentable: &Argumentable, args: impl Iterator, - values: &FxHashMap<&str, BindingId>, + values: &Scope, bindings: &Bindings, dummy_variable_rgx: &Regex, ) -> Vec { let mut diagnostics: Vec = vec![]; for arg in args { if let Some(binding) = values - .get(&arg.node.arg.as_str()) + .get(arg.node.arg.as_str()) .map(|index| &bindings[*index]) { if !binding.used() @@ -197,7 +195,7 @@ pub fn unused_arguments( function( &Argumentable::Function, args, - &scope.bindings, + scope, bindings, &checker.settings.dummy_variable_rgx, checker @@ -226,7 +224,7 @@ pub fn unused_arguments( method( &Argumentable::Method, args, - &scope.bindings, + scope, bindings, &checker.settings.dummy_variable_rgx, checker @@ -255,7 +253,7 @@ pub fn unused_arguments( method( &Argumentable::ClassMethod, args, - &scope.bindings, + scope, bindings, &checker.settings.dummy_variable_rgx, checker @@ -284,7 +282,7 @@ pub fn unused_arguments( function( &Argumentable::StaticMethod, args, - &scope.bindings, + scope, bindings, &checker.settings.dummy_variable_rgx, checker @@ -307,7 +305,7 @@ pub fn unused_arguments( function( &Argumentable::Lambda, args, - &scope.bindings, + scope, bindings, &checker.settings.dummy_variable_rgx, checker diff --git a/crates/ruff/src/rules/pandas_vet/rules/check_attr.rs b/crates/ruff/src/rules/pandas_vet/rules/check_attr.rs index edfff77903a80..fed2f5638da27 100644 --- a/crates/ruff/src/rules/pandas_vet/rules/check_attr.rs +++ b/crates/ruff/src/rules/pandas_vet/rules/check_attr.rs @@ -3,7 +3,8 @@ use rustpython_parser::ast::{Expr, ExprKind}; use ruff_diagnostics::Violation; use ruff_diagnostics::{Diagnostic, DiagnosticKind}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::{BindingKind, Range}; +use ruff_python_ast::scope::BindingKind; +use ruff_python_ast::types::Range; use crate::checkers::ast::Checker; use crate::registry::Rule; diff --git a/crates/ruff/src/rules/pandas_vet/rules/check_call.rs b/crates/ruff/src/rules/pandas_vet/rules/check_call.rs index d74aaba1574d0..0e894a4d948d6 100644 --- a/crates/ruff/src/rules/pandas_vet/rules/check_call.rs +++ b/crates/ruff/src/rules/pandas_vet/rules/check_call.rs @@ -3,7 +3,8 @@ use rustpython_parser::ast::{Expr, ExprKind}; use ruff_diagnostics::Violation; use ruff_diagnostics::{Diagnostic, DiagnosticKind}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::{BindingKind, Range}; +use ruff_python_ast::scope::BindingKind; +use ruff_python_ast::types::Range; use crate::checkers::ast::Checker; use crate::registry::Rule; diff --git a/crates/ruff/src/rules/pep8_naming/rules/dunder_function_name.rs b/crates/ruff/src/rules/pep8_naming/rules/dunder_function_name.rs index fa9b9d4319dd8..8b94be17453bf 100644 --- a/crates/ruff/src/rules/pep8_naming/rules/dunder_function_name.rs +++ b/crates/ruff/src/rules/pep8_naming/rules/dunder_function_name.rs @@ -3,8 +3,8 @@ use rustpython_parser::ast::Stmt; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::identifier_range; +use ruff_python_ast::scope::{Scope, ScopeKind}; use ruff_python_ast::source_code::Locator; -use ruff_python_ast::types::{Scope, ScopeKind}; /// ## What it does /// Checks for functions with "dunder" names (that is, names with two diff --git a/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_class_method.rs b/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_class_method.rs index 233c22c55dec6..19bce277120c9 100644 --- a/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_class_method.rs +++ b/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_class_method.rs @@ -3,7 +3,8 @@ use rustpython_parser::ast::{Arguments, Expr}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::function_type; -use ruff_python_ast::types::{Range, Scope}; +use ruff_python_ast::scope::Scope; +use ruff_python_ast::types::Range; use crate::checkers::ast::Checker; diff --git a/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_method.rs b/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_method.rs index b7658e419c6af..f090d8b1cd0ff 100644 --- a/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_method.rs +++ b/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_method.rs @@ -3,7 +3,8 @@ use rustpython_parser::ast::{Arguments, Expr}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::function_type; -use ruff_python_ast::types::{Range, Scope}; +use ruff_python_ast::scope::Scope; +use ruff_python_ast::types::Range; use crate::checkers::ast::Checker; diff --git a/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs b/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs index 10cda5f65c9e7..3343ac2524eb5 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs @@ -4,8 +4,9 @@ use ruff_diagnostics::{AutofixKind, Availability, Diagnostic, Fix, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::{match_leading_content, match_trailing_content, unparse_stmt}; use ruff_python_ast::newlines::StrExt; +use ruff_python_ast::scope::ScopeKind; use ruff_python_ast::source_code::Stylist; -use ruff_python_ast::types::{Range, ScopeKind}; +use ruff_python_ast::types::Range; use ruff_python_ast::whitespace::leading_space; use crate::checkers::ast::Checker; diff --git a/crates/ruff/src/rules/pyflakes/rules/return_outside_function.rs b/crates/ruff/src/rules/pyflakes/rules/return_outside_function.rs index 2ec768426a9f4..d5688ac43c3bb 100644 --- a/crates/ruff/src/rules/pyflakes/rules/return_outside_function.rs +++ b/crates/ruff/src/rules/pyflakes/rules/return_outside_function.rs @@ -2,7 +2,8 @@ use rustpython_parser::ast::Stmt; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::{Range, ScopeKind}; +use ruff_python_ast::scope::ScopeKind; +use ruff_python_ast::types::Range; use crate::checkers::ast::Checker; diff --git a/crates/ruff/src/rules/pyflakes/rules/undefined_export.rs b/crates/ruff/src/rules/pyflakes/rules/undefined_export.rs index 6a22cf24bad75..1cfa2709d9f93 100644 --- a/crates/ruff/src/rules/pyflakes/rules/undefined_export.rs +++ b/crates/ruff/src/rules/pyflakes/rules/undefined_export.rs @@ -2,7 +2,8 @@ use std::path::Path; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::{Range, Scope}; +use ruff_python_ast::scope::Scope; +use ruff_python_ast::types::Range; #[violation] pub struct UndefinedExport { @@ -27,7 +28,7 @@ pub fn undefined_export( let mut diagnostics = Vec::new(); if !scope.import_starred && !path.ends_with("__init__.py") { for name in names { - if !scope.bindings.contains_key(name) { + if !scope.defines(name) { diagnostics.push(Diagnostic::new( UndefinedExport { name: (*name).to_string(), diff --git a/crates/ruff/src/rules/pyflakes/rules/undefined_local.rs b/crates/ruff/src/rules/pyflakes/rules/undefined_local.rs index e63af15388228..b0f7d5a410c34 100644 --- a/crates/ruff/src/rules/pyflakes/rules/undefined_local.rs +++ b/crates/ruff/src/rules/pyflakes/rules/undefined_local.rs @@ -2,8 +2,7 @@ use std::string::ToString; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::context::Bindings; -use ruff_python_ast::types::{Scope, ScopeKind}; +use ruff_python_ast::scope::{Bindings, Scope, ScopeKind}; #[violation] pub struct UndefinedLocal { @@ -21,10 +20,10 @@ impl Violation for UndefinedLocal { /// F821 pub fn undefined_local(name: &str, scopes: &[&Scope], bindings: &Bindings) -> Option { let current = &scopes.last().expect("No current scope found"); - if matches!(current.kind, ScopeKind::Function(_)) && !current.bindings.contains_key(name) { + if matches!(current.kind, ScopeKind::Function(_)) && !current.defines(name) { for scope in scopes.iter().rev().skip(1) { if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) { - if let Some(binding) = scope.bindings.get(name).map(|index| &bindings[*index]) { + if let Some(binding) = scope.get(name).map(|index| &bindings[*index]) { if let Some((scope_id, location)) = binding.runtime_usage { if scope_id == current.id { return Some(Diagnostic::new( diff --git a/crates/ruff/src/rules/pyflakes/rules/unused_annotation.rs b/crates/ruff/src/rules/pyflakes/rules/unused_annotation.rs index f7a994b7e76ed..80f87b988efb4 100644 --- a/crates/ruff/src/rules/pyflakes/rules/unused_annotation.rs +++ b/crates/ruff/src/rules/pyflakes/rules/unused_annotation.rs @@ -1,6 +1,6 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::ScopeId; +use ruff_python_ast::scope::ScopeId; use crate::checkers::ast::Checker; @@ -21,8 +21,7 @@ impl Violation for UnusedAnnotation { pub fn unused_annotation(checker: &mut Checker, scope: ScopeId) { let scope = &checker.ctx.scopes[scope]; for (name, binding) in scope - .bindings - .iter() + .bindings() .map(|(name, index)| (name, &checker.ctx.bindings[*index])) { if !binding.used() diff --git a/crates/ruff/src/rules/pyflakes/rules/unused_variable.rs b/crates/ruff/src/rules/pyflakes/rules/unused_variable.rs index 109b875ca83f7..8c8245df04d24 100644 --- a/crates/ruff/src/rules/pyflakes/rules/unused_variable.rs +++ b/crates/ruff/src/rules/pyflakes/rules/unused_variable.rs @@ -6,8 +6,9 @@ use rustpython_parser::{lexer, Mode, Tok}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::contains_effect; +use ruff_python_ast::scope::{ScopeId, ScopeKind}; use ruff_python_ast::source_code::Locator; -use ruff_python_ast::types::{Range, RefEquality, ScopeId, ScopeKind}; +use ruff_python_ast::types::{Range, RefEquality}; use crate::autofix::helpers::delete_stmt; use crate::checkers::ast::Checker; @@ -319,8 +320,7 @@ pub fn unused_variable(checker: &mut Checker, scope: ScopeId) { } for (name, binding) in scope - .bindings - .iter() + .bindings() .map(|(name, index)| (name, &checker.ctx.bindings[*index])) { if !binding.used() diff --git a/crates/ruff/src/rules/pyflakes/rules/yield_outside_function.rs b/crates/ruff/src/rules/pyflakes/rules/yield_outside_function.rs index 96133bc936f52..27cc3539eb1f4 100644 --- a/crates/ruff/src/rules/pyflakes/rules/yield_outside_function.rs +++ b/crates/ruff/src/rules/pyflakes/rules/yield_outside_function.rs @@ -4,7 +4,8 @@ use rustpython_parser::ast::{Expr, ExprKind}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::{Range, ScopeKind}; +use ruff_python_ast::scope::ScopeKind; +use ruff_python_ast::types::Range; use crate::checkers::ast::Checker; diff --git a/crates/ruff/src/rules/pylint/helpers.rs b/crates/ruff/src/rules/pylint/helpers.rs index e1fb0b413a6bd..d083a42b04bbe 100644 --- a/crates/ruff/src/rules/pylint/helpers.rs +++ b/crates/ruff/src/rules/pylint/helpers.rs @@ -1,6 +1,6 @@ use ruff_python_ast::function_type; use ruff_python_ast::function_type::FunctionType; -use ruff_python_ast::types::{FunctionDef, ScopeKind}; +use ruff_python_ast::scope::{FunctionDef, ScopeKind}; use crate::checkers::ast::Checker; diff --git a/crates/ruff/src/rules/pylint/rules/await_outside_async.rs b/crates/ruff/src/rules/pylint/rules/await_outside_async.rs index 4c1728ad43d87..73e72135d4887 100644 --- a/crates/ruff/src/rules/pylint/rules/await_outside_async.rs +++ b/crates/ruff/src/rules/pylint/rules/await_outside_async.rs @@ -2,7 +2,8 @@ use rustpython_parser::ast::Expr; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::{FunctionDef, Range, ScopeKind}; +use ruff_python_ast::scope::{FunctionDef, ScopeKind}; +use ruff_python_ast::types::Range; use crate::checkers::ast::Checker; diff --git a/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs b/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs index 1822a9dc95ae8..ae3032845026e 100644 --- a/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs +++ b/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs @@ -2,7 +2,8 @@ use rustpython_parser::ast::{Expr, ExprKind}; use ruff_diagnostics::{AutofixKind, Availability, Diagnostic, Fix, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::{BindingKind, Range}; +use ruff_python_ast::scope::BindingKind; +use ruff_python_ast::types::Range; use crate::checkers::ast::Checker; use crate::registry::AsRule; @@ -29,7 +30,7 @@ impl Violation for ConsiderUsingSysExit { /// sys import *`). fn is_module_star_imported(checker: &Checker, module: &str) -> bool { checker.ctx.scopes().any(|scope| { - scope.bindings.values().any(|index| { + scope.binding_ids().any(|index| { if let BindingKind::StarImportation(_, name) = &checker.ctx.bindings[*index].kind { name.as_ref().map(|name| name == module).unwrap_or_default() } else { @@ -44,8 +45,7 @@ fn is_module_star_imported(checker: &Checker, module: &str) -> bool { fn get_member_import_name_alias(checker: &Checker, module: &str, member: &str) -> Option { checker.ctx.scopes().find_map(|scope| { scope - .bindings - .values() + .binding_ids() .find_map(|index| match &checker.ctx.bindings[*index].kind { // e.g. module=sys object=exit // `import sys` -> `sys.exit` diff --git a/crates/ruff/src/rules/pylint/rules/global_statement.rs b/crates/ruff/src/rules/pylint/rules/global_statement.rs index 530510feab58d..2483c66317eb3 100644 --- a/crates/ruff/src/rules/pylint/rules/global_statement.rs +++ b/crates/ruff/src/rules/pylint/rules/global_statement.rs @@ -53,7 +53,7 @@ impl Violation for GlobalStatement { /// PLW0603 pub fn global_statement(checker: &mut Checker, name: &str) { let scope = checker.ctx.scope(); - if let Some(index) = scope.bindings.get(name) { + if let Some(index) = scope.get(name) { let binding = &checker.ctx.bindings[*index]; if binding.kind.is_global() { let source: &Stmt = binding diff --git a/crates/ruff/src/rules/pylint/rules/used_prior_global_declaration.rs b/crates/ruff/src/rules/pylint/rules/used_prior_global_declaration.rs index 46beebe2d0211..e3bb759db8af5 100644 --- a/crates/ruff/src/rules/pylint/rules/used_prior_global_declaration.rs +++ b/crates/ruff/src/rules/pylint/rules/used_prior_global_declaration.rs @@ -2,7 +2,8 @@ use rustpython_parser::ast::Expr; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::{Range, ScopeKind}; +use ruff_python_ast::scope::ScopeKind; +use ruff_python_ast::types::Range; use crate::checkers::ast::Checker; diff --git a/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs b/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs index 4fdab4b178676..29c98812c8bb9 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs @@ -2,7 +2,8 @@ use rustpython_parser::ast::{ArgData, Expr, ExprKind, Stmt, StmtKind}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::{Range, ScopeKind}; +use ruff_python_ast::scope::ScopeKind; +use ruff_python_ast::types::Range; use crate::checkers::ast::Checker; use crate::registry::AsRule; diff --git a/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs b/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs index e2bf86f1c45d1..064a1755c0568 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs @@ -2,8 +2,8 @@ use rustpython_parser::ast::{Expr, ExprKind, Keyword, Stmt}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::context::Bindings; -use ruff_python_ast::types::{Binding, BindingKind, Range, Scope}; +use ruff_python_ast::scope::{Binding, BindingKind, Bindings, Scope}; +use ruff_python_ast::types::Range; use crate::checkers::ast::Checker; use crate::registry::AsRule; @@ -36,10 +36,7 @@ fn rule(name: &str, bases: &[Expr], scope: &Scope, bindings: &Bindings) -> Optio continue; } if !matches!( - scope - .bindings - .get(&id.as_str()) - .map(|index| &bindings[*index]), + scope.get(id.as_str()).map(|index| &bindings[*index]), None | Some(Binding { kind: BindingKind::Builtin, .. diff --git a/crates/ruff_python_ast/src/context.rs b/crates/ruff_python_ast/src/context.rs index 947d3b08a6b61..daf57e51caabf 100644 --- a/crates/ruff_python_ast/src/context.rs +++ b/crates/ruff_python_ast/src/context.rs @@ -1,4 +1,3 @@ -use std::ops::{Deref, Index, IndexMut}; use std::path::Path; use nohash_hasher::{BuildNoHashHasher, IntMap}; @@ -10,10 +9,11 @@ use ruff_python_stdlib::path::is_python_stub_file; use ruff_python_stdlib::typing::TYPING_EXTENSIONS; use crate::helpers::{collect_call_path, from_relative_import, Exceptions}; -use crate::types::{ - Binding, BindingId, BindingKind, CallPath, ExecutionContext, RefEquality, Scope, ScopeId, - ScopeKind, +use crate::scope::{ + Binding, BindingId, BindingKind, Bindings, ExecutionContext, Scope, ScopeId, ScopeKind, + ScopeStack, Scopes, }; +use crate::types::{CallPath, RefEquality}; use crate::visibility::{module_visibility, Modifier, VisibleScope}; #[allow(clippy::struct_excessive_bools)] @@ -125,7 +125,7 @@ impl<'a> Context<'a> { /// Return the current `Binding` for a given `name`. pub fn find_binding(&self, member: &str) -> Option<&Binding> { self.scopes() - .find_map(|scope| scope.bindings.get(member)) + .find_map(|scope| scope.get(member)) .map(|index| &self.bindings[*index]) } @@ -319,132 +319,3 @@ impl<'a> Context<'a> { } } } - -/// The scopes of a program indexed by [`ScopeId`] -#[derive(Debug)] -pub struct Scopes<'a>(Vec>); - -impl<'a> Scopes<'a> { - /// Returns a reference to the global scope - pub fn global(&self) -> &Scope<'a> { - &self[ScopeId::global()] - } - - /// Returns a mutable reference to the global scope - pub fn global_mut(&mut self) -> &mut Scope<'a> { - &mut self[ScopeId::global()] - } - - /// Pushes a new scope and returns its unique id - fn push_scope(&mut self, kind: ScopeKind<'a>) -> ScopeId { - let next_id = ScopeId::try_from(self.0.len()).unwrap(); - self.0.push(Scope::local(next_id, kind)); - next_id - } -} - -impl Default for Scopes<'_> { - fn default() -> Self { - Self(vec![Scope::global(ScopeKind::Module)]) - } -} - -impl<'a> Index for Scopes<'a> { - type Output = Scope<'a>; - - fn index(&self, index: ScopeId) -> &Self::Output { - &self.0[usize::from(index)] - } -} - -impl<'a> IndexMut for Scopes<'a> { - fn index_mut(&mut self, index: ScopeId) -> &mut Self::Output { - &mut self.0[usize::from(index)] - } -} - -impl<'a> Deref for Scopes<'a> { - type Target = [Scope<'a>]; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[derive(Debug, Clone)] -pub struct ScopeStack(Vec); - -impl ScopeStack { - /// Pushes a new scope on the stack - pub fn push(&mut self, id: ScopeId) { - self.0.push(id); - } - - /// Pops the top most scope - pub fn pop(&mut self) -> Option { - self.0.pop() - } - - /// Returns the id of the top-most - pub fn top(&self) -> Option { - self.0.last().copied() - } - - /// Returns an iterator from the current scope to the top scope (reverse iterator) - pub fn iter(&self) -> std::iter::Rev> { - self.0.iter().rev() - } -} - -impl Default for ScopeStack { - fn default() -> Self { - Self(vec![ScopeId::global()]) - } -} - -/// The bindings in a program. -/// -/// Bindings are indexed by [`BindingId`] -#[derive(Debug, Clone, Default)] -pub struct Bindings<'a>(Vec>); - -impl<'a> Bindings<'a> { - /// Pushes a new binding and returns its id - pub fn push(&mut self, binding: Binding<'a>) -> BindingId { - let id = self.next_id(); - self.0.push(binding); - id - } - - /// Returns the id that will be assigned when pushing the next binding - pub fn next_id(&self) -> BindingId { - BindingId::try_from(self.0.len()).unwrap() - } -} - -impl<'a> Index for Bindings<'a> { - type Output = Binding<'a>; - - fn index(&self, index: BindingId) -> &Self::Output { - &self.0[usize::from(index)] - } -} - -impl<'a> IndexMut for Bindings<'a> { - fn index_mut(&mut self, index: BindingId) -> &mut Self::Output { - &mut self.0[usize::from(index)] - } -} - -impl<'a> Deref for Bindings<'a> { - type Target = [Binding<'a>]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'a> FromIterator> for Bindings<'a> { - fn from_iter>>(iter: T) -> Self { - Self(Vec::from_iter(iter)) - } -} diff --git a/crates/ruff_python_ast/src/function_type.rs b/crates/ruff_python_ast/src/function_type.rs index d19b0882f3d42..79c80f4c327f8 100644 --- a/crates/ruff_python_ast/src/function_type.rs +++ b/crates/ruff_python_ast/src/function_type.rs @@ -2,7 +2,7 @@ use rustpython_parser::ast::Expr; use crate::context::Context; use crate::helpers::{map_callable, to_call_path}; -use crate::types::{Scope, ScopeKind}; +use crate::scope::{Scope, ScopeKind}; const CLASS_METHODS: [&str; 3] = ["__new__", "__init_subclass__", "__class_getitem__"]; const METACLASS_BASES: [(&str, &str); 2] = [("", "type"), ("abc", "ABCMeta")]; diff --git a/crates/ruff_python_ast/src/helpers.rs b/crates/ruff_python_ast/src/helpers.rs index 9070609d821aa..0c399fe6a72f0 100644 --- a/crates/ruff_python_ast/src/helpers.rs +++ b/crates/ruff_python_ast/src/helpers.rs @@ -15,8 +15,9 @@ use smallvec::{smallvec, SmallVec}; use crate::context::Context; use crate::newlines::StrExt; +use crate::scope::{Binding, BindingKind}; use crate::source_code::{Generator, Indexer, Locator, Stylist}; -use crate::types::{Binding, BindingKind, CallPath, Range}; +use crate::types::{CallPath, Range}; use crate::visitor; use crate::visitor::Visitor; diff --git a/crates/ruff_python_ast/src/lib.rs b/crates/ruff_python_ast/src/lib.rs index 2856716fbb361..afb3a925586f4 100644 --- a/crates/ruff_python_ast/src/lib.rs +++ b/crates/ruff_python_ast/src/lib.rs @@ -9,6 +9,7 @@ pub mod logging; pub mod newlines; pub mod operations; pub mod relocate; +pub mod scope; pub mod source_code; pub mod str; pub mod types; diff --git a/crates/ruff_python_ast/src/operations.rs b/crates/ruff_python_ast/src/operations.rs index beb8437aaefe6..86160b4d5f95e 100644 --- a/crates/ruff_python_ast/src/operations.rs +++ b/crates/ruff_python_ast/src/operations.rs @@ -5,7 +5,7 @@ use rustpython_parser::{lexer, Mode, Tok}; use crate::context::Context; use crate::helpers::any_over_expr; -use crate::types::{BindingKind, Scope}; +use crate::scope::{BindingKind, Scope}; use crate::visitor; use crate::visitor::Visitor; @@ -95,7 +95,7 @@ pub fn extract_all_names( // Grab the existing bound __all__ values. if let StmtKind::AugAssign { .. } = &stmt.node { - if let Some(index) = scope.bindings.get("__all__") { + if let Some(index) = scope.get("__all__") { if let BindingKind::Export(existing) = &ctx.bindings[*index].kind { names.extend_from_slice(existing); } diff --git a/crates/ruff_python_ast/src/scope.rs b/crates/ruff_python_ast/src/scope.rs new file mode 100644 index 0000000000000..e4f1a991f0121 --- /dev/null +++ b/crates/ruff_python_ast/src/scope.rs @@ -0,0 +1,424 @@ +use crate::types::{Range, RefEquality}; +use rustc_hash::FxHashMap; +use rustpython_parser::ast::{Arguments, Expr, Keyword, Stmt}; +use std::num::TryFromIntError; +use std::ops::{Deref, Index, IndexMut}; + +#[derive(Debug)] +pub struct Scope<'a> { + pub id: ScopeId, + pub kind: ScopeKind<'a>, + pub import_starred: bool, + pub uses_locals: bool, + /// A map from bound name to binding index, for live bindings. + bindings: FxHashMap<&'a str, BindingId>, + /// A map from bound name to binding index, for bindings that were created + /// in the scope but rebound (and thus overridden) later on in the same + /// scope. + pub rebounds: FxHashMap<&'a str, Vec>, +} + +impl<'a> Scope<'a> { + pub fn global() -> Self { + Scope::local(ScopeId::global(), ScopeKind::Module) + } + + pub fn local(id: ScopeId, kind: ScopeKind<'a>) -> Self { + Scope { + id, + kind, + import_starred: false, + uses_locals: false, + bindings: FxHashMap::default(), + rebounds: FxHashMap::default(), + } + } + + /// Returns the [id](BindingId) of the binding with the given name. + pub fn get(&self, name: &str) -> Option<&BindingId> { + self.bindings.get(name) + } + + /// Adds a new binding with the given name to this scope. + pub fn add(&mut self, name: &'a str, id: BindingId) -> Option { + self.bindings.insert(name, id) + } + + /// Returns `true` if this scope defines a binding with the given name. + pub fn defines(&self, name: &str) -> bool { + self.bindings.contains_key(name) + } + + /// Removes the binding with the given name + pub fn remove(&mut self, name: &str) -> Option { + self.bindings.remove(name) + } + + /// Returns the ids of all bindings defined in this scope. + pub fn binding_ids(&self) -> std::collections::hash_map::Values<&str, BindingId> { + self.bindings.values() + } + + pub fn bindings(&self) -> std::collections::hash_map::Iter<&'a str, BindingId> { + self.bindings.iter() + } +} + +/// Id uniquely identifying a scope in a program. +/// +/// Using a `u32` is sufficient because Ruff only supports parsing documents with a size of max `u32::max` +/// and it is impossible to have more scopes than characters in the file (because defining a function or class +/// requires more than one character). +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct ScopeId(u32); + +impl ScopeId { + /// Returns the ID for the global scope + #[inline] + pub const fn global() -> Self { + ScopeId(0) + } + + /// Returns `true` if this is the id of the global scope + pub const fn is_global(&self) -> bool { + self.0 == 0 + } +} + +impl TryFrom for ScopeId { + type Error = TryFromIntError; + + fn try_from(value: usize) -> Result { + Ok(Self(u32::try_from(value)?)) + } +} + +impl From for usize { + fn from(value: ScopeId) -> Self { + value.0 as usize + } +} + +#[derive(Debug)] +pub enum ScopeKind<'a> { + Class(ClassDef<'a>), + Function(FunctionDef<'a>), + Generator, + Module, + Lambda(Lambda<'a>), +} + +#[derive(Debug)] +pub struct FunctionDef<'a> { + // Properties derived from StmtKind::FunctionDef. + pub name: &'a str, + pub args: &'a Arguments, + pub body: &'a [Stmt], + pub decorator_list: &'a [Expr], + // pub returns: Option<&'a Expr>, + // pub type_comment: Option<&'a str>, + // Scope-specific properties. + // TODO(charlie): Create AsyncFunctionDef to mirror the AST. + pub async_: bool, + pub globals: FxHashMap<&'a str, &'a Stmt>, +} + +#[derive(Debug)] +pub struct ClassDef<'a> { + // Properties derived from StmtKind::ClassDef. + pub name: &'a str, + pub bases: &'a [Expr], + pub keywords: &'a [Keyword], + // pub body: &'a [Stmt], + pub decorator_list: &'a [Expr], + // Scope-specific properties. + pub globals: FxHashMap<&'a str, &'a Stmt>, +} + +#[derive(Debug)] +pub struct Lambda<'a> { + pub args: &'a Arguments, + pub body: &'a Expr, +} + +/// The scopes of a program indexed by [`ScopeId`] +#[derive(Debug)] +pub struct Scopes<'a>(Vec>); + +impl<'a> Scopes<'a> { + /// Returns a reference to the global scope + pub fn global(&self) -> &Scope<'a> { + &self[ScopeId::global()] + } + + /// Returns a mutable reference to the global scope + pub fn global_mut(&mut self) -> &mut Scope<'a> { + &mut self[ScopeId::global()] + } + + /// Pushes a new scope and returns its unique id + pub(crate) fn push_scope(&mut self, kind: ScopeKind<'a>) -> ScopeId { + let next_id = ScopeId::try_from(self.0.len()).unwrap(); + self.0.push(Scope::local(next_id, kind)); + next_id + } +} + +impl Default for Scopes<'_> { + fn default() -> Self { + Self(vec![Scope::global()]) + } +} + +impl<'a> Index for Scopes<'a> { + type Output = Scope<'a>; + + fn index(&self, index: ScopeId) -> &Self::Output { + &self.0[usize::from(index)] + } +} + +impl<'a> IndexMut for Scopes<'a> { + fn index_mut(&mut self, index: ScopeId) -> &mut Self::Output { + &mut self.0[usize::from(index)] + } +} + +impl<'a> Deref for Scopes<'a> { + type Target = [Scope<'a>]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Clone)] +pub struct ScopeStack(Vec); + +impl ScopeStack { + /// Pushes a new scope on the stack + pub fn push(&mut self, id: ScopeId) { + self.0.push(id); + } + + /// Pops the top most scope + pub fn pop(&mut self) -> Option { + self.0.pop() + } + + /// Returns the id of the top-most + pub fn top(&self) -> Option { + self.0.last().copied() + } + + /// Returns an iterator from the current scope to the top scope (reverse iterator) + pub fn iter(&self) -> std::iter::Rev> { + self.0.iter().rev() + } +} + +impl Default for ScopeStack { + fn default() -> Self { + Self(vec![ScopeId::global()]) + } +} + +#[derive(Debug, Clone)] +pub struct Binding<'a> { + pub kind: BindingKind<'a>, + pub range: Range, + /// The context in which the binding was created. + pub context: ExecutionContext, + /// The statement in which the [`Binding`] was defined. + pub source: Option>, + /// Tuple of (scope index, range) indicating the scope and range at which + /// the binding was last used in a runtime context. + pub runtime_usage: Option<(ScopeId, Range)>, + /// Tuple of (scope index, range) indicating the scope and range at which + /// the binding was last used in a typing-time context. + pub typing_usage: Option<(ScopeId, Range)>, + /// Tuple of (scope index, range) indicating the scope and range at which + /// the binding was last used in a synthetic context. This is used for + /// (e.g.) `__future__` imports, explicit re-exports, and other bindings + /// that should be considered used even if they're never referenced. + pub synthetic_usage: Option<(ScopeId, Range)>, +} + +impl<'a> Binding<'a> { + pub fn mark_used(&mut self, scope: ScopeId, range: Range, context: ExecutionContext) { + match context { + ExecutionContext::Runtime => self.runtime_usage = Some((scope, range)), + ExecutionContext::Typing => self.typing_usage = Some((scope, range)), + } + } + + pub const fn used(&self) -> bool { + self.runtime_usage.is_some() + || self.synthetic_usage.is_some() + || self.typing_usage.is_some() + } + + pub const fn is_definition(&self) -> bool { + matches!( + self.kind, + BindingKind::ClassDefinition + | BindingKind::FunctionDefinition + | BindingKind::Builtin + | BindingKind::FutureImportation + | BindingKind::StarImportation(..) + | BindingKind::Importation(..) + | BindingKind::FromImportation(..) + | BindingKind::SubmoduleImportation(..) + ) + } + + pub fn redefines(&self, existing: &'a Binding) -> bool { + match &self.kind { + BindingKind::Importation(.., full_name) => { + if let BindingKind::SubmoduleImportation(.., existing) = &existing.kind { + return full_name == existing; + } + } + BindingKind::FromImportation(.., full_name) => { + if let BindingKind::SubmoduleImportation(.., existing) = &existing.kind { + return full_name == existing; + } + } + BindingKind::SubmoduleImportation(.., full_name) => match &existing.kind { + BindingKind::Importation(.., existing) + | BindingKind::SubmoduleImportation(.., existing) => { + return full_name == existing; + } + BindingKind::FromImportation(.., existing) => { + return full_name == existing; + } + _ => {} + }, + BindingKind::Annotation => { + return false; + } + BindingKind::FutureImportation => { + return false; + } + BindingKind::StarImportation(..) => { + return false; + } + _ => {} + } + existing.is_definition() + } +} + +#[derive(Copy, Debug, Clone)] +pub enum ExecutionContext { + Runtime, + Typing, +} + +/// ID uniquely identifying a [Binding] in a program. +/// +/// Using a `u32` to identify [Binding]s should is sufficient because Ruff only supports documents with a +/// size smaller than or equal to `u32::max`. A document with the size of `u32::max` must have fewer than `u32::max` +/// bindings because bindings must be separated by whitespace (and have an assignment). +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct BindingId(u32); + +impl From for usize { + fn from(value: BindingId) -> Self { + value.0 as usize + } +} + +impl TryFrom for BindingId { + type Error = TryFromIntError; + + fn try_from(value: usize) -> Result { + Ok(Self(u32::try_from(value)?)) + } +} + +impl nohash_hasher::IsEnabled for BindingId {} + +// Pyflakes defines the following binding hierarchy (via inheritance): +// Binding +// ExportBinding +// Annotation +// Argument +// Assignment +// NamedExprAssignment +// Definition +// FunctionDefinition +// ClassDefinition +// Builtin +// Importation +// SubmoduleImportation +// ImportationFrom +// StarImportation +// FutureImportation + +#[derive(Clone, Debug, is_macro::Is)] +pub enum BindingKind<'a> { + Annotation, + Argument, + Assignment, + Binding, + LoopVar, + Global, + Nonlocal, + Builtin, + ClassDefinition, + FunctionDefinition, + Export(Vec), + FutureImportation, + StarImportation(Option, Option), + Importation(&'a str, &'a str), + FromImportation(&'a str, String), + SubmoduleImportation(&'a str, &'a str), +} + +/// The bindings in a program. +/// +/// Bindings are indexed by [`BindingId`] +#[derive(Debug, Clone, Default)] +pub struct Bindings<'a>(Vec>); + +impl<'a> Bindings<'a> { + /// Pushes a new binding and returns its id + pub fn push(&mut self, binding: Binding<'a>) -> BindingId { + let id = self.next_id(); + self.0.push(binding); + id + } + + /// Returns the id that will be assigned when pushing the next binding + pub fn next_id(&self) -> BindingId { + BindingId::try_from(self.0.len()).unwrap() + } +} + +impl<'a> Index for Bindings<'a> { + type Output = Binding<'a>; + + fn index(&self, index: BindingId) -> &Self::Output { + &self.0[usize::from(index)] + } +} + +impl<'a> IndexMut for Bindings<'a> { + fn index_mut(&mut self, index: BindingId) -> &mut Self::Output { + &mut self.0[usize::from(index)] + } +} + +impl<'a> Deref for Bindings<'a> { + type Target = [Binding<'a>]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> FromIterator> for Bindings<'a> { + fn from_iter>>(iter: T) -> Self { + Self(Vec::from_iter(iter)) + } +} diff --git a/crates/ruff_python_ast/src/types.rs b/crates/ruff_python_ast/src/types.rs index 1c27a7bf581d3..19c020d7d7c7b 100644 --- a/crates/ruff_python_ast/src/types.rs +++ b/crates/ruff_python_ast/src/types.rs @@ -1,8 +1,6 @@ -use std::num::TryFromIntError; use std::ops::Deref; -use rustc_hash::FxHashMap; -use rustpython_parser::ast::{Arguments, Expr, Keyword, Located, Location, Stmt}; +use rustpython_parser::ast::{Expr, Located, Location, Stmt}; #[derive(Clone)] pub enum Node<'a> { @@ -37,267 +35,6 @@ impl From<&Box>> for Range { } } -#[derive(Debug)] -pub struct FunctionDef<'a> { - // Properties derived from StmtKind::FunctionDef. - pub name: &'a str, - pub args: &'a Arguments, - pub body: &'a [Stmt], - pub decorator_list: &'a [Expr], - // pub returns: Option<&'a Expr>, - // pub type_comment: Option<&'a str>, - // Scope-specific properties. - // TODO(charlie): Create AsyncFunctionDef to mirror the AST. - pub async_: bool, - pub globals: FxHashMap<&'a str, &'a Stmt>, -} - -#[derive(Debug)] -pub struct ClassDef<'a> { - // Properties derived from StmtKind::ClassDef. - pub name: &'a str, - pub bases: &'a [Expr], - pub keywords: &'a [Keyword], - // pub body: &'a [Stmt], - pub decorator_list: &'a [Expr], - // Scope-specific properties. - pub globals: FxHashMap<&'a str, &'a Stmt>, -} - -#[derive(Debug)] -pub struct Lambda<'a> { - pub args: &'a Arguments, - pub body: &'a Expr, -} - -#[derive(Debug)] -pub enum ScopeKind<'a> { - Class(ClassDef<'a>), - Function(FunctionDef<'a>), - Generator, - Module, - Lambda(Lambda<'a>), -} - -/// Id uniquely identifying a scope in a program. -/// -/// Using a `u32` is sufficient because Ruff only supports parsing documents with a size of max `u32::max` -/// and it is impossible to have more scopes than characters in the file (because defining a function or class -/// requires more than one character). -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub struct ScopeId(u32); - -impl ScopeId { - /// Returns the ID for the global scope - #[inline] - pub const fn global() -> Self { - ScopeId(0) - } - - /// Returns `true` if this is the id of the global scope - pub const fn is_global(&self) -> bool { - self.0 == 0 - } -} - -impl TryFrom for ScopeId { - type Error = TryFromIntError; - - fn try_from(value: usize) -> Result { - Ok(Self(u32::try_from(value)?)) - } -} - -impl From for usize { - fn from(value: ScopeId) -> Self { - value.0 as usize - } -} - -#[derive(Debug)] -pub struct Scope<'a> { - pub id: ScopeId, - pub kind: ScopeKind<'a>, - pub import_starred: bool, - pub uses_locals: bool, - /// A map from bound name to binding index, for live bindings. - pub bindings: FxHashMap<&'a str, BindingId>, - /// A map from bound name to binding index, for bindings that were created - /// in the scope but rebound (and thus overridden) later on in the same - /// scope. - pub rebounds: FxHashMap<&'a str, Vec>, -} - -impl<'a> Scope<'a> { - pub fn global(kind: ScopeKind<'a>) -> Self { - Self::local(ScopeId::global(), kind) - } - - pub fn local(id: ScopeId, kind: ScopeKind<'a>) -> Self { - Scope { - id, - kind, - import_starred: false, - uses_locals: false, - bindings: FxHashMap::default(), - rebounds: FxHashMap::default(), - } - } -} - -// Pyflakes defines the following binding hierarchy (via inheritance): -// Binding -// ExportBinding -// Annotation -// Argument -// Assignment -// NamedExprAssignment -// Definition -// FunctionDefinition -// ClassDefinition -// Builtin -// Importation -// SubmoduleImportation -// ImportationFrom -// StarImportation -// FutureImportation - -#[derive(Clone, Debug, is_macro::Is)] -pub enum BindingKind<'a> { - Annotation, - Argument, - Assignment, - Binding, - LoopVar, - Global, - Nonlocal, - Builtin, - ClassDefinition, - FunctionDefinition, - Export(Vec), - FutureImportation, - StarImportation(Option, Option), - Importation(&'a str, &'a str), - FromImportation(&'a str, String), - SubmoduleImportation(&'a str, &'a str), -} - -/// ID uniquely identifying a [Binding] in a program. -/// -/// Using a `u32` to identify [Binding]s should is sufficient because Ruff only supports documents with a -/// size smaller than or equal to `u32::max`. A document with the size of `u32::max` must have fewer than `u32::max` -/// bindings because bindings must be separated by whitespace (and have an assignment). -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct BindingId(u32); - -impl From for usize { - fn from(value: BindingId) -> Self { - value.0 as usize - } -} - -impl TryFrom for BindingId { - type Error = TryFromIntError; - - fn try_from(value: usize) -> Result { - Ok(Self(u32::try_from(value)?)) - } -} - -impl nohash_hasher::IsEnabled for BindingId {} - -#[derive(Debug, Clone)] -pub struct Binding<'a> { - pub kind: BindingKind<'a>, - pub range: Range, - /// The context in which the binding was created. - pub context: ExecutionContext, - /// The statement in which the [`Binding`] was defined. - pub source: Option>, - /// Tuple of (scope index, range) indicating the scope and range at which - /// the binding was last used in a runtime context. - pub runtime_usage: Option<(ScopeId, Range)>, - /// Tuple of (scope index, range) indicating the scope and range at which - /// the binding was last used in a typing-time context. - pub typing_usage: Option<(ScopeId, Range)>, - /// Tuple of (scope index, range) indicating the scope and range at which - /// the binding was last used in a synthetic context. This is used for - /// (e.g.) `__future__` imports, explicit re-exports, and other bindings - /// that should be considered used even if they're never referenced. - pub synthetic_usage: Option<(ScopeId, Range)>, -} - -#[derive(Copy, Debug, Clone)] -pub enum ExecutionContext { - Runtime, - Typing, -} - -impl<'a> Binding<'a> { - pub fn mark_used(&mut self, scope: ScopeId, range: Range, context: ExecutionContext) { - match context { - ExecutionContext::Runtime => self.runtime_usage = Some((scope, range)), - ExecutionContext::Typing => self.typing_usage = Some((scope, range)), - } - } - - pub const fn used(&self) -> bool { - self.runtime_usage.is_some() - || self.synthetic_usage.is_some() - || self.typing_usage.is_some() - } - - pub const fn is_definition(&self) -> bool { - matches!( - self.kind, - BindingKind::ClassDefinition - | BindingKind::FunctionDefinition - | BindingKind::Builtin - | BindingKind::FutureImportation - | BindingKind::StarImportation(..) - | BindingKind::Importation(..) - | BindingKind::FromImportation(..) - | BindingKind::SubmoduleImportation(..) - ) - } - - pub fn redefines(&self, existing: &'a Binding) -> bool { - match &self.kind { - BindingKind::Importation(.., full_name) => { - if let BindingKind::SubmoduleImportation(.., existing) = &existing.kind { - return full_name == existing; - } - } - BindingKind::FromImportation(.., full_name) => { - if let BindingKind::SubmoduleImportation(.., existing) = &existing.kind { - return full_name == existing; - } - } - BindingKind::SubmoduleImportation(.., full_name) => match &existing.kind { - BindingKind::Importation(.., existing) - | BindingKind::SubmoduleImportation(.., existing) => { - return full_name == existing; - } - BindingKind::FromImportation(.., existing) => { - return full_name == existing; - } - _ => {} - }, - BindingKind::Annotation => { - return false; - } - BindingKind::FutureImportation => { - return false; - } - BindingKind::StarImportation(..) => { - return false; - } - _ => {} - } - existing.is_definition() - } -} - #[derive(Debug, Copy, Clone)] pub struct RefEquality<'a, T>(pub &'a T); From 2e21920adfd03c82733e7073b9f84b7c1b35074a Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 17 Mar 2023 14:29:05 -0400 Subject: [PATCH 41/49] Respect `type` overrides in E721 (#3582) --- .../test/fixtures/pycodestyle/E721.py | 4 +++ crates/ruff/src/checkers/ast/mod.rs | 6 +--- .../pycodestyle/rules/type_comparison.rs | 30 ++++++++++++------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/pycodestyle/E721.py b/crates/ruff/resources/test/fixtures/pycodestyle/E721.py index acb92fab29c9f..a220f58c3d8e4 100644 --- a/crates/ruff/resources/test/fixtures/pycodestyle/E721.py +++ b/crates/ruff/resources/test/fixtures/pycodestyle/E721.py @@ -54,3 +54,7 @@ pass assert type(res) == type(None) + +types = StrEnum +if x == types.X: + pass diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 72b5d101bcbf8..5c4bff9c642fb 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -3186,11 +3186,7 @@ where } if self.settings.rules.enabled(Rule::TypeComparison) { - self.diagnostics.extend(pycodestyle::rules::type_comparison( - ops, - comparators, - Range::from(expr), - )); + pycodestyle::rules::type_comparison(self, expr, ops, comparators); } if self.settings.rules.enabled(Rule::SysVersionCmpStr3) diff --git a/crates/ruff/src/rules/pycodestyle/rules/type_comparison.rs b/crates/ruff/src/rules/pycodestyle/rules/type_comparison.rs index fde3b00735cdf..3109617e4f3f9 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/type_comparison.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/type_comparison.rs @@ -1,6 +1,7 @@ use itertools::izip; use rustpython_parser::ast::{Cmpop, Constant, Expr, ExprKind}; +use crate::checkers::ast::Checker; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::types::Range; @@ -34,9 +35,7 @@ impl Violation for TypeComparison { } /// E721 -pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) -> Vec { - let mut diagnostics: Vec = vec![]; - +pub fn type_comparison(checker: &mut Checker, expr: &Expr, ops: &[Cmpop], comparators: &[Expr]) { for (op, right) in izip!(ops, comparators) { if !matches!(op, Cmpop::Is | Cmpop::IsNot | Cmpop::Eq | Cmpop::NotEq) { continue; @@ -44,8 +43,8 @@ pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) -> match &right.node { ExprKind::Call { func, args, .. } => { if let ExprKind::Name { id, .. } = &func.node { - // Ex) type(False) - if id == "type" { + // Ex) `type(False)` + if id == "type" && checker.ctx.is_builtin("type") { if let Some(arg) = args.first() { // Allow comparison for types which are not obvious. if !matches!( @@ -56,7 +55,9 @@ pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) -> kind: None } ) { - diagnostics.push(Diagnostic::new(TypeComparison, location)); + checker + .diagnostics + .push(Diagnostic::new(TypeComparison, Range::from(expr))); } } } @@ -64,15 +65,22 @@ pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) -> } ExprKind::Attribute { value, .. } => { if let ExprKind::Name { id, .. } = &value.node { - // Ex) types.IntType - if id == "types" { - diagnostics.push(Diagnostic::new(TypeComparison, location)); + // Ex) `types.NoneType` + if id == "types" + && checker + .ctx + .resolve_call_path(value) + .map_or(false, |call_path| { + call_path.first().map_or(false, |module| *module == "types") + }) + { + checker + .diagnostics + .push(Diagnostic::new(TypeComparison, Range::from(expr))); } } } _ => {} } } - - diagnostics } From 87fab4a2e179d398ae23129962c6ec9d61dc824a Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 17 Mar 2023 19:29:39 +0100 Subject: [PATCH 42/49] Benchmark all rules (#3570) --- Cargo.lock | 3 ++ crates/ruff_benchmark/Cargo.toml | 3 ++ crates/ruff_benchmark/benches/linter.rs | 47 ++++++++++++++++++------- crates/ruff_benchmark/src/lib.rs | 29 +++++++++++++-- 4 files changed, 67 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a8e7bbc9bbd7..bea0f1351772b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2041,7 +2041,10 @@ version = "0.0.0" dependencies = [ "criterion", "mimalloc", + "once_cell", "ruff", + "serde", + "serde_json", "tikv-jemallocator", "ureq", "url", diff --git a/crates/ruff_benchmark/Cargo.toml b/crates/ruff_benchmark/Cargo.toml index 0d4899b11e1e4..50d414281cebf 100644 --- a/crates/ruff_benchmark/Cargo.toml +++ b/crates/ruff_benchmark/Cargo.toml @@ -17,7 +17,10 @@ name = "linter" harness = false [dependencies] +once_cell.workspace = true ruff.path = "../ruff" +serde.workspace = true +serde_json.workspace = true url = "2.3.1" ureq = "2.6.2" diff --git a/crates/ruff_benchmark/benches/linter.rs b/crates/ruff_benchmark/benches/linter.rs index c0777b6e6cdf0..5c34b2eae05ef 100644 --- a/crates/ruff_benchmark/benches/linter.rs +++ b/crates/ruff_benchmark/benches/linter.rs @@ -1,6 +1,10 @@ -use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use criterion::measurement::WallTime; +use criterion::{ + criterion_group, criterion_main, BenchmarkGroup, BenchmarkId, Criterion, Throughput, +}; use ruff::linter::lint_only; use ruff::settings::{flags, Settings}; +use ruff::RuleSelector; use ruff_benchmark::{TestCase, TestCaseSpeed, TestFile, TestFileDownloadError}; use std::time::Duration; @@ -22,12 +26,12 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; fn create_test_cases() -> Result, TestFileDownloadError> { Ok(vec![ - TestCase::fast(TestFile::try_download("numpy/globals.py", "https://github.com/numpy/numpy/blob/89d64415e349ca75a25250f22b874aa16e5c0973/numpy/_globals.py")?), + TestCase::fast(TestFile::try_download("numpy/globals.py", "https://raw.githubusercontent.com/numpy/numpy/89d64415e349ca75a25250f22b874aa16e5c0973/numpy/_globals.py")?), TestCase::normal(TestFile::try_download( "pydantic/types.py", - "https://raw.githubusercontent.com/pydantic/pydantic/main/pydantic/types.py", + "https://raw.githubusercontent.com/pydantic/pydantic/83b3c49e99ceb4599d9286a3d793cea44ac36d4b/pydantic/types.py", )?), - TestCase::normal(TestFile::try_download("numpy/ctypeslib.py", "https://github.com/numpy/numpy/blob/main/numpy/ctypeslib.py")?), + TestCase::normal(TestFile::try_download("numpy/ctypeslib.py", "https://raw.githubusercontent.com/numpy/numpy/e42c9503a14d66adfd41356ef5640c6975c45218/numpy/ctypeslib.py")?), TestCase::slow(TestFile::try_download( "large/dataset.py", "https://raw.githubusercontent.com/DHI/mikeio/b7d26418f4db2909b0aa965253dbe83194d7bb5b/tests/test_dataset.py", @@ -35,30 +39,33 @@ fn create_test_cases() -> Result, TestFileDownloadError> { ]) } -fn benchmark_linter(criterion: &mut Criterion) { +fn benchmark_linter(mut group: BenchmarkGroup, settings: &Settings) { let test_cases = create_test_cases().unwrap(); - let mut group = criterion.benchmark_group("linter"); for case in test_cases { group.throughput(Throughput::Bytes(case.code().len() as u64)); group.measurement_time(match case.speed() { TestCaseSpeed::Fast => Duration::from_secs(10), TestCaseSpeed::Normal => Duration::from_secs(20), - TestCaseSpeed::Slow => Duration::from_secs(30), + TestCaseSpeed::Slow => Duration::from_secs(45), }); + group.bench_with_input( BenchmarkId::from_parameter(case.name()), &case, |b, case| { b.iter(|| { - lint_only( + let result = lint_only( case.code(), &case.path(), None, - &black_box(Settings::default()), + settings, flags::Noqa::Enabled, flags::Autofix::Enabled, - ) + ); + + // Assert that file contains no parse errors + assert_eq!(result.error, None); }); }, ); @@ -67,5 +74,21 @@ fn benchmark_linter(criterion: &mut Criterion) { group.finish(); } -criterion_group!(benches, benchmark_linter); -criterion_main!(benches); +fn benchmark_default_rules(criterion: &mut Criterion) { + let group = criterion.benchmark_group("linter/default-rules"); + benchmark_linter(group, &Settings::default()); +} + +fn benchmark_all_rules(criterion: &mut Criterion) { + let settings = Settings { + rules: RuleSelector::All.into_iter().into(), + ..Settings::default() + }; + + let group = criterion.benchmark_group("linter/all-rules"); + benchmark_linter(group, &settings); +} + +criterion_group!(default_rules, benchmark_default_rules); +criterion_group!(all_rules, benchmark_all_rules); +criterion_main!(default_rules, all_rules); diff --git a/crates/ruff_benchmark/src/lib.rs b/crates/ruff_benchmark/src/lib.rs index af7eb91ae961f..6ea1fcabe160c 100644 --- a/crates/ruff_benchmark/src/lib.rs +++ b/crates/ruff_benchmark/src/lib.rs @@ -1,5 +1,6 @@ use std::fmt::{Display, Formatter}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; +use std::process::Command; use url::Url; /// Relative size of a test case. Benchmarks can use it to configure the time for how long a benchmark should run to get stable results. @@ -58,7 +59,7 @@ impl TestCase { } pub fn path(&self) -> PathBuf { - Path::new("target").join(self.name()) + TARGET_DIR.join(self.name()) } } @@ -68,6 +69,28 @@ pub struct TestFile { code: String, } +static TARGET_DIR: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { + cargo_target_directory().unwrap_or_else(|| PathBuf::from("target")) +}); + +fn cargo_target_directory() -> Option { + #[derive(serde::Deserialize)] + struct Metadata { + target_directory: PathBuf, + } + + std::env::var_os("CARGO_TARGET_DIR") + .map(PathBuf::from) + .or_else(|| { + let output = Command::new(std::env::var_os("CARGO")?) + .args(["metadata", "--format-version", "1"]) + .output() + .ok()?; + let metadata: Metadata = serde_json::from_slice(&output.stdout).ok()?; + Some(metadata.target_directory) + }) +} + impl TestFile { pub fn new(name: String, code: String) -> Self { Self { name, code } @@ -77,7 +100,7 @@ impl TestFile { pub fn try_download(name: &str, url: &str) -> Result { let url = Url::parse(url)?; - let cached_filename = Path::new("target").join(name); + let cached_filename = TARGET_DIR.join(name); if let Ok(content) = std::fs::read_to_string(&cached_filename) { Ok(TestFile::new(name.to_string(), content)) From babd0a05acaf347243d2ba9527e22d6f0021f123 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 17 Mar 2023 14:40:32 -0400 Subject: [PATCH 43/49] Avoid adding dashed line outside of docstring (#3581) --- .../test/fixtures/pydocstyle/sections.py | 9 + .../src/rules/pydocstyle/rules/sections.rs | 22 +- ...__pydocstyle__tests__D214_sections.py.snap | 8 +- ...__pydocstyle__tests__D215_sections.py.snap | 16 +- ...__pydocstyle__tests__D405_sections.py.snap | 8 +- ...__pydocstyle__tests__D406_sections.py.snap | 8 +- ...__pydocstyle__tests__D407_sections.py.snap | 236 ++++++++++-------- ...__pydocstyle__tests__D408_sections.py.snap | 8 +- ...__pydocstyle__tests__D409_sections.py.snap | 16 +- ...__pydocstyle__tests__D410_sections.py.snap | 24 +- ...__pydocstyle__tests__D411_sections.py.snap | 24 +- ...__pydocstyle__tests__D412_sections.py.snap | 8 +- ...__pydocstyle__tests__D413_sections.py.snap | 23 +- ...__pydocstyle__tests__D414_sections.py.snap | 27 +- ...__pydocstyle__tests__D417_sections.py.snap | 44 ++-- 15 files changed, 271 insertions(+), 210 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/pydocstyle/sections.py b/crates/ruff/resources/test/fixtures/pydocstyle/sections.py index 419dacd958779..cf31200aaf2e2 100644 --- a/crates/ruff/resources/test/fixtures/pydocstyle/sections.py +++ b/crates/ruff/resources/test/fixtures/pydocstyle/sections.py @@ -58,6 +58,15 @@ def no_underline_and_no_description(): # noqa: D416 """ +@expect(_D213) +@expect("D407: Missing dashed underline after section ('Returns')") +@expect("D414: Section has no content ('Returns')") +def no_underline_and_no_newline(): # noqa: D416 + """Toggle the gizmo. + + Returns""" + + @expect(_D213) @expect("D410: Missing blank line after section ('Returns')") @expect("D414: Section has no content ('Returns')") diff --git a/crates/ruff/src/rules/pydocstyle/rules/sections.rs b/crates/ruff/src/rules/pydocstyle/rules/sections.rs index 80d359b59d455..8c5158b78bd7c 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/sections.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/sections.rs @@ -365,15 +365,15 @@ fn blanks_and_section_underline( // Add a dashed line (of the appropriate length) under the section header. let content = format!( "{}{}{}", + checker.stylist.line_ending().as_str(), whitespace::clean(docstring.indentation), "-".repeat(context.section_name.len()), - checker.stylist.line_ending().as_str() ); diagnostic.amend(Fix::insertion( content, Location::new( - docstring.expr.location.row() + context.original_index + 1, - 0, + docstring.expr.location.row() + context.original_index, + context.line.trim_end().chars().count(), ), )); } @@ -595,15 +595,15 @@ fn blanks_and_section_underline( // Add a dashed line (of the appropriate length) under the section header. let content = format!( "{}{}{}", + checker.stylist.line_ending().as_str(), whitespace::clean(docstring.indentation), "-".repeat(context.section_name.len()), - checker.stylist.line_ending().as_str() ); diagnostic.amend(Fix::insertion( content, Location::new( - docstring.expr.location.row() + context.original_index + 1, - 0, + docstring.expr.location.row() + context.original_index, + context.line.trim_end().chars().count(), ), )); } @@ -721,14 +721,14 @@ fn common_section(checker: &mut Checker, docstring: &Docstring, context: &Sectio ); if checker.patch(diagnostic.kind.rule()) { // Add a newline after the section. + let line = context.following_lines.last().unwrap_or(&context.line); diagnostic.amend(Fix::insertion( - line_end.to_string(), + format!("{}{}", line_end, docstring.indentation), Location::new( docstring.expr.location.row() + context.original_index - + 1 + context.following_lines.len(), - 0, + line.trim_end().chars().count(), ), )); } @@ -744,14 +744,14 @@ fn common_section(checker: &mut Checker, docstring: &Docstring, context: &Sectio ); if checker.patch(diagnostic.kind.rule()) { // Add a newline after the section. + let line = context.following_lines.last().unwrap_or(&context.line); diagnostic.amend(Fix::insertion( line_end.to_string(), Location::new( docstring.expr.location.row() + context.original_index - + 1 + context.following_lines.len(), - 0, + line.trim_end().chars().count(), ), )); } diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D214_sections.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D214_sections.py.snap index 8dcab2b97fd7a..3012cf86b2570 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D214_sections.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D214_sections.py.snap @@ -8,18 +8,18 @@ expression: diagnostics suggestion: "Remove over-indentation from \"Returns\"" fixable: true location: - row: 135 + row: 144 column: 4 end_location: - row: 141 + row: 150 column: 7 fix: content: " " location: - row: 137 + row: 146 column: 0 end_location: - row: 137 + row: 146 column: 8 parent: ~ diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D215_sections.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D215_sections.py.snap index 400dbe7e934ff..3a6759ec23bd7 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D215_sections.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D215_sections.py.snap @@ -8,18 +8,18 @@ expression: diagnostics suggestion: "Remove over-indentation from \"Returns\" underline" fixable: true location: - row: 147 + row: 156 column: 4 end_location: - row: 153 + row: 162 column: 7 fix: content: " " location: - row: 150 + row: 159 column: 0 end_location: - row: 150 + row: 159 column: 9 parent: ~ - kind: @@ -28,18 +28,18 @@ expression: diagnostics suggestion: "Remove over-indentation from \"Returns\" underline" fixable: true location: - row: 161 + row: 170 column: 4 end_location: - row: 165 + row: 174 column: 7 fix: content: " " location: - row: 164 + row: 173 column: 0 end_location: - row: 164 + row: 173 column: 9 parent: ~ diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D405_sections.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D405_sections.py.snap index 3d4224a17d0f8..253d2154bbbaa 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D405_sections.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D405_sections.py.snap @@ -28,18 +28,18 @@ expression: diagnostics suggestion: "Capitalize \"Short summary\"" fixable: true location: - row: 207 + row: 216 column: 4 end_location: - row: 221 + row: 230 column: 7 fix: content: Short Summary location: - row: 209 + row: 218 column: 4 end_location: - row: 209 + row: 218 column: 17 parent: ~ diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D406_sections.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D406_sections.py.snap index 481d691e17953..7eabee0ae83be 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D406_sections.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D406_sections.py.snap @@ -28,18 +28,18 @@ expression: diagnostics suggestion: "Add newline after \"Raises\"" fixable: true location: - row: 207 + row: 216 column: 4 end_location: - row: 221 + row: 230 column: 7 fix: content: "" location: - row: 218 + row: 227 column: 10 end_location: - row: 218 + row: 227 column: 11 parent: ~ diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D407_sections.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D407_sections.py.snap index 3bd6dcca59bb3..3068e5c7436fd 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D407_sections.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D407_sections.py.snap @@ -14,13 +14,13 @@ expression: diagnostics row: 47 column: 7 fix: - content: " -------\n" + content: "\n -------" location: - row: 45 - column: 0 + row: 44 + column: 11 end_location: - row: 45 - column: 0 + row: 44 + column: 11 parent: ~ - kind: name: DashedUnderlineAfterSection @@ -34,13 +34,33 @@ expression: diagnostics row: 58 column: 7 fix: - content: " -------\n" + content: "\n -------" location: - row: 57 - column: 0 + row: 56 + column: 11 end_location: - row: 57 - column: 0 + row: 56 + column: 11 + parent: ~ +- kind: + name: DashedUnderlineAfterSection + body: "Missing dashed underline after section (\"Returns\")" + suggestion: "Add dashed line under \"Returns\"" + fixable: true + location: + row: 65 + column: 4 + end_location: + row: 67 + column: 14 + fix: + content: "\n -------" + location: + row: 67 + column: 11 + end_location: + row: 67 + column: 11 parent: ~ - kind: name: DashedUnderlineAfterSection @@ -48,19 +68,19 @@ expression: diagnostics suggestion: "Add dashed line under \"Raises\"" fixable: true location: - row: 207 + row: 216 column: 4 end_location: - row: 221 + row: 230 column: 7 fix: - content: " ------\n" + content: "\n ------" location: - row: 219 - column: 0 + row: 227 + column: 11 end_location: - row: 219 - column: 0 + row: 227 + column: 11 parent: ~ - kind: name: DashedUnderlineAfterSection @@ -68,19 +88,19 @@ expression: diagnostics suggestion: "Add dashed line under \"Args\"" fixable: true location: - row: 252 + row: 261 column: 4 end_location: - row: 262 + row: 271 column: 7 fix: - content: " ----\n" + content: "\n ----" location: - row: 255 - column: 0 + row: 263 + column: 9 end_location: - row: 255 - column: 0 + row: 263 + column: 9 parent: ~ - kind: name: DashedUnderlineAfterSection @@ -88,19 +108,19 @@ expression: diagnostics suggestion: "Add dashed line under \"Returns\"" fixable: true location: - row: 252 + row: 261 column: 4 end_location: - row: 262 + row: 271 column: 7 fix: - content: " -------\n" + content: "\n -------" location: - row: 258 - column: 0 + row: 266 + column: 12 end_location: - row: 258 - column: 0 + row: 266 + column: 12 parent: ~ - kind: name: DashedUnderlineAfterSection @@ -108,19 +128,19 @@ expression: diagnostics suggestion: "Add dashed line under \"Raises\"" fixable: true location: - row: 252 + row: 261 column: 4 end_location: - row: 262 + row: 271 column: 7 fix: - content: " ------\n" + content: "\n ------" location: - row: 260 - column: 0 + row: 268 + column: 11 end_location: - row: 260 - column: 0 + row: 268 + column: 11 parent: ~ - kind: name: DashedUnderlineAfterSection @@ -128,19 +148,19 @@ expression: diagnostics suggestion: "Add dashed line under \"Args\"" fixable: true location: - row: 269 + row: 278 column: 4 end_location: - row: 274 + row: 283 column: 7 fix: - content: " ----\n" + content: "\n ----" location: - row: 272 - column: 0 + row: 280 + column: 8 end_location: - row: 272 - column: 0 + row: 280 + column: 8 parent: ~ - kind: name: DashedUnderlineAfterSection @@ -148,19 +168,19 @@ expression: diagnostics suggestion: "Add dashed line under \"Args\"" fixable: true location: - row: 284 + row: 293 column: 8 end_location: - row: 292 + row: 301 column: 11 fix: - content: " ----\n" + content: "\n ----" location: - row: 289 - column: 0 + row: 297 + column: 13 end_location: - row: 289 - column: 0 + row: 297 + column: 13 parent: ~ - kind: name: DashedUnderlineAfterSection @@ -168,19 +188,19 @@ expression: diagnostics suggestion: "Add dashed line under \"Args\"" fixable: true location: - row: 301 + row: 310 column: 4 end_location: - row: 306 + row: 315 column: 7 fix: - content: " ----\n" + content: "\n ----" location: - row: 304 - column: 0 + row: 312 + column: 9 end_location: - row: 304 - column: 0 + row: 312 + column: 9 parent: ~ - kind: name: DashedUnderlineAfterSection @@ -188,19 +208,19 @@ expression: diagnostics suggestion: "Add dashed line under \"Args\"" fixable: true location: - row: 313 + row: 322 column: 8 end_location: - row: 319 + row: 328 column: 11 fix: - content: " ----\n" + content: "\n ----" location: - row: 316 - column: 0 + row: 324 + column: 13 end_location: - row: 316 - column: 0 + row: 324 + column: 13 parent: ~ - kind: name: DashedUnderlineAfterSection @@ -208,19 +228,19 @@ expression: diagnostics suggestion: "Add dashed line under \"Args\"" fixable: true location: - row: 325 + row: 334 column: 8 end_location: - row: 330 + row: 339 column: 11 fix: - content: " ----\n" + content: "\n ----" location: - row: 328 - column: 0 + row: 336 + column: 13 end_location: - row: 328 - column: 0 + row: 336 + column: 13 parent: ~ - kind: name: DashedUnderlineAfterSection @@ -228,19 +248,19 @@ expression: diagnostics suggestion: "Add dashed line under \"Args\"" fixable: true location: - row: 337 + row: 346 column: 8 end_location: - row: 343 + row: 352 column: 11 fix: - content: " ----\n" + content: "\n ----" location: - row: 340 - column: 0 + row: 348 + column: 13 end_location: - row: 340 - column: 0 + row: 348 + column: 13 parent: ~ - kind: name: DashedUnderlineAfterSection @@ -248,19 +268,19 @@ expression: diagnostics suggestion: "Add dashed line under \"Args\"" fixable: true location: - row: 350 + row: 359 column: 8 end_location: - row: 355 + row: 364 column: 11 fix: - content: " ----\n" + content: "\n ----" location: - row: 353 - column: 0 + row: 361 + column: 13 end_location: - row: 353 - column: 0 + row: 361 + column: 13 parent: ~ - kind: name: DashedUnderlineAfterSection @@ -268,19 +288,19 @@ expression: diagnostics suggestion: "Add dashed line under \"Args\"" fixable: true location: - row: 362 + row: 371 column: 8 end_location: - row: 367 + row: 376 column: 11 fix: - content: " ----\n" + content: "\n ----" location: - row: 365 - column: 0 + row: 373 + column: 13 end_location: - row: 365 - column: 0 + row: 373 + column: 13 parent: ~ - kind: name: DashedUnderlineAfterSection @@ -288,19 +308,19 @@ expression: diagnostics suggestion: "Add dashed line under \"Args\"" fixable: true location: - row: 371 + row: 380 column: 8 end_location: - row: 382 + row: 391 column: 11 fix: - content: " ----\n" + content: "\n ----" location: - row: 374 - column: 0 + row: 382 + column: 13 end_location: - row: 374 - column: 0 + row: 382 + column: 13 parent: ~ - kind: name: DashedUnderlineAfterSection @@ -308,18 +328,18 @@ expression: diagnostics suggestion: "Add dashed line under \"Args\"" fixable: true location: - row: 490 + row: 499 column: 8 end_location: - row: 497 + row: 506 column: 11 fix: - content: " ----\n" + content: "\n ----" location: - row: 495 - column: 0 + row: 503 + column: 13 end_location: - row: 495 - column: 0 + row: 503 + column: 13 parent: ~ diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D408_sections.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D408_sections.py.snap index 5b22ddd978b5b..9cfc4904360ab 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D408_sections.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D408_sections.py.snap @@ -8,18 +8,18 @@ expression: diagnostics suggestion: "Add underline to \"Returns\"" fixable: true location: - row: 85 + row: 94 column: 4 end_location: - row: 92 + row: 101 column: 7 fix: content: "" location: - row: 88 + row: 97 column: 0 end_location: - row: 89 + row: 98 column: 0 parent: ~ diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D409_sections.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D409_sections.py.snap index 7911488856507..5e5cb0560c02b 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D409_sections.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D409_sections.py.snap @@ -8,18 +8,18 @@ expression: diagnostics suggestion: "Adjust underline length to match \"Returns\"" fixable: true location: - row: 99 + row: 108 column: 4 end_location: - row: 105 + row: 114 column: 7 fix: content: " -------\n" location: - row: 102 + row: 111 column: 0 end_location: - row: 103 + row: 112 column: 0 parent: ~ - kind: @@ -28,18 +28,18 @@ expression: diagnostics suggestion: "Adjust underline length to match \"Returns\"" fixable: true location: - row: 207 + row: 216 column: 4 end_location: - row: 221 + row: 230 column: 7 fix: content: " -------\n" location: - row: 216 + row: 225 column: 0 end_location: - row: 217 + row: 226 column: 0 parent: ~ diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D410_sections.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D410_sections.py.snap index eb1033ce45595..a7f9a705d4eb7 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D410_sections.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D410_sections.py.snap @@ -8,19 +8,19 @@ expression: diagnostics suggestion: "Add blank line after \"Returns\"" fixable: true location: - row: 67 + row: 76 column: 4 end_location: - row: 78 + row: 87 column: 7 fix: content: "\n" location: - row: 71 - column: 0 + row: 79 + column: 11 end_location: - row: 71 - column: 0 + row: 79 + column: 11 parent: ~ - kind: name: BlankLineAfterSection @@ -28,18 +28,18 @@ expression: diagnostics suggestion: "Add blank line after \"Returns\"" fixable: true location: - row: 207 + row: 216 column: 4 end_location: - row: 221 + row: 230 column: 7 fix: content: "\n" location: - row: 218 - column: 0 + row: 226 + column: 31 end_location: - row: 218 - column: 0 + row: 226 + column: 31 parent: ~ diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D411_sections.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D411_sections.py.snap index c95cb8fd6ab9c..61730a11b252e 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D411_sections.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D411_sections.py.snap @@ -8,18 +8,18 @@ expression: diagnostics suggestion: "Add blank line before \"Yields\"" fixable: true location: - row: 67 + row: 76 column: 4 end_location: - row: 78 + row: 87 column: 7 fix: content: "\n" location: - row: 71 + row: 80 column: 0 end_location: - row: 71 + row: 80 column: 0 parent: ~ - kind: @@ -28,18 +28,18 @@ expression: diagnostics suggestion: "Add blank line before \"Returns\"" fixable: true location: - row: 122 + row: 131 column: 4 end_location: - row: 129 + row: 138 column: 7 fix: content: "\n" location: - row: 125 + row: 134 column: 0 end_location: - row: 125 + row: 134 column: 0 parent: ~ - kind: @@ -48,18 +48,18 @@ expression: diagnostics suggestion: "Add blank line before \"Raises\"" fixable: true location: - row: 207 + row: 216 column: 4 end_location: - row: 221 + row: 230 column: 7 fix: content: "\n" location: - row: 218 + row: 227 column: 0 end_location: - row: 218 + row: 227 column: 0 parent: ~ diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D412_sections.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D412_sections.py.snap index 0b79d1274993b..1ef073211b54f 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D412_sections.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D412_sections.py.snap @@ -8,18 +8,18 @@ expression: diagnostics suggestion: Remove blank line(s) fixable: true location: - row: 207 + row: 216 column: 4 end_location: - row: 221 + row: 230 column: 7 fix: content: "" location: - row: 211 + row: 220 column: 0 end_location: - row: 212 + row: 221 column: 0 parent: ~ diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D413_sections.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D413_sections.py.snap index 9c3ea66b0e726..58a097f3a9442 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D413_sections.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D413_sections.py.snap @@ -1,6 +1,25 @@ --- -source: src/rules/pydocstyle/mod.rs +source: crates/ruff/src/rules/pydocstyle/mod.rs expression: diagnostics --- -[] +- kind: + name: BlankLineAfterLastSection + body: "Missing blank line after last section (\"Returns\")" + suggestion: "Add blank line after \"Returns\"" + fixable: true + location: + row: 65 + column: 4 + end_location: + row: 67 + column: 14 + fix: + content: "\n " + location: + row: 67 + column: 11 + end_location: + row: 67 + column: 11 + parent: ~ diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D414_sections.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D414_sections.py.snap index a08fe926ce8d7..069412293a53c 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D414_sections.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D414_sections.py.snap @@ -21,10 +21,23 @@ expression: diagnostics suggestion: ~ fixable: false location: + row: 65 + column: 4 + end_location: row: 67 + column: 14 + fix: ~ + parent: ~ +- kind: + name: EmptyDocstringSection + body: "Section has no content (\"Returns\")" + suggestion: ~ + fixable: false + location: + row: 76 column: 4 end_location: - row: 78 + row: 87 column: 7 fix: ~ parent: ~ @@ -34,10 +47,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 67 + row: 76 column: 4 end_location: - row: 78 + row: 87 column: 7 fix: ~ parent: ~ @@ -47,10 +60,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 161 + row: 170 column: 4 end_location: - row: 165 + row: 174 column: 7 fix: ~ parent: ~ @@ -60,10 +73,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 252 + row: 261 column: 4 end_location: - row: 262 + row: 271 column: 7 fix: ~ parent: ~ diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D417_sections.py.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D417_sections.py.snap index b5acbbc86234a..73c343dbf0aa9 100644 --- a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D417_sections.py.snap +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__D417_sections.py.snap @@ -8,10 +8,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 283 + row: 292 column: 8 end_location: - row: 283 + row: 292 column: 11 fix: ~ parent: ~ @@ -21,10 +21,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 300 + row: 309 column: 4 end_location: - row: 300 + row: 309 column: 28 fix: ~ parent: ~ @@ -34,10 +34,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 324 + row: 333 column: 8 end_location: - row: 324 + row: 333 column: 25 fix: ~ parent: ~ @@ -47,10 +47,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 336 + row: 345 column: 8 end_location: - row: 336 + row: 345 column: 38 fix: ~ parent: ~ @@ -60,10 +60,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 349 + row: 358 column: 8 end_location: - row: 349 + row: 358 column: 39 fix: ~ parent: ~ @@ -73,10 +73,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 361 + row: 370 column: 8 end_location: - row: 361 + row: 370 column: 30 fix: ~ parent: ~ @@ -86,10 +86,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 389 + row: 398 column: 4 end_location: - row: 389 + row: 398 column: 27 fix: ~ parent: ~ @@ -99,10 +99,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 425 + row: 434 column: 8 end_location: - row: 425 + row: 434 column: 25 fix: ~ parent: ~ @@ -112,10 +112,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 440 + row: 449 column: 8 end_location: - row: 440 + row: 449 column: 38 fix: ~ parent: ~ @@ -125,10 +125,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 459 + row: 468 column: 8 end_location: - row: 459 + row: 468 column: 39 fix: ~ parent: ~ @@ -138,10 +138,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 489 + row: 498 column: 8 end_location: - row: 489 + row: 498 column: 29 fix: ~ parent: ~ From bd935cbd49f3de9b5d4949391bea77478d2cdd2e Mon Sep 17 00:00:00 2001 From: Johan Date: Fri, 17 Mar 2023 20:20:08 +0100 Subject: [PATCH 44/49] [`flake8-bugbear`] Add `no-explicit-stacklevel` (`B028`) (#3550) --- .../test/fixtures/flake8_bugbear/B028.py | 11 +++ crates/ruff/src/checkers/ast/mod.rs | 3 + crates/ruff/src/codes.rs | 1 + crates/ruff/src/registry.rs | 1 + crates/ruff/src/rules/flake8_bugbear/mod.rs | 1 + .../src/rules/flake8_bugbear/rules/mod.rs | 2 + .../rules/no_explicit_stacklevel.rs | 68 +++++++++++++++++++ ...__flake8_bugbear__tests__B028_B028.py.snap | 31 +++++++++ ruff.schema.json | 1 + 9 files changed, 119 insertions(+) create mode 100644 crates/ruff/resources/test/fixtures/flake8_bugbear/B028.py create mode 100644 crates/ruff/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs create mode 100644 crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B028_B028.py.snap diff --git a/crates/ruff/resources/test/fixtures/flake8_bugbear/B028.py b/crates/ruff/resources/test/fixtures/flake8_bugbear/B028.py new file mode 100644 index 0000000000000..a2915f29adf08 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_bugbear/B028.py @@ -0,0 +1,11 @@ +import warnings + +""" +Should emit: +B028 - on lines 8 and 9 +""" + +warnings.warn(DeprecationWarning("test")) +warnings.warn(DeprecationWarning("test"), source=None) +warnings.warn(DeprecationWarning("test"), source=None, stacklevel=2) +warnings.warn(DeprecationWarning("test"), stacklevel=1) diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 5c4bff9c642fb..0b24ebc6eaa96 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -2456,6 +2456,9 @@ where { flake8_bugbear::rules::zip_without_explicit_strict(self, expr, func, keywords); } + if self.settings.rules.enabled(Rule::NoExplicitStacklevel) { + flake8_bugbear::rules::no_explicit_stacklevel(self, func, args, keywords); + } // flake8-pie if self.settings.rules.enabled(Rule::UnnecessaryDictKwargs) { diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index f4d2f5592ba45..952576fc55058 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -227,6 +227,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option { (Flake8Bugbear, "025") => Rule::DuplicateTryBlockException, (Flake8Bugbear, "026") => Rule::StarArgUnpackingAfterKeywordArg, (Flake8Bugbear, "027") => Rule::EmptyMethodWithoutAbstractDecorator, + (Flake8Bugbear, "028") => Rule::NoExplicitStacklevel, (Flake8Bugbear, "029") => Rule::ExceptWithEmptyTuple, (Flake8Bugbear, "030") => Rule::ExceptWithNonExceptionClasses, (Flake8Bugbear, "032") => Rule::UnintentionalTypeAnnotation, diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 04e5bfc147c3c..e833fe8b9852f 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -186,6 +186,7 @@ ruff_macros::register_rules!( rules::flake8_bugbear::rules::UnreliableCallableCheck, rules::flake8_bugbear::rules::StripWithMultiCharacters, rules::flake8_bugbear::rules::MutableArgumentDefault, + rules::flake8_bugbear::rules::NoExplicitStacklevel, rules::flake8_bugbear::rules::UnusedLoopControlVariable, rules::flake8_bugbear::rules::FunctionCallArgumentDefault, rules::flake8_bugbear::rules::GetAttrWithConstant, diff --git a/crates/ruff/src/rules/flake8_bugbear/mod.rs b/crates/ruff/src/rules/flake8_bugbear/mod.rs index a20926156cdfb..4f3a166ee5e77 100644 --- a/crates/ruff/src/rules/flake8_bugbear/mod.rs +++ b/crates/ruff/src/rules/flake8_bugbear/mod.rs @@ -41,6 +41,7 @@ mod tests { #[test_case(Rule::StarArgUnpackingAfterKeywordArg, Path::new("B026.py"); "B026")] #[test_case(Rule::EmptyMethodWithoutAbstractDecorator, Path::new("B027.py"); "B027")] #[test_case(Rule::EmptyMethodWithoutAbstractDecorator, Path::new("B027.pyi"); "B027_pyi")] + #[test_case(Rule::NoExplicitStacklevel, Path::new("B028.py"); "B028")] #[test_case(Rule::ExceptWithEmptyTuple, Path::new("B029.py"); "B029")] #[test_case(Rule::ExceptWithNonExceptionClasses, Path::new("B030.py"); "B030")] #[test_case(Rule::UnintentionalTypeAnnotation, Path::new("B032.py"); "B032")] diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/mod.rs b/crates/ruff/src/rules/flake8_bugbear/rules/mod.rs index 1c1f2ada284a6..7802109c4eebe 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/mod.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/mod.rs @@ -25,6 +25,7 @@ pub use loop_variable_overrides_iterator::{ loop_variable_overrides_iterator, LoopVariableOverridesIterator, }; pub use mutable_argument_default::{mutable_argument_default, MutableArgumentDefault}; +pub use no_explicit_stacklevel::{no_explicit_stacklevel, NoExplicitStacklevel}; pub use raise_without_from_inside_except::{ raise_without_from_inside_except, RaiseWithoutFromInsideExcept, }; @@ -63,6 +64,7 @@ mod getattr_with_constant; mod jump_statement_in_finally; mod loop_variable_overrides_iterator; mod mutable_argument_default; +mod no_explicit_stacklevel; mod raise_without_from_inside_except; mod redundant_tuple_in_exception_handler; mod setattr_with_constant; diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs b/crates/ruff/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs new file mode 100644 index 0000000000000..b93961fed8a92 --- /dev/null +++ b/crates/ruff/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs @@ -0,0 +1,68 @@ +use rustpython_parser::ast::{Expr, Keyword}; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::helpers::SimpleCallArgs; +use ruff_python_ast::types::Range; + +use crate::checkers::ast::Checker; + +/// ## What it does +/// Checks for `warnings.warn` calls without an explicit `stacklevel` keyword +/// argument. +/// +/// ## Why is this bad? +/// The `warnings.warn` method uses a `stacklevel` of 1 by default, which +/// limits the rendered stack trace to that of the line on which the +/// `warn` method is called. +/// +/// It's recommended to use a `stacklevel` of 2 or higher, give the caller +/// more context about the warning. +/// +/// ## Example +/// ```python +/// warnings.warn("This is a warning") +/// ``` +/// +/// Use instead: +/// ```python +/// warnings.warn("This is a warning", stacklevel=2) +/// ``` +#[violation] +pub struct NoExplicitStacklevel; + +impl Violation for NoExplicitStacklevel { + #[derive_message_formats] + fn message(&self) -> String { + format!("No explicit `stacklevel` keyword argument found") + } +} + +/// B028 +pub fn no_explicit_stacklevel( + checker: &mut Checker, + func: &Expr, + args: &[Expr], + keywords: &[Keyword], +) { + if !checker + .ctx + .resolve_call_path(func) + .map_or(false, |call_path| { + call_path.as_slice() == ["warnings", "warn"] + }) + { + return; + } + + if SimpleCallArgs::new(args, keywords) + .keyword_argument("stacklevel") + .is_some() + { + return; + } + + checker + .diagnostics + .push(Diagnostic::new(NoExplicitStacklevel, Range::from(func))); +} diff --git a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B028_B028.py.snap b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B028_B028.py.snap new file mode 100644 index 0000000000000..e2c47aa6d1d00 --- /dev/null +++ b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B028_B028.py.snap @@ -0,0 +1,31 @@ +--- +source: crates/ruff/src/rules/flake8_bugbear/mod.rs +expression: diagnostics +--- +- kind: + name: NoExplicitStacklevel + body: "No explicit `stacklevel` keyword argument found" + suggestion: ~ + fixable: false + location: + row: 8 + column: 0 + end_location: + row: 8 + column: 13 + fix: ~ + parent: ~ +- kind: + name: NoExplicitStacklevel + body: "No explicit `stacklevel` keyword argument found" + suggestion: ~ + fixable: false + location: + row: 9 + column: 0 + end_location: + row: 9 + column: 13 + fix: ~ + parent: ~ + diff --git a/ruff.schema.json b/ruff.schema.json index 2d5f187c93d00..c25cf1582777c 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1469,6 +1469,7 @@ "B025", "B026", "B027", + "B028", "B029", "B03", "B030", From 1dd3cbd047beda01a7833e1b20e10e745757fe3e Mon Sep 17 00:00:00 2001 From: Tomer Chachamu Date: Fri, 17 Mar 2023 19:30:41 +0000 Subject: [PATCH 45/49] [`pylint`] invalid-characters-* (#3552) --- .../fixtures/pylint/invalid_characters.py | Bin 0 -> 972 bytes crates/ruff/src/checkers/tokens.rs | 78 ++++--- crates/ruff/src/codes.rs | 7 +- crates/ruff/src/registry.rs | 10 + crates/ruff/src/rules/pylint/mod.rs | 41 ++-- .../pylint/rules/invalid_string_characters.rs | 211 ++++++++++++++++++ crates/ruff/src/rules/pylint/rules/mod.rs | 5 + ..._tests__PLE2510_invalid_characters.py.snap | 25 +++ ..._tests__PLE2512_invalid_characters.py.snap | 25 +++ ..._tests__PLE2513_invalid_characters.py.snap | 25 +++ ..._tests__PLE2514_invalid_characters.py.snap | 25 +++ ..._tests__PLE2515_invalid_characters.py.snap | 25 +++ crates/ruff/src/settings/rule_table.rs | 5 + ruff.schema.json | 6 + 14 files changed, 437 insertions(+), 51 deletions(-) create mode 100644 crates/ruff/resources/test/fixtures/pylint/invalid_characters.py create mode 100644 crates/ruff/src/rules/pylint/rules/invalid_string_characters.rs create mode 100644 crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2510_invalid_characters.py.snap create mode 100644 crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2512_invalid_characters.py.snap create mode 100644 crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2513_invalid_characters.py.snap create mode 100644 crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2514_invalid_characters.py.snap create mode 100644 crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2515_invalid_characters.py.snap diff --git a/crates/ruff/resources/test/fixtures/pylint/invalid_characters.py b/crates/ruff/resources/test/fixtures/pylint/invalid_characters.py new file mode 100644 index 0000000000000000000000000000000000000000..ecc681b112288e93baa23e2f27c688d3d7fb907d GIT binary patch literal 972 zcmaKqOKTf35XXBg{s_rsNDkXT4q7`82&CX_Z7_sT;+R4RgxS@2S6G%7J?c28-uo^4 zNh9HixQ*af*R7e70fs>=c5eKv|P^A>M0-?as zn*-v)X=6L5@a6kb3gyn>WRP~*)yWy$euYOb>E`BMm!(kUlh#98^i65cgMPDiu^zkL z#qN-goeAJjn>o6tWlj6R?j`FsK*5CxB@3-kPLwD#ZzWT Vec { let mut diagnostics: Vec = vec![]; - let enforce_ambiguous_unicode_character = settings - .rules - .enabled(Rule::AmbiguousUnicodeCharacterString) - || settings - .rules - .enabled(Rule::AmbiguousUnicodeCharacterDocstring) - || settings - .rules - .enabled(Rule::AmbiguousUnicodeCharacterComment); - let enforce_quotes = settings.rules.enabled(Rule::BadQuotesInlineString) - || settings.rules.enabled(Rule::BadQuotesMultilineString) - || settings.rules.enabled(Rule::BadQuotesDocstring) - || settings.rules.enabled(Rule::AvoidableEscapedQuote); + let enforce_ambiguous_unicode_character = settings.rules.any_enabled(&[ + Rule::AmbiguousUnicodeCharacterString, + Rule::AmbiguousUnicodeCharacterDocstring, + Rule::AmbiguousUnicodeCharacterComment, + ]); + let enforce_invalid_string_character = settings.rules.any_enabled(&[ + Rule::InvalidCharacterBackspace, + Rule::InvalidCharacterSub, + Rule::InvalidCharacterEsc, + Rule::InvalidCharacterNul, + Rule::InvalidCharacterZeroWidthSpace, + ]); + let enforce_quotes = settings.rules.any_enabled(&[ + Rule::BadQuotesInlineString, + Rule::BadQuotesMultilineString, + Rule::BadQuotesDocstring, + Rule::AvoidableEscapedQuote, + ]); let enforce_commented_out_code = settings.rules.enabled(Rule::CommentedOutCode); - let enforce_compound_statements = settings - .rules - .enabled(Rule::MultipleStatementsOnOneLineColon) - || settings - .rules - .enabled(Rule::MultipleStatementsOnOneLineSemicolon) - || settings.rules.enabled(Rule::UselessSemicolon); + let enforce_compound_statements = settings.rules.any_enabled(&[ + Rule::MultipleStatementsOnOneLineColon, + Rule::MultipleStatementsOnOneLineSemicolon, + Rule::UselessSemicolon, + ]); let enforce_invalid_escape_sequence = settings.rules.enabled(Rule::InvalidEscapeSequence); - let enforce_implicit_string_concatenation = settings - .rules - .enabled(Rule::SingleLineImplicitStringConcatenation) - || settings - .rules - .enabled(Rule::MultiLineImplicitStringConcatenation); - let enforce_trailing_comma = settings.rules.enabled(Rule::TrailingCommaMissing) - || settings - .rules - .enabled(Rule::TrailingCommaOnBareTupleProhibited) - || settings.rules.enabled(Rule::TrailingCommaProhibited); + let enforce_implicit_string_concatenation = settings.rules.any_enabled(&[ + Rule::SingleLineImplicitStringConcatenation, + Rule::MultiLineImplicitStringConcatenation, + ]); + + let enforce_trailing_comma = settings.rules.any_enabled(&[ + Rule::TrailingCommaMissing, + Rule::TrailingCommaOnBareTupleProhibited, + Rule::TrailingCommaProhibited, + ]); let enforce_extraneous_parenthesis = settings.rules.enabled(Rule::ExtraneousParentheses); let enforce_type_comment_in_stub = settings.rules.enabled(Rule::TypeCommentInStub); @@ -116,6 +118,18 @@ pub fn check_tokens( } } } + // PLE2510, PLE2512, PLE2513 + if enforce_invalid_string_character { + for (start, tok, end) in tokens.iter().flatten() { + if matches!(tok, Tok::String { .. }) { + diagnostics.extend( + pylint::rules::invalid_string_characters(locator, *start, *end, autofix.into()) + .into_iter() + .filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())), + ); + } + } + } // E701, E702, E703 if enforce_compound_statements { diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 952576fc55058..0f271e60f66b3 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -176,9 +176,14 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option { (Pylint, "E1205") => Rule::LoggingTooManyArgs, (Pylint, "E1206") => Rule::LoggingTooFewArgs, (Pylint, "E1307") => Rule::BadStringFormatType, + (Pylint, "E2502") => Rule::BidirectionalUnicode, + (Pylint, "E2510") => Rule::InvalidCharacterBackspace, + (Pylint, "E2512") => Rule::InvalidCharacterSub, + (Pylint, "E2513") => Rule::InvalidCharacterEsc, + (Pylint, "E2514") => Rule::InvalidCharacterNul, + (Pylint, "E2515") => Rule::InvalidCharacterZeroWidthSpace, (Pylint, "E1310") => Rule::BadStrStripCall, (Pylint, "E1507") => Rule::InvalidEnvvarValue, - (Pylint, "E2502") => Rule::BidirectionalUnicode, (Pylint, "R0133") => Rule::ComparisonOfConstant, (Pylint, "R0206") => Rule::PropertyWithParameters, (Pylint, "R0402") => Rule::ConsiderUsingFromImport, diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index e833fe8b9852f..51c5ff8a8edf0 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -150,6 +150,11 @@ ruff_macros::register_rules!( rules::pylint::rules::InvalidEnvvarValue, rules::pylint::rules::BadStringFormatType, rules::pylint::rules::BidirectionalUnicode, + rules::pylint::rules::InvalidCharacterBackspace, + rules::pylint::rules::InvalidCharacterSub, + rules::pylint::rules::InvalidCharacterEsc, + rules::pylint::rules::InvalidCharacterNul, + rules::pylint::rules::InvalidCharacterZeroWidthSpace, rules::pylint::rules::BadStrStripCall, rules::pylint::rules::CollapsibleElseIf, rules::pylint::rules::ContinueInFinally, @@ -855,6 +860,11 @@ impl Rule { | Rule::BadQuotesMultilineString | Rule::CommentedOutCode | Rule::MultiLineImplicitStringConcatenation + | Rule::InvalidCharacterBackspace + | Rule::InvalidCharacterSub + | Rule::InvalidCharacterEsc + | Rule::InvalidCharacterNul + | Rule::InvalidCharacterZeroWidthSpace | Rule::ExtraneousParentheses | Rule::InvalidEscapeSequence | Rule::SingleLineImplicitStringConcatenation diff --git a/crates/ruff/src/rules/pylint/mod.rs b/crates/ruff/src/rules/pylint/mod.rs index 635d163e78861..0d8949fdb00d3 100644 --- a/crates/ruff/src/rules/pylint/mod.rs +++ b/crates/ruff/src/rules/pylint/mod.rs @@ -18,19 +18,15 @@ mod tests { use crate::settings::Settings; use crate::test::test_path; - #[test_case(Rule::LoggingTooManyArgs, Path::new("logging_too_many_args.py"); "PLE1205")] - #[test_case(Rule::LoggingTooFewArgs, Path::new("logging_too_few_args.py"); "PLE1206")] - #[test_case(Rule::ReturnInInit, Path::new("return_in_init.py"); "PLE0101")] - #[test_case(Rule::UselessImportAlias, Path::new("import_aliasing.py"); "PLC0414")] - #[test_case(Rule::UnnecessaryDirectLambdaCall, Path::new("unnecessary_direct_lambda_call.py"); "PLC3002")] - #[test_case(Rule::NonlocalWithoutBinding, Path::new("nonlocal_without_binding.py"); "PLE0117")] - #[test_case(Rule::UsedPriorGlobalDeclaration, Path::new("used_prior_global_declaration.py"); "PLE0118")] #[test_case(Rule::AwaitOutsideAsync, Path::new("await_outside_async.py"); "PLE1142")] + #[test_case(Rule::BadStrStripCall, Path::new("bad_str_strip_call.py"); "PLE01310")] + #[test_case(Rule::BadStringFormatType, Path::new("bad_string_format_type.py"); "PLE1307")] + #[test_case(Rule::BidirectionalUnicode, Path::new("bidirectional_unicode.py"); "PLE2502")] + #[test_case(Rule::CollapsibleElseIf, Path::new("collapsible_else_if.py"); "PLR5501")] #[test_case(Rule::CompareToEmptyString, Path::new("compare_to_empty_string.py"); "PLC1901")] #[test_case(Rule::ComparisonOfConstant, Path::new("comparison_of_constant.py"); "PLR0133")] - #[test_case(Rule::PropertyWithParameters, Path::new("property_with_parameters.py"); "PLR0206")] - #[test_case(Rule::ConsiderUsingFromImport, Path::new("import_aliasing.py"); "PLR0402")] #[test_case(Rule::ConsiderMergingIsinstance, Path::new("consider_merging_isinstance.py"); "PLR1701")] + #[test_case(Rule::ConsiderUsingFromImport, Path::new("import_aliasing.py"); "PLR0402")] #[test_case(Rule::ConsiderUsingSysExit, Path::new("consider_using_sys_exit_0.py"); "PLR1722_0")] #[test_case(Rule::ConsiderUsingSysExit, Path::new("consider_using_sys_exit_1.py"); "PLR1722_1")] #[test_case(Rule::ConsiderUsingSysExit, Path::new("consider_using_sys_exit_2.py"); "PLR1722_2")] @@ -39,24 +35,33 @@ mod tests { #[test_case(Rule::ConsiderUsingSysExit, Path::new("consider_using_sys_exit_5.py"); "PLR1722_5")] #[test_case(Rule::ConsiderUsingSysExit, Path::new("consider_using_sys_exit_6.py"); "PLR1722_6")] #[test_case(Rule::ContinueInFinally, Path::new("continue_in_finally.py"); "PLE0116")] - #[test_case(Rule::MagicValueComparison, Path::new("magic_value_comparison.py"); "PLR2004")] - #[test_case(Rule::UselessElseOnLoop, Path::new("useless_else_on_loop.py"); "PLW0120")] - #[test_case(Rule::GlobalVariableNotAssigned, Path::new("global_variable_not_assigned.py"); "PLW0602")] #[test_case(Rule::GlobalStatement, Path::new("global_statement.py"); "PLW0603")] + #[test_case(Rule::GlobalVariableNotAssigned, Path::new("global_variable_not_assigned.py"); "PLW0602")] #[test_case(Rule::InvalidAllFormat, Path::new("invalid_all_format.py"); "PLE0605")] #[test_case(Rule::InvalidAllObject, Path::new("invalid_all_object.py"); "PLE0604")] + #[test_case(Rule::InvalidCharacterBackspace, Path::new("invalid_characters.py"); "PLE2510")] + #[test_case(Rule::InvalidCharacterEsc, Path::new("invalid_characters.py"); "PLE2513")] + #[test_case(Rule::InvalidCharacterNul, Path::new("invalid_characters.py"); "PLE2514")] + #[test_case(Rule::InvalidCharacterSub, Path::new("invalid_characters.py"); "PLE2512")] + #[test_case(Rule::InvalidCharacterZeroWidthSpace, Path::new("invalid_characters.py"); "PLE2515")] #[test_case(Rule::InvalidEnvvarDefault, Path::new("invalid_envvar_default.py"); "PLW1508")] #[test_case(Rule::InvalidEnvvarValue, Path::new("invalid_envvar_value.py"); "PLE1507")] - #[test_case(Rule::TooManyReturnStatements, Path::new("too_many_return_statements.py"); "PLR0911")] + #[test_case(Rule::LoggingTooFewArgs, Path::new("logging_too_few_args.py"); "PLE1206")] + #[test_case(Rule::LoggingTooManyArgs, Path::new("logging_too_many_args.py"); "PLE1205")] + #[test_case(Rule::MagicValueComparison, Path::new("magic_value_comparison.py"); "PLR2004")] + #[test_case(Rule::NonlocalWithoutBinding, Path::new("nonlocal_without_binding.py"); "PLE0117")] + #[test_case(Rule::PropertyWithParameters, Path::new("property_with_parameters.py"); "PLR0206")] + #[test_case(Rule::RedefinedLoopName, Path::new("redefined_loop_name.py"); "PLW2901")] + #[test_case(Rule::ReturnInInit, Path::new("return_in_init.py"); "PLE0101")] #[test_case(Rule::TooManyArguments, Path::new("too_many_arguments.py"); "PLR0913")] #[test_case(Rule::TooManyBranches, Path::new("too_many_branches.py"); "PLR0912")] + #[test_case(Rule::TooManyReturnStatements, Path::new("too_many_return_statements.py"); "PLR0911")] #[test_case(Rule::TooManyStatements, Path::new("too_many_statements.py"); "PLR0915")] - #[test_case(Rule::CollapsibleElseIf, Path::new("collapsible_else_if.py"); "PLR5501")] - #[test_case(Rule::BadStringFormatType, Path::new("bad_string_format_type.py"); "PLE1307")] - #[test_case(Rule::BidirectionalUnicode, Path::new("bidirectional_unicode.py"); "PLE2502")] - #[test_case(Rule::BadStrStripCall, Path::new("bad_str_strip_call.py"); "PLE01310")] + #[test_case(Rule::UnnecessaryDirectLambdaCall, Path::new("unnecessary_direct_lambda_call.py"); "PLC3002")] + #[test_case(Rule::UsedPriorGlobalDeclaration, Path::new("used_prior_global_declaration.py"); "PLE0118")] + #[test_case(Rule::UselessElseOnLoop, Path::new("useless_else_on_loop.py"); "PLW0120")] + #[test_case(Rule::UselessImportAlias, Path::new("import_aliasing.py"); "PLC0414")] #[test_case(Rule::YieldInInit, Path::new("yield_in_init.py"); "PLE0100")] - #[test_case(Rule::RedefinedLoopName, Path::new("redefined_loop_name.py"); "PLW2901")] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff/src/rules/pylint/rules/invalid_string_characters.rs b/crates/ruff/src/rules/pylint/rules/invalid_string_characters.rs new file mode 100644 index 0000000000000..fbe8b4912a41e --- /dev/null +++ b/crates/ruff/src/rules/pylint/rules/invalid_string_characters.rs @@ -0,0 +1,211 @@ +use rustpython_parser::ast::Location; + +use ruff_diagnostics::AlwaysAutofixableViolation; +use ruff_diagnostics::Fix; +use ruff_diagnostics::{Diagnostic, DiagnosticKind}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::helpers; +use ruff_python_ast::newlines::UniversalNewlineIterator; +use ruff_python_ast::source_code::Locator; +use ruff_python_ast::types::Range; + +/// ## What it does +/// Checks for strings that contain the control character `BS`. +/// +/// ## Why is this bad? +/// Control characters are displayed differently by different text editors and +/// terminals. +/// +/// By using the `\b` sequence in lieu of the `BS` control character, the +/// string will contain the same value, but will render visibly in all editors. +/// +/// ## Example +/// ```python +/// x = '' +/// ``` +/// +/// Use instead: +/// ```python +/// x = '\b' +/// ``` +#[violation] +pub struct InvalidCharacterBackspace; + +impl AlwaysAutofixableViolation for InvalidCharacterBackspace { + #[derive_message_formats] + fn message(&self) -> String { + format!("Invalid unescaped character backspace, use \"\\b\" instead") + } + + fn autofix_title(&self) -> String { + "Replace with escape sequence".to_string() + } +} + +/// ## What it does +/// Checks for strings that contain the raw control character `SUB`. +/// +/// ## Why is this bad? +/// Control characters are displayed differently by different text editors and +/// terminals. +/// +/// By using the `\x1A` sequence in lieu of the `SUB` control character, the +/// string will contain the same value, but will render visibly in all editors. +/// +/// ## Example +/// ```python +/// x = '' +/// ``` +/// +/// Use instead: +/// ```python +/// x = '\x1A' +/// ``` +#[violation] +pub struct InvalidCharacterSub; + +impl AlwaysAutofixableViolation for InvalidCharacterSub { + #[derive_message_formats] + fn message(&self) -> String { + format!("Invalid unescaped character SUB, use \"\\x1A\" instead") + } + + fn autofix_title(&self) -> String { + "Replace with escape sequence".to_string() + } +} + +/// ## What it does +/// Checks for strings that contain the raw control character `ESC`. +/// +/// ## Why is this bad? +/// Control characters are displayed differently by different text editors and +/// terminals. +/// +/// By using the `\x1B` sequence in lieu of the `SUB` control character, the +/// string will contain the same value, but will render visibly in all editors. +/// +/// ## Example +/// ```python +/// x = '' +/// ``` +/// +/// Use instead: +/// ```python +/// x = '\x1B' +/// ``` +#[violation] +pub struct InvalidCharacterEsc; + +impl AlwaysAutofixableViolation for InvalidCharacterEsc { + #[derive_message_formats] + fn message(&self) -> String { + format!("Invalid unescaped character ESC, use \"\\x1B\" instead") + } + + fn autofix_title(&self) -> String { + "Replace with escape sequence".to_string() + } +} + +/// ## What it does +/// Checks for strings that contain the raw control character `NUL` (0 byte). +/// +/// ## Why is this bad? +/// Control characters are displayed differently by different text editors and +/// terminals. +/// +/// By using the `\0` sequence in lieu of the `NUL` control character, the +/// string will contain the same value, but will render visibly in all editors. +/// +/// ## Example +/// ```python +/// x = '' +/// ``` +/// +/// Use instead: +/// ```python +/// x = '\0' +/// ``` +#[violation] +pub struct InvalidCharacterNul; + +impl AlwaysAutofixableViolation for InvalidCharacterNul { + #[derive_message_formats] + fn message(&self) -> String { + format!("Invalid unescaped character NUL, use \"\\0\" instead") + } + + fn autofix_title(&self) -> String { + "Replace with escape sequence".to_string() + } +} + +/// ## What it does +/// Checks for strings that contain the zero width space character. +/// +/// ## Why is this bad? +/// This character is rendered invisibly in some text editors and terminals. +/// +/// By using the `\u200B` sequence, the string will contain the same value, +/// but will render visibly in all editors. +/// +/// ## Example +/// ```python +/// x = 'Dear Sir/Madam' +/// ``` +/// +/// Use instead: +/// ```python +/// x = 'Dear Sir\u200B/\u200BMadam' # zero width space +/// ``` +#[violation] +pub struct InvalidCharacterZeroWidthSpace; + +impl AlwaysAutofixableViolation for InvalidCharacterZeroWidthSpace { + #[derive_message_formats] + fn message(&self) -> String { + format!("Invalid unescaped character zero-width-space, use \"\\u200B\" instead") + } + + fn autofix_title(&self) -> String { + "Replace with escape sequence".to_string() + } +} + +/// PLE2510, PLE2512, PLE2513, PLE2514, PLE2515 +pub fn invalid_string_characters( + locator: &Locator, + start: Location, + end: Location, + autofix: bool, +) -> Vec { + let mut diagnostics = Vec::new(); + let text = locator.slice(Range::new(start, end)); + + for (row, line) in UniversalNewlineIterator::from(text).enumerate() { + for (column, match_) in line.match_indices(&['\x08', '\x1A', '\x1B', '\0', '\u{200b}']) { + let (replacement, rule): (&str, DiagnosticKind) = match match_.chars().next().unwrap() { + '\x08' => ("\\b", InvalidCharacterBackspace.into()), + '\x1A' => ("\\x1A", InvalidCharacterSub.into()), + '\x1B' => ("\\x1B", InvalidCharacterEsc.into()), + '\0' => ("\\0", InvalidCharacterNul.into()), + '\u{200b}' => ("\\u200b", InvalidCharacterZeroWidthSpace.into()), + _ => unreachable!(), + }; + let location = helpers::to_absolute(Location::new(row + 1, column), start); + let end_location = Location::new(location.row(), location.column() + 1); + let mut diagnostic = Diagnostic::new(rule, Range::new(location, end_location)); + if autofix { + diagnostic.amend(Fix::replacement( + replacement.to_string(), + location, + end_location, + )); + } + diagnostics.push(diagnostic); + } + } + + diagnostics +} diff --git a/crates/ruff/src/rules/pylint/rules/mod.rs b/crates/ruff/src/rules/pylint/rules/mod.rs index 8e55817cbd7b4..656c527aab3c8 100644 --- a/crates/ruff/src/rules/pylint/rules/mod.rs +++ b/crates/ruff/src/rules/pylint/rules/mod.rs @@ -13,6 +13,10 @@ pub use invalid_all_format::{invalid_all_format, InvalidAllFormat}; pub use invalid_all_object::{invalid_all_object, InvalidAllObject}; pub use invalid_envvar_default::{invalid_envvar_default, InvalidEnvvarDefault}; pub use invalid_envvar_value::{invalid_envvar_value, InvalidEnvvarValue}; +pub use invalid_string_characters::{ + invalid_string_characters, InvalidCharacterBackspace, InvalidCharacterEsc, InvalidCharacterNul, + InvalidCharacterSub, InvalidCharacterZeroWidthSpace, +}; pub use logging::{logging_call, LoggingTooFewArgs, LoggingTooManyArgs}; pub use magic_value_comparison::{magic_value_comparison, MagicValueComparison}; pub use merge_isinstance::{merge_isinstance, ConsiderMergingIsinstance}; @@ -50,6 +54,7 @@ mod invalid_all_format; mod invalid_all_object; mod invalid_envvar_default; mod invalid_envvar_value; +mod invalid_string_characters; mod logging; mod magic_value_comparison; mod merge_isinstance; diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2510_invalid_characters.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2510_invalid_characters.py.snap new file mode 100644 index 0000000000000..af015497f03de --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2510_invalid_characters.py.snap @@ -0,0 +1,25 @@ +--- +source: crates/ruff/src/rules/pylint/mod.rs +expression: diagnostics +--- +- kind: + name: InvalidCharacterBackspace + body: "Invalid unescaped character backspace, use \"\\b\" instead" + suggestion: Replace with escape sequence + fixable: true + location: + row: 15 + column: 5 + end_location: + row: 15 + column: 6 + fix: + content: "\\b" + location: + row: 15 + column: 5 + end_location: + row: 15 + column: 6 + parent: ~ + diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2512_invalid_characters.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2512_invalid_characters.py.snap new file mode 100644 index 0000000000000..e561dfd916f71 --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2512_invalid_characters.py.snap @@ -0,0 +1,25 @@ +--- +source: crates/ruff/src/rules/pylint/mod.rs +expression: diagnostics +--- +- kind: + name: InvalidCharacterSub + body: "Invalid unescaped character SUB, use \"\\x1A\" instead" + suggestion: Replace with escape sequence + fixable: true + location: + row: 21 + column: 11 + end_location: + row: 21 + column: 12 + fix: + content: "\\x1A" + location: + row: 21 + column: 11 + end_location: + row: 21 + column: 12 + parent: ~ + diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2513_invalid_characters.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2513_invalid_characters.py.snap new file mode 100644 index 0000000000000..e4d3f36871ff6 --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2513_invalid_characters.py.snap @@ -0,0 +1,25 @@ +--- +source: crates/ruff/src/rules/pylint/mod.rs +expression: diagnostics +--- +- kind: + name: InvalidCharacterEsc + body: "Invalid unescaped character ESC, use \"\\x1B\" instead" + suggestion: Replace with escape sequence + fixable: true + location: + row: 25 + column: 15 + end_location: + row: 25 + column: 16 + fix: + content: "\\x1B" + location: + row: 25 + column: 15 + end_location: + row: 25 + column: 16 + parent: ~ + diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2514_invalid_characters.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2514_invalid_characters.py.snap new file mode 100644 index 0000000000000..ee1ad0847a945 --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2514_invalid_characters.py.snap @@ -0,0 +1,25 @@ +--- +source: crates/ruff/src/rules/pylint/mod.rs +expression: diagnostics +--- +- kind: + name: InvalidCharacterNul + body: "Invalid unescaped character NUL, use \"\\0\" instead" + suggestion: Replace with escape sequence + fixable: true + location: + row: 30 + column: 4 + end_location: + row: 30 + column: 5 + fix: + content: "\\0" + location: + row: 30 + column: 4 + end_location: + row: 30 + column: 5 + parent: ~ + diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2515_invalid_characters.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2515_invalid_characters.py.snap new file mode 100644 index 0000000000000..b084eedd58f1c --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2515_invalid_characters.py.snap @@ -0,0 +1,25 @@ +--- +source: crates/ruff/src/rules/pylint/mod.rs +expression: diagnostics +--- +- kind: + name: InvalidCharacterZeroWidthSpace + body: "Invalid unescaped character zero-width-space, use \"\\u200B\" instead" + suggestion: Replace with escape sequence + fixable: true + location: + row: 34 + column: 12 + end_location: + row: 34 + column: 13 + fix: + content: "\\u200b" + location: + row: 34 + column: 12 + end_location: + row: 34 + column: 13 + parent: ~ + diff --git a/crates/ruff/src/settings/rule_table.rs b/crates/ruff/src/settings/rule_table.rs index c3e3e251bfc88..1464e6407543d 100644 --- a/crates/ruff/src/settings/rule_table.rs +++ b/crates/ruff/src/settings/rule_table.rs @@ -27,6 +27,11 @@ impl RuleTable { self.enabled.contains_key(&code) } + /// Returns whether any of the given rules should be checked. + pub fn any_enabled(&self, codes: &[Rule]) -> bool { + codes.iter().any(|c| self.enabled.contains_key(c)) + } + /// Returns whether violations of the given rule should be autofixed. #[inline] pub fn should_fix(&self, code: Rule) -> bool { diff --git a/ruff.schema.json b/ruff.schema.json index c25cf1582777c..71296adabc6e1 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1860,6 +1860,12 @@ "PLE25", "PLE250", "PLE2502", + "PLE251", + "PLE2510", + "PLE2512", + "PLE2513", + "PLE2514", + "PLE2515", "PLR", "PLR0", "PLR01", From 50f9db21daeb56765c6fc6c75057e685231277e1 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 17 Mar 2023 17:34:39 -0400 Subject: [PATCH 46/49] Enable ANSI colors on Windows 10 (#3583) --- crates/ruff_cli/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/ruff_cli/src/lib.rs b/crates/ruff_cli/src/lib.rs index 0fce84ded8423..b817d0df67f30 100644 --- a/crates/ruff_cli/src/lib.rs +++ b/crates/ruff_cli/src/lib.rs @@ -69,6 +69,10 @@ quoting the executed command, along with the relevant file contents and `pyproje })); } + // Enabled ANSI colors on Windows 10. + #[cfg(windows)] + assert!(colored::control::set_virtual_terminal(true).is_ok()); + let log_level: LogLevel = (&log_level_args).into(); set_up_logging(&log_level)?; From 8dd3959e74371ed336b42ea4b25a8ec6affc4f9b Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 17 Mar 2023 17:51:03 -0400 Subject: [PATCH 47/49] Update output in `resources/test/project/README.md` (#3587) --- crates/ruff/resources/test/project/README.md | 72 ++++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/crates/ruff/resources/test/project/README.md b/crates/ruff/resources/test/project/README.md index a4b915bf30482..bdeaebd78e907 100644 --- a/crates/ruff/resources/test/project/README.md +++ b/crates/ruff/resources/test/project/README.md @@ -9,30 +9,30 @@ Running from the repo root should pick up and enforce the appropriate settings f ```console ∴ cargo run -p ruff_cli -- check crates/ruff/resources/test/project/ -crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted -crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused -crates/ruff/resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused -crates/ruff/resources/test/project/examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted -crates/ruff/resources/test/project/examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used -crates/ruff/resources/test/project/project/file.py:1:8: F401 `os` imported but unused -crates/ruff/resources/test/project/project/import_file.py:1:1: I001 Import block is un-sorted or un-formatted +crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:1: I001 [*] Import block is un-sorted or un-formatted +crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:8: F401 [*] `numpy` imported but unused +crates/ruff/resources/test/project/examples/.dotfiles/script.py:2:17: F401 [*] `app.app_file` imported but unused +crates/ruff/resources/test/project/examples/docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted +crates/ruff/resources/test/project/examples/docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used +crates/ruff/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused +crates/ruff/resources/test/project/project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted Found 7 errors. -7 potentially fixable with the --fix option. +[*] 7 potentially fixable with the --fix option. ``` Running from the project directory itself should exhibit the same behavior: ```console ∴ (cd crates/ruff/resources/test/project/ && cargo run -p ruff_cli -- check .) -examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted -examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused -examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused -examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted -examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used -project/file.py:1:8: F401 `os` imported but unused -project/import_file.py:1:1: I001 Import block is un-sorted or un-formatted +examples/.dotfiles/script.py:1:1: I001 [*] Import block is un-sorted or un-formatted +examples/.dotfiles/script.py:1:8: F401 [*] `numpy` imported but unused +examples/.dotfiles/script.py:2:17: F401 [*] `app.app_file` imported but unused +examples/docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted +examples/docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used +project/file.py:1:8: F401 [*] `os` imported but unused +project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted Found 7 errors. -7 potentially fixable with the --fix option. +[*] 7 potentially fixable with the --fix option. ``` Running from the sub-package directory should exhibit the same behavior, but omit the top-level @@ -40,10 +40,10 @@ files: ```console ∴ (cd crates/ruff/resources/test/project/examples/docs && cargo run -p ruff_cli -- check .) -docs/file.py:1:1: I001 Import block is un-sorted or un-formatted -docs/file.py:8:5: F841 Local variable `x` is assigned to but never used +docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted +docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used Found 2 errors. -2 potentially fixable with the --fix option. +[*] 2 potentially fixable with the --fix option. ``` `--config` should force Ruff to use the specified `pyproject.toml` for all files, and resolve @@ -51,17 +51,17 @@ file paths from the current working directory: ```console ∴ (cargo run -p ruff_cli -- check --config=crates/ruff/resources/test/project/pyproject.toml crates/ruff/resources/test/project/) -crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused -crates/ruff/resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused -crates/ruff/resources/test/project/examples/docs/docs/concepts/file.py:1:8: F401 `os` imported but unused -crates/ruff/resources/test/project/examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted -crates/ruff/resources/test/project/examples/docs/docs/file.py:1:8: F401 `os` imported but unused -crates/ruff/resources/test/project/examples/docs/docs/file.py:3:8: F401 `numpy` imported but unused -crates/ruff/resources/test/project/examples/docs/docs/file.py:4:27: F401 `docs.concepts.file` imported but unused -crates/ruff/resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused -crates/ruff/resources/test/project/project/file.py:1:8: F401 `os` imported but unused +crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:8: F401 [*] `numpy` imported but unused +crates/ruff/resources/test/project/examples/.dotfiles/script.py:2:17: F401 [*] `app.app_file` imported but unused +crates/ruff/resources/test/project/examples/docs/docs/concepts/file.py:1:8: F401 [*] `os` imported but unused +crates/ruff/resources/test/project/examples/docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted +crates/ruff/resources/test/project/examples/docs/docs/file.py:1:8: F401 [*] `os` imported but unused +crates/ruff/resources/test/project/examples/docs/docs/file.py:3:8: F401 [*] `numpy` imported but unused +crates/ruff/resources/test/project/examples/docs/docs/file.py:4:27: F401 [*] `docs.concepts.file` imported but unused +crates/ruff/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused +crates/ruff/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused Found 9 errors. -9 potentially fixable with the --fix option. +[*] 9 potentially fixable with the --fix option. ``` Running from a parent directory should "ignore" the `exclude` (hence, `concepts/file.py` gets @@ -69,21 +69,21 @@ included in the output): ```console ∴ (cd crates/ruff/resources/test/project/examples && cargo run -p ruff_cli -- check --config=docs/ruff.toml .) -docs/docs/concepts/file.py:5:5: F841 Local variable `x` is assigned to but never used -docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted -docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used -excluded/script.py:5:5: F841 Local variable `x` is assigned to but never used +docs/docs/concepts/file.py:5:5: F841 [*] Local variable `x` is assigned to but never used +docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted +docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used +excluded/script.py:5:5: F841 [*] Local variable `x` is assigned to but never used Found 4 errors. -4 potentially fixable with the --fix option. +[*] 4 potentially fixable with the --fix option. ``` Passing an excluded directory directly should report errors in the contained files: ```console ∴ cargo run -p ruff_cli -- check crates/ruff/resources/test/project/examples/excluded/ -crates/ruff/resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused +crates/ruff/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused Found 1 error. -1 potentially fixable with the --fix option. +[*] 1 potentially fixable with the --fix option. ``` Unless we `--force-exclude`: From 61653b9f27887285983278077d892bc2b6f67aed Mon Sep 17 00:00:00 2001 From: tomecki Date: Fri, 17 Mar 2023 23:30:32 +0100 Subject: [PATCH 48/49] [`pylint`] Implement `useless-return` (`R1711`) (#3116) --- .../test/fixtures/pylint/useless_return.py | 50 ++++++++ crates/ruff/src/checkers/ast/mod.rs | 4 + crates/ruff/src/codes.rs | 7 +- crates/ruff/src/registry.rs | 1 + crates/ruff/src/rules/pylint/mod.rs | 1 + crates/ruff/src/rules/pylint/rules/mod.rs | 2 + .../src/rules/pylint/rules/useless_return.rs | 121 ++++++++++++++++++ ...int__tests__PLR1711_useless_return.py.snap | 105 +++++++++++++++ ruff.schema.json | 2 + 9 files changed, 290 insertions(+), 3 deletions(-) create mode 100644 crates/ruff/resources/test/fixtures/pylint/useless_return.py create mode 100644 crates/ruff/src/rules/pylint/rules/useless_return.rs create mode 100644 crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR1711_useless_return.py.snap diff --git a/crates/ruff/resources/test/fixtures/pylint/useless_return.py b/crates/ruff/resources/test/fixtures/pylint/useless_return.py new file mode 100644 index 0000000000000..75da8d8bebc5e --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pylint/useless_return.py @@ -0,0 +1,50 @@ +import sys + + +def print_python_version(): + print(sys.version) + return None # [useless-return] + + +def print_python_version(): + print(sys.version) + return None # [useless-return] + + +def print_python_version(): + print(sys.version) + return None # [useless-return] + + +class SomeClass: + def print_python_version(self): + print(sys.version) + return None # [useless-return] + + +def print_python_version(): + if 2 * 2 == 4: + return + print(sys.version) + + +def print_python_version(): + if 2 * 2 == 4: + return None + return + + +def print_python_version(): + if 2 * 2 == 4: + return None + + +def print_python_version(): + """This function returns None.""" + return None + + +def print_python_version(): + """This function returns None.""" + print(sys.version) + return None # [useless-return] diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 0b24ebc6eaa96..d984b21289451 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -436,6 +436,10 @@ where flake8_return::rules::function(self, body); } + if self.settings.rules.enabled(Rule::UselessReturn) { + pylint::rules::useless_return(self, stmt, body); + } + if self.settings.rules.enabled(Rule::ComplexStructure) { if let Some(diagnostic) = mccabe::rules::function_is_too_complex( stmt, diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 0f271e60f66b3..41e618eddfdae 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -171,19 +171,18 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option { (Pylint, "E0118") => Rule::UsedPriorGlobalDeclaration, (Pylint, "E0604") => Rule::InvalidAllObject, (Pylint, "E0605") => Rule::InvalidAllFormat, - (Pylint, "W1508") => Rule::InvalidEnvvarDefault, (Pylint, "E1142") => Rule::AwaitOutsideAsync, (Pylint, "E1205") => Rule::LoggingTooManyArgs, (Pylint, "E1206") => Rule::LoggingTooFewArgs, (Pylint, "E1307") => Rule::BadStringFormatType, + (Pylint, "E1310") => Rule::BadStrStripCall, + (Pylint, "E1507") => Rule::InvalidEnvvarValue, (Pylint, "E2502") => Rule::BidirectionalUnicode, (Pylint, "E2510") => Rule::InvalidCharacterBackspace, (Pylint, "E2512") => Rule::InvalidCharacterSub, (Pylint, "E2513") => Rule::InvalidCharacterEsc, (Pylint, "E2514") => Rule::InvalidCharacterNul, (Pylint, "E2515") => Rule::InvalidCharacterZeroWidthSpace, - (Pylint, "E1310") => Rule::BadStrStripCall, - (Pylint, "E1507") => Rule::InvalidEnvvarValue, (Pylint, "R0133") => Rule::ComparisonOfConstant, (Pylint, "R0206") => Rule::PropertyWithParameters, (Pylint, "R0402") => Rule::ConsiderUsingFromImport, @@ -192,12 +191,14 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option { (Pylint, "R0913") => Rule::TooManyArguments, (Pylint, "R0915") => Rule::TooManyStatements, (Pylint, "R1701") => Rule::ConsiderMergingIsinstance, + (Pylint, "R1711") => Rule::UselessReturn, (Pylint, "R1722") => Rule::ConsiderUsingSysExit, (Pylint, "R2004") => Rule::MagicValueComparison, (Pylint, "R5501") => Rule::CollapsibleElseIf, (Pylint, "W0120") => Rule::UselessElseOnLoop, (Pylint, "W0602") => Rule::GlobalVariableNotAssigned, (Pylint, "W0603") => Rule::GlobalStatement, + (Pylint, "W1508") => Rule::InvalidEnvvarDefault, (Pylint, "W2901") => Rule::RedefinedLoopName, // flake8-builtins diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 51c5ff8a8edf0..fdd1188959eda 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -143,6 +143,7 @@ ruff_macros::register_rules!( rules::pyflakes::rules::UnusedAnnotation, rules::pyflakes::rules::RaiseNotImplemented, // pylint + rules::pylint::rules::UselessReturn, rules::pylint::rules::YieldInInit, rules::pylint::rules::InvalidAllObject, rules::pylint::rules::InvalidAllFormat, diff --git a/crates/ruff/src/rules/pylint/mod.rs b/crates/ruff/src/rules/pylint/mod.rs index 0d8949fdb00d3..0cf76dd4e2cef 100644 --- a/crates/ruff/src/rules/pylint/mod.rs +++ b/crates/ruff/src/rules/pylint/mod.rs @@ -61,6 +61,7 @@ mod tests { #[test_case(Rule::UsedPriorGlobalDeclaration, Path::new("used_prior_global_declaration.py"); "PLE0118")] #[test_case(Rule::UselessElseOnLoop, Path::new("useless_else_on_loop.py"); "PLW0120")] #[test_case(Rule::UselessImportAlias, Path::new("import_aliasing.py"); "PLC0414")] + #[test_case(Rule::UselessReturn, Path::new("useless_return.py"); "PLR1711")] #[test_case(Rule::YieldInInit, Path::new("yield_in_init.py"); "PLE0100")] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); diff --git a/crates/ruff/src/rules/pylint/rules/mod.rs b/crates/ruff/src/rules/pylint/rules/mod.rs index 656c527aab3c8..1bd908b5e8488 100644 --- a/crates/ruff/src/rules/pylint/rules/mod.rs +++ b/crates/ruff/src/rules/pylint/rules/mod.rs @@ -37,6 +37,7 @@ pub use used_prior_global_declaration::{ }; pub use useless_else_on_loop::{useless_else_on_loop, UselessElseOnLoop}; pub use useless_import_alias::{useless_import_alias, UselessImportAlias}; +pub use useless_return::{useless_return, UselessReturn}; pub use yield_in_init::{yield_in_init, YieldInInit}; mod await_outside_async; @@ -71,4 +72,5 @@ mod use_from_import; mod used_prior_global_declaration; mod useless_else_on_loop; mod useless_import_alias; +mod useless_return; mod yield_in_init; diff --git a/crates/ruff/src/rules/pylint/rules/useless_return.rs b/crates/ruff/src/rules/pylint/rules/useless_return.rs new file mode 100644 index 0000000000000..3be3b36795605 --- /dev/null +++ b/crates/ruff/src/rules/pylint/rules/useless_return.rs @@ -0,0 +1,121 @@ +use log::error; +use rustpython_parser::ast::{Constant, ExprKind, Stmt, StmtKind}; + +use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::helpers::{is_const_none, ReturnStatementVisitor}; +use ruff_python_ast::types::{Range, RefEquality}; +use ruff_python_ast::visitor::Visitor; + +use crate::autofix::helpers::delete_stmt; +use crate::checkers::ast::Checker; +use crate::registry::AsRule; + +/// ## What it does +/// Checks for functions that end with an unnecessary `return` or +/// `return None`, and contain no other `return` statements. +/// +/// ## Why is this bad? +/// Python implicitly assumes a `None` return at the end of a function, making +/// it unnecessary to explicitly write `return None`. +/// +/// ## Example +/// ```python +/// def f(): +/// print(5) +/// return None +/// ``` +/// +/// Use instead: +/// ```python +/// def f(): +/// print(5) +/// ``` +#[violation] +pub struct UselessReturn; + +impl AlwaysAutofixableViolation for UselessReturn { + #[derive_message_formats] + fn message(&self) -> String { + format!("Useless `return` statement at end of function") + } + + fn autofix_title(&self) -> String { + format!("Remove useless `return` statement") + } +} + +/// PLR1711 +pub fn useless_return<'a>(checker: &mut Checker<'a>, stmt: &'a Stmt, body: &'a [Stmt]) { + // Skip empty functions. + if body.is_empty() { + return; + } + + // Find the last statement in the function. + let last_stmt = body.last().unwrap(); + if !matches!(last_stmt.node, StmtKind::Return { .. }) { + return; + } + + // Skip functions that consist of a single return statement. + if body.len() == 1 { + return; + } + + // Skip functions that consist of a docstring and a return statement. + if body.len() == 2 { + if let StmtKind::Expr { value } = &body[0].node { + if matches!( + value.node, + ExprKind::Constant { + value: Constant::Str(_), + .. + } + ) { + return; + } + } + } + + // Verify that the last statement is a return statement. + let StmtKind::Return { value} = &last_stmt.node else { + return; + }; + + // Verify that the return statement is either bare or returns `None`. + if !value.as_ref().map_or(true, |expr| is_const_none(expr)) { + return; + }; + + // Finally: verify that there are no _other_ return statements in the function. + let mut visitor = ReturnStatementVisitor::default(); + visitor.visit_body(body); + if visitor.returns.len() > 1 { + return; + } + + let mut diagnostic = Diagnostic::new(UselessReturn, Range::from(last_stmt)); + if checker.patch(diagnostic.kind.rule()) { + let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect(); + match delete_stmt( + last_stmt, + Some(stmt), + &deleted, + checker.locator, + checker.indexer, + checker.stylist, + ) { + Ok(fix) => { + if fix.content.is_empty() || fix.content == "pass" { + checker.deletions.insert(RefEquality(last_stmt)); + } + diagnostic.amend(fix); + } + Err(e) => { + error!("Failed to delete `return` statement: {}", e); + } + }; + } + checker.diagnostics.push(diagnostic); +} diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR1711_useless_return.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR1711_useless_return.py.snap new file mode 100644 index 0000000000000..46d84a2169dbf --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR1711_useless_return.py.snap @@ -0,0 +1,105 @@ +--- +source: crates/ruff/src/rules/pylint/mod.rs +expression: diagnostics +--- +- kind: + name: UselessReturn + body: "Useless `return` statement at end of function" + suggestion: "Remove useless `return` statement" + fixable: true + location: + row: 6 + column: 4 + end_location: + row: 6 + column: 15 + fix: + content: "" + location: + row: 6 + column: 0 + end_location: + row: 7 + column: 0 + parent: ~ +- kind: + name: UselessReturn + body: "Useless `return` statement at end of function" + suggestion: "Remove useless `return` statement" + fixable: true + location: + row: 11 + column: 4 + end_location: + row: 11 + column: 15 + fix: + content: "" + location: + row: 11 + column: 0 + end_location: + row: 12 + column: 0 + parent: ~ +- kind: + name: UselessReturn + body: "Useless `return` statement at end of function" + suggestion: "Remove useless `return` statement" + fixable: true + location: + row: 16 + column: 4 + end_location: + row: 16 + column: 15 + fix: + content: "" + location: + row: 16 + column: 0 + end_location: + row: 17 + column: 0 + parent: ~ +- kind: + name: UselessReturn + body: "Useless `return` statement at end of function" + suggestion: "Remove useless `return` statement" + fixable: true + location: + row: 22 + column: 8 + end_location: + row: 22 + column: 19 + fix: + content: "" + location: + row: 22 + column: 0 + end_location: + row: 23 + column: 0 + parent: ~ +- kind: + name: UselessReturn + body: "Useless `return` statement at end of function" + suggestion: "Remove useless `return` statement" + fixable: true + location: + row: 50 + column: 4 + end_location: + row: 50 + column: 15 + fix: + content: "" + location: + row: 50 + column: 0 + end_location: + row: 51 + column: 0 + parent: ~ + diff --git a/ruff.schema.json b/ruff.schema.json index 71296adabc6e1..9c76d61ca0462 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1887,6 +1887,8 @@ "PLR17", "PLR170", "PLR1701", + "PLR171", + "PLR1711", "PLR172", "PLR1722", "PLR2", From 0c4926ff7be0f251402edacb7a873ec104117ea0 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 17 Mar 2023 22:34:10 -0400 Subject: [PATCH 49/49] Bump version to v0.0.257 (#3591) --- Cargo.lock | 6 +++--- README.md | 2 +- crates/flake8_to_ruff/Cargo.toml | 2 +- crates/ruff/Cargo.toml | 2 +- crates/ruff_cli/Cargo.toml | 2 +- docs/tutorial.md | 2 +- docs/usage.md | 4 ++-- pyproject.toml | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bea0f1351772b..358421cebae70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -780,7 +780,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flake8-to-ruff" -version = "0.0.256" +version = "0.0.257" dependencies = [ "anyhow", "clap 4.1.8", @@ -1982,7 +1982,7 @@ dependencies = [ [[package]] name = "ruff" -version = "0.0.256" +version = "0.0.257" dependencies = [ "anyhow", "bisection", @@ -2063,7 +2063,7 @@ dependencies = [ [[package]] name = "ruff_cli" -version = "0.0.256" +version = "0.0.257" dependencies = [ "annotate-snippets 0.9.1", "anyhow", diff --git a/README.md b/README.md index 509255cc8ecce..d4dd3e6fabca8 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook: ```yaml - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.0.256' + rev: 'v0.0.257' hooks: - id: ruff ``` diff --git a/crates/flake8_to_ruff/Cargo.toml b/crates/flake8_to_ruff/Cargo.toml index b98d7e3e2ebe0..6019bea8dc2f7 100644 --- a/crates/flake8_to_ruff/Cargo.toml +++ b/crates/flake8_to_ruff/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flake8-to-ruff" -version = "0.0.256" +version = "0.0.257" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index 69fbdb42e574c..7b979c94080b8 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff" -version = "0.0.256" +version = "0.0.257" authors.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/crates/ruff_cli/Cargo.toml b/crates/ruff_cli/Cargo.toml index 9dc3adb332f1d..777f25a9ad269 100644 --- a/crates/ruff_cli/Cargo.toml +++ b/crates/ruff_cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_cli" -version = "0.0.256" +version = "0.0.257" authors = ["Charlie Marsh "] edition = { workspace = true } rust-version = { workspace = true } diff --git a/docs/tutorial.md b/docs/tutorial.md index 85fb1eff4b035..919dbe18e80ca 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -242,7 +242,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be ```yaml - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.0.256' + rev: 'v0.0.257' hooks: - id: ruff ``` diff --git a/docs/usage.md b/docs/usage.md index 743f5fe69e8ea..b8944cc0fdcff 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -20,7 +20,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook: ```yaml - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.0.256' + rev: 'v0.0.257' hooks: - id: ruff ``` @@ -30,7 +30,7 @@ Or, to enable autofix: ```yaml - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.0.256' + rev: 'v0.0.257' hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/pyproject.toml b/pyproject.toml index 8e4f0f5523189..b1315d92d589b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "maturin" [project] name = "ruff" -version = "0.0.256" +version = "0.0.257" description = "An extremely fast Python linter, written in Rust." authors = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }] maintainers = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }]