From 55f8f3b2cc64188393e978af8e80a7309714eda4 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 11 Jan 2024 17:13:00 -0500 Subject: [PATCH 01/10] Bump version to v0.1.12 (#9475) --- CHANGELOG.md | 52 +++++++++++++++++++++++++++++++ Cargo.lock | 6 ++-- README.md | 2 +- crates/ruff_cli/Cargo.toml | 2 +- crates/ruff_linter/Cargo.toml | 2 +- crates/ruff_shrinking/Cargo.toml | 2 +- docs/integrations.md | 6 ++-- pyproject.toml | 2 +- scripts/benchmarks/pyproject.toml | 2 +- 9 files changed, 64 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 855ca109a5534..ddb00205aeb5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,57 @@ # Changelog +## 0.1.12 + +### Preview features + +- Formatter: Hug multiline-strings in preview style ([#9243](https://github.com/astral-sh/ruff/pull/9243)) +- \[`flake8-bandit`\] Add `ssl-with-no-version` (`S504`) ([#9384](https://github.com/astral-sh/ruff/pull/9384)) +- \[`flake8-bandit`\] Implement `ssl-insecure-version` (`S502`) ([#9390](https://github.com/astral-sh/ruff/pull/9390)) +- \[`flake8-bandit`\] Implement `ssl-with-bad-defaults` (`S503`) ([#9391](https://github.com/astral-sh/ruff/pull/9391)) +- \[`flake8-bandit`\] Implement suspicious import rules (`S4XX`) ([#8831](https://github.com/astral-sh/ruff/pull/8831)) +- \[`flake8-simplify`\] Implement `zip-dict-keys-and-values` (`SIM911`) ([#9460](https://github.com/astral-sh/ruff/pull/9460)) +- \[`pyflakes`\] Add a fix for `redefined-while-unused` (`F811`) ([#9419](https://github.com/astral-sh/ruff/pull/9419)) +- \[`pylint`\] Implement `unnecessary-dunder-call` (`C2801`) ([#9166](https://github.com/astral-sh/ruff/pull/9166)) +- \[`ruff`\] Add `parenthesize-chained-operators` (`RUF021`) to enforce parentheses in `a or b and c` ([#9440](https://github.com/astral-sh/ruff/pull/9440)) + +### Rule changes + +- \[`flake8-boolean-trap`\] Allow Boolean positional arguments in setters ([#9429](https://github.com/astral-sh/ruff/pull/9429)) +- \[`flake8-builtins`\] Restrict `builtin-attribute-shadowing` (`A003`) to actual shadowed references ([#9462](https://github.com/astral-sh/ruff/pull/9462)) +- \[`flake8-pyi`\] Add fix for `generator-return-from-iter-method` (`PYI058`) ([#9355](https://github.com/astral-sh/ruff/pull/9355)) +- \[`pyflakes`\] Don't flag `redefined-while-unused` (`F811`) in `if` branches ([#9418](https://github.com/astral-sh/ruff/pull/9418)) +- \[`pyupgrade`\] Add some additional Python 3.12 typing members to `deprecated-import` ([#9445](https://github.com/astral-sh/ruff/pull/9445)) +- \[`ruff`\] Add fix for `parenthesize-chained-operators` (`RUF021`) ([#9449](https://github.com/astral-sh/ruff/pull/9449)) +- \[`ruff`\] Include subscripts and attributes in static key rule (`RUF011`) ([#9416](https://github.com/astral-sh/ruff/pull/9416)) +- \[`ruff`\] Support variable keys in static dictionary key rule (`RUF011`) ([#9411](https://github.com/astral-sh/ruff/pull/9411)) + +### Formatter + +- Generate deterministic IDs when formatting notebooks ([#9359](https://github.com/astral-sh/ruff/pull/9359)) +- Allow `# fmt: skip` with interspersed same-line comments ([#9395](https://github.com/astral-sh/ruff/pull/9395)) +- Parenthesize breaking named expressions in match guards ([#9396](https://github.com/astral-sh/ruff/pull/9396)) + +### Bug fixes + +- Add cell indexes to all diagnostics ([#9387](https://github.com/astral-sh/ruff/pull/9387)) +- Avoid infinite loop in constant vs. `None` comparisons ([#9376](https://github.com/astral-sh/ruff/pull/9376)) +- Handle raises with implicit alternate branches ([#9377](https://github.com/astral-sh/ruff/pull/9377)) +- Ignore trailing quotes for unclosed l-brace errors ([#9388](https://github.com/astral-sh/ruff/pull/9388)) +- Respect multi-segment submodule imports when resolving qualified names ([#9382](https://github.com/astral-sh/ruff/pull/9382)) +- Use `DisplayParseError` for stdin parser errors ([#9409](https://github.com/astral-sh/ruff/pull/9409)) +- Use `comment_ranges` for isort directive extraction ([#9414](https://github.com/astral-sh/ruff/pull/9414)) +- Use transformed source code for diagnostic locations ([#9408](https://github.com/astral-sh/ruff/pull/9408)) +- \[`flake8-pyi`\] Exclude `warnings.deprecated` and `typing_extensions.deprecated` arguments ([#9423](https://github.com/astral-sh/ruff/pull/9423)) +- \[`flake8-pyi`\] Fix false negative for `unused-private-protocol` (`PYI046`) with unused generic protocols ([#9405](https://github.com/astral-sh/ruff/pull/9405)) +- \[`pydocstyle`\] Disambiguate argument descriptors from section headers ([#9427](https://github.com/astral-sh/ruff/pull/9427)) +- \[`pylint`\] Homogenize `PLR0914` message to match other `PLR09XX` rules ([#9399](https://github.com/astral-sh/ruff/pull/9399)) +- \[`ruff`\] Allow `Hashable = None` in type annotations (`RUF013`) ([#9442](https://github.com/astral-sh/ruff/pull/9442)) + +### Documentation + +- Fix admonition hyperlink colouring ([#9385](https://github.com/astral-sh/ruff/pull/9385)) +- Add missing preview link ([#9386](https://github.com/astral-sh/ruff/pull/9386)) + ## 0.1.11 ### Preview features diff --git a/Cargo.lock b/Cargo.lock index cb3c2dc518625..c72947bb034ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2038,7 +2038,7 @@ dependencies = [ [[package]] name = "ruff_cli" -version = "0.1.11" +version = "0.1.12" dependencies = [ "anyhow", "argfile", @@ -2165,7 +2165,7 @@ dependencies = [ [[package]] name = "ruff_linter" -version = "0.1.11" +version = "0.1.12" dependencies = [ "aho-corasick", "annotate-snippets 0.9.2", @@ -2417,7 +2417,7 @@ dependencies = [ [[package]] name = "ruff_shrinking" -version = "0.1.11" +version = "0.1.12" dependencies = [ "anyhow", "clap", diff --git a/README.md b/README.md index 89938480b1fc1..4813b5a6c376a 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.1.11 + rev: v0.1.12 hooks: # Run the linter. - id: ruff diff --git a/crates/ruff_cli/Cargo.toml b/crates/ruff_cli/Cargo.toml index 6239d783777b8..cc4aabeee250f 100644 --- a/crates/ruff_cli/Cargo.toml +++ b/crates/ruff_cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_cli" -version = "0.1.11" +version = "0.1.12" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index 6bc6f9f2f4b2b..30773a734a856 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_linter" -version = "0.1.11" +version = "0.1.12" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_shrinking/Cargo.toml b/crates/ruff_shrinking/Cargo.toml index 11fc479599194..d7b6f3e4b073a 100644 --- a/crates/ruff_shrinking/Cargo.toml +++ b/crates/ruff_shrinking/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_shrinking" -version = "0.1.11" +version = "0.1.12" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/docs/integrations.md b/docs/integrations.md index c57f9d3250df7..9675f11958cb0 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -14,7 +14,7 @@ Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-c ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.1.11 + rev: v0.1.12 hooks: # Run the linter. - id: ruff @@ -27,7 +27,7 @@ To enable lint fixes, add the `--fix` argument to the lint hook: ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.1.11 + rev: v0.1.12 hooks: # Run the linter. - id: ruff @@ -41,7 +41,7 @@ To run the hooks over Jupyter Notebooks too, add `jupyter` to the list of allowe ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.1.11 + rev: v0.1.12 hooks: # Run the linter. - id: ruff diff --git a/pyproject.toml b/pyproject.toml index 35bc607452619..cff313b8b6954 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "ruff" -version = "0.1.11" +version = "0.1.12" description = "An extremely fast Python linter and code formatter, written in Rust." authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] readme = "README.md" diff --git a/scripts/benchmarks/pyproject.toml b/scripts/benchmarks/pyproject.toml index 38d189688eb30..f050ed4413ede 100644 --- a/scripts/benchmarks/pyproject.toml +++ b/scripts/benchmarks/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "scripts" -version = "0.1.11" +version = "0.1.12" description = "" authors = ["Charles Marsh "] From 350dcb807ac3e1f58b895149a7f8e4c680ffaa5a Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 11 Jan 2024 19:18:49 -0500 Subject: [PATCH 02/10] Include base pyproject when initializing cache settings (#9480) ## Summary Regression from https://github.com/astral-sh/ruff/pull/9453/files#diff-80a9c2637c432502a7075c792cc60db92282dd786999a78bfa9bb6f025afab35L482. Closes https://github.com/astral-sh/ruff/issues/9478. ## Test Plan ``` rm -rf .ruff_cache cargo run -p ruff_cli -- check ../foo.py ``` Failed prior to this PR; passes afterwards. The file must be outside of the current working directory, and must not have a `pyproject.toml` in any parent directory. --- crates/ruff_workspace/src/resolver.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/ruff_workspace/src/resolver.rs b/crates/ruff_workspace/src/resolver.rs index 590eb3e2d7f83..d9bd592059672 100644 --- a/crates/ruff_workspace/src/resolver.rs +++ b/crates/ruff_workspace/src/resolver.rs @@ -11,7 +11,7 @@ use anyhow::Result; use anyhow::{anyhow, bail}; use globset::{Candidate, GlobSet}; use ignore::{WalkBuilder, WalkState}; -use itertools::{Either, Itertools}; +use itertools::Itertools; use log::debug; use path_absolutize::path_dedot; use rustc_hash::{FxHashMap, FxHashSet}; @@ -204,12 +204,7 @@ impl<'a> Resolver<'a> { /// Return an iterator over the resolved [`Settings`] in this [`Resolver`]. pub fn settings(&self) -> impl Iterator { - match self.pyproject_config.strategy { - PyprojectDiscoveryStrategy::Fixed => { - Either::Left(std::iter::once(&self.pyproject_config.settings)) - } - PyprojectDiscoveryStrategy::Hierarchical => Either::Right(self.settings.values()), - } + std::iter::once(&self.pyproject_config.settings).chain(self.settings.values()) } } From f9dd7bb190e0fd4d85ab1f908ae4c2bb8a1ffef0 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 11 Jan 2024 20:24:57 -0500 Subject: [PATCH 03/10] Remove `unreachable-code` feature (#9463) ## Summary We haven't found time to flip this on, so feels like it's best to remove it for now -- can always restore from source when we get back to it. --- crates/ruff_linter/Cargo.toml | 2 - .../resources/test/fixtures/ruff/RUF014.py | 185 --- .../src/checkers/ast/analyze/statement.rs | 6 - crates/ruff_linter/src/codes.rs | 3 - crates/ruff_linter/src/rule_selector.rs | 59 +- crates/ruff_linter/src/rules/ruff/mod.rs | 4 - .../ruff_linter/src/rules/ruff/rules/mod.rs | 4 - ...les__unreachable__tests__assert.py.md.snap | 97 -- ...__unreachable__tests__async-for.py.md.snap | 241 ---- ..._rules__unreachable__tests__for.py.md.snap | 302 ----- ...__rules__unreachable__tests__if.py.md.snap | 553 -------- ...ules__unreachable__tests__match.py.md.snap | 815 ------------ ...ules__unreachable__tests__raise.py.md.snap | 41 - ...les__unreachable__tests__simple.py.md.snap | 136 -- ...ules__unreachable__tests__while.py.md.snap | 527 -------- .../src/rules/ruff/rules/unreachable.rs | 1114 ----------------- ..._rules__ruff__tests__RUF014_RUF014.py.snap | 249 ---- crates/ruff_workspace/src/configuration.rs | 2 - 18 files changed, 22 insertions(+), 4318 deletions(-) delete mode 100644 crates/ruff_linter/resources/test/fixtures/ruff/RUF014.py delete mode 100644 crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__assert.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__async-for.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__for.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__if.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__match.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__raise.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__simple.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__while.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/ruff/rules/unreachable.rs delete mode 100644 crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF014_RUF014.py.snap diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index 30773a734a856..1232ac8fb11bf 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -85,8 +85,6 @@ tempfile = { workspace = true } [features] default = [] schemars = ["dep:schemars"] -# Enables the UnreachableCode rule -unreachable-code = [] [lints] workspace = true diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF014.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF014.py deleted file mode 100644 index d1ae40f3ca864..0000000000000 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF014.py +++ /dev/null @@ -1,185 +0,0 @@ -def after_return(): - return "reachable" - return "unreachable" - -async def also_works_on_async_functions(): - return "reachable" - return "unreachable" - -def if_always_true(): - if True: - return "reachable" - return "unreachable" - -def if_always_false(): - if False: - return "unreachable" - return "reachable" - -def if_elif_always_false(): - if False: - return "unreachable" - elif False: - return "also unreachable" - return "reachable" - -def if_elif_always_true(): - if False: - return "unreachable" - elif True: - return "reachable" - return "also unreachable" - -def ends_with_if(): - if False: - return "unreachable" - else: - return "reachable" - -def infinite_loop(): - while True: - continue - return "unreachable" - -''' TODO: we could determine these, but we don't yet. -def for_range_return(): - for i in range(10): - if i == 5: - return "reachable" - return "unreachable" - -def for_range_else(): - for i in range(111): - if i == 5: - return "reachable" - else: - return "unreachable" - return "also unreachable" - -def for_range_break(): - for i in range(13): - return "reachable" - return "unreachable" - -def for_range_if_break(): - for i in range(1110): - if True: - return "reachable" - return "unreachable" -''' - -def match_wildcard(status): - match status: - case _: - return "reachable" - return "unreachable" - -def match_case_and_wildcard(status): - match status: - case 1: - return "reachable" - case _: - return "reachable" - return "unreachable" - -def raise_exception(): - raise Exception - return "unreachable" - -def while_false(): - while False: - return "unreachable" - return "reachable" - -def while_false_else(): - while False: - return "unreachable" - else: - return "reachable" - -def while_false_else_return(): - while False: - return "unreachable" - else: - return "reachable" - return "also unreachable" - -def while_true(): - while True: - return "reachable" - return "unreachable" - -def while_true_else(): - while True: - return "reachable" - else: - return "unreachable" - -def while_true_else_return(): - while True: - return "reachable" - else: - return "unreachable" - return "also unreachable" - -def while_false_var_i(): - i = 0 - while False: - i += 1 - return i - -def while_true_var_i(): - i = 0 - while True: - i += 1 - return i - -def while_infinite(): - while True: - pass - return "unreachable" - -def while_if_true(): - while True: - if True: - return "reachable" - return "unreachable" - -# Test case found in the Bokeh repository that trigger a false positive. -def bokeh1(self, obj: BytesRep) -> bytes: - data = obj["data"] - - if isinstance(data, str): - return base64.b64decode(data) - elif isinstance(data, Buffer): - buffer = data - else: - id = data["id"] - - if id in self._buffers: - buffer = self._buffers[id] - else: - self.error(f"can't resolve buffer '{id}'") - - return buffer.data - -''' -TODO: because `try` statements aren't handled this triggers a false positive as -the last statement is reached, but the rules thinks it isn't (it doesn't -see/process the break statement). - -# Test case found in the Bokeh repository that trigger a false positive. -def bokeh2(self, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> None: - self.stop_serving = False - while True: - try: - self.server = HTTPServer((host, port), HtmlOnlyHandler) - self.host = host - self.port = port - break - except OSError: - log.debug(f"port {port} is in use, trying to next one") - port += 1 - - self.thread = threading.Thread(target=self._run_web_server) -''' diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 5d8c4294165fd..ff764de5f8977 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -355,12 +355,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { if checker.enabled(Rule::TrioAsyncFunctionWithTimeout) { flake8_trio::rules::async_function_with_timeout(checker, function_def); } - #[cfg(feature = "unreachable-code")] - if checker.enabled(Rule::UnreachableCode) { - checker - .diagnostics - .extend(ruff::rules::unreachable::in_function(name, body)); - } if checker.enabled(Rule::ReimplementedOperator) { refurb::rules::reimplemented_operator(checker, &function_def.into()); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 62a38967271c6..1f24c4f1866fa 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -914,9 +914,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "011") => (RuleGroup::Stable, rules::ruff::rules::StaticKeyDictComprehension), (Ruff, "012") => (RuleGroup::Stable, rules::ruff::rules::MutableClassDefault), (Ruff, "013") => (RuleGroup::Stable, rules::ruff::rules::ImplicitOptional), - #[cfg(feature = "unreachable-code")] // When removing this feature gate, also update rules_selector.rs - #[allow(deprecated)] - (Ruff, "014") => (RuleGroup::Nursery, rules::ruff::rules::UnreachableCode), (Ruff, "015") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryIterableAllocationForFirstElement), (Ruff, "016") => (RuleGroup::Stable, rules::ruff::rules::InvalidIndexType), #[allow(deprecated)] diff --git a/crates/ruff_linter/src/rule_selector.rs b/crates/ruff_linter/src/rule_selector.rs index ea83c058ed014..a5ee86ea23353 100644 --- a/crates/ruff_linter/src/rule_selector.rs +++ b/crates/ruff_linter/src/rule_selector.rs @@ -289,9 +289,6 @@ mod schema { (!prefix.is_empty()).then(|| prefix.to_string()) })), ) - // Filter out rule gated behind `#[cfg(feature = "unreachable-code")]`, which is - // off-by-default - .filter(|prefix| prefix != "RUF014") .sorted() .map(Value::String) .collect(), @@ -407,40 +404,28 @@ pub mod clap_completion { let prefix = l.common_prefix(); (!prefix.is_empty()).then(|| PossibleValue::new(prefix).help(l.name())) }) - .chain( - RuleCodePrefix::iter() - // Filter out rule gated behind `#[cfg(feature = "unreachable-code")]`, which is - // off-by-default - .filter(|prefix| { - format!( - "{}{}", - prefix.linter().common_prefix(), - prefix.short_code() - ) != "RUF014" - }) - .filter_map(|prefix| { - // Ex) `UP` - if prefix.short_code().is_empty() { - let code = prefix.linter().common_prefix(); - let name = prefix.linter().name(); - return Some(PossibleValue::new(code).help(name)); - } - - // Ex) `UP004` - if is_single_rule_selector(&prefix) { - let rule = prefix.rules().next()?; - let code = format!( - "{}{}", - prefix.linter().common_prefix(), - prefix.short_code() - ); - let name: &'static str = rule.into(); - return Some(PossibleValue::new(code).help(name)); - } - - None - }), - ), + .chain(RuleCodePrefix::iter().filter_map(|prefix| { + // Ex) `UP` + if prefix.short_code().is_empty() { + let code = prefix.linter().common_prefix(); + let name = prefix.linter().name(); + return Some(PossibleValue::new(code).help(name)); + } + + // Ex) `UP004` + if is_single_rule_selector(&prefix) { + let rule = prefix.rules().next()?; + let code = format!( + "{}{}", + prefix.linter().common_prefix(), + prefix.short_code() + ); + let name: &'static str = rule.into(); + return Some(PossibleValue::new(code).help(name)); + } + + None + })), ), )) } diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index bda96268fdbae..837abed45fd4e 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -37,10 +37,6 @@ mod tests { Path::new("RUF015.py") )] #[test_case(Rule::InvalidIndexType, Path::new("RUF016.py"))] - #[cfg_attr( - feature = "unreachable-code", - test_case(Rule::UnreachableCode, Path::new("RUF014.py")) - )] #[test_case(Rule::QuadraticListSummation, Path::new("RUF017_1.py"))] #[test_case(Rule::QuadraticListSummation, Path::new("RUF017_0.py"))] #[test_case(Rule::AssignmentInAssert, Path::new("RUF018.py"))] diff --git a/crates/ruff_linter/src/rules/ruff/rules/mod.rs b/crates/ruff_linter/src/rules/ruff/rules/mod.rs index 37bc4e9c91260..1a89cb6a71fac 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mod.rs @@ -16,8 +16,6 @@ pub(crate) use quadratic_list_summation::*; pub(crate) use static_key_dict_comprehension::*; pub(crate) use unnecessary_iterable_allocation_for_first_element::*; pub(crate) use unnecessary_key_check::*; -#[cfg(feature = "unreachable-code")] -pub(crate) use unreachable::*; pub(crate) use unused_noqa::*; mod ambiguous_unicode_character; @@ -39,8 +37,6 @@ mod parenthesize_logical_operators; mod static_key_dict_comprehension; mod unnecessary_iterable_allocation_for_first_element; mod unnecessary_key_check; -#[cfg(feature = "unreachable-code")] -pub(crate) mod unreachable; mod unused_noqa; #[derive(Clone, Copy)] diff --git a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__assert.py.md.snap b/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__assert.py.md.snap deleted file mode 100644 index 50dcab6daa13b..0000000000000 --- a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__assert.py.md.snap +++ /dev/null @@ -1,97 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/ruff/rules/unreachable.rs -description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." ---- -## Function 0 -### Source -```python -def func(): - assert True -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1[["Exception raised"]] - block2["assert True\n"] - - start --> block2 - block2 -- "True" --> block0 - block2 -- "else" --> block1 - block1 --> return - block0 --> return -``` - -## Function 1 -### Source -```python -def func(): - assert False -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1[["Exception raised"]] - block2["assert False\n"] - - start --> block2 - block2 -- "False" --> block0 - block2 -- "else" --> block1 - block1 --> return - block0 --> return -``` - -## Function 2 -### Source -```python -def func(): - assert True, "oops" -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1[["Exception raised"]] - block2["assert True, #quot;oops#quot;\n"] - - start --> block2 - block2 -- "True" --> block0 - block2 -- "else" --> block1 - block1 --> return - block0 --> return -``` - -## Function 3 -### Source -```python -def func(): - assert False, "oops" -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1[["Exception raised"]] - block2["assert False, #quot;oops#quot;\n"] - - start --> block2 - block2 -- "False" --> block0 - block2 -- "else" --> block1 - block1 --> return - block0 --> return -``` - - diff --git a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__async-for.py.md.snap b/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__async-for.py.md.snap deleted file mode 100644 index 2847a7d87366b..0000000000000 --- a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__async-for.py.md.snap +++ /dev/null @@ -1,241 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/ruff/rules/unreachable.rs -description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." ---- -## Function 0 -### Source -```python -def func(): - async for i in range(5): - print(i) -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["print(i)\n"] - block2["async for i in range(5): - print(i)\n"] - - start --> block2 - block2 -- "range(5)" --> block1 - block2 -- "else" --> block0 - block1 --> block2 - block0 --> return -``` - -## Function 1 -### Source -```python -def func(): - async for i in range(20): - print(i) - else: - return 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["print(i)\n"] - block1["return 0\n"] - block2["async for i in range(20): - print(i) - else: - return 0\n"] - - start --> block2 - block2 -- "range(20)" --> block0 - block2 -- "else" --> block1 - block1 --> return - block0 --> return -``` - -## Function 2 -### Source -```python -def func(): - async for i in range(10): - if i == 5: - return 1 - return 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 0\n"] - block1["return 1\n"] - block2["if i == 5: - return 1\n"] - block3["async for i in range(10): - if i == 5: - return 1\n"] - - start --> block3 - block3 -- "range(10)" --> block2 - block3 -- "else" --> block0 - block2 -- "i == 5" --> block1 - block2 -- "else" --> block3 - block1 --> return - block0 --> return -``` - -## Function 3 -### Source -```python -def func(): - async for i in range(111): - if i == 5: - return 1 - else: - return 0 - return 2 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 2\n"] - block1["return 1\n"] - block2["if i == 5: - return 1\n"] - block3["return 0\n"] - block4["async for i in range(111): - if i == 5: - return 1 - else: - return 0\n"] - - start --> block4 - block4 -- "range(111)" --> block2 - block4 -- "else" --> block3 - block3 --> return - block2 -- "i == 5" --> block1 - block2 -- "else" --> block4 - block1 --> return - block0 --> return -``` - -## Function 4 -### Source -```python -def func(): - async for i in range(12): - continue -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["continue\n"] - block2["async for i in range(12): - continue\n"] - - start --> block2 - block2 -- "range(12)" --> block1 - block2 -- "else" --> block0 - block1 --> block2 - block0 --> return -``` - -## Function 5 -### Source -```python -def func(): - async for i in range(1110): - if True: - continue -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["continue\n"] - block2["if True: - continue\n"] - block3["async for i in range(1110): - if True: - continue\n"] - - start --> block3 - block3 -- "range(1110)" --> block2 - block3 -- "else" --> block0 - block2 -- "True" --> block1 - block2 -- "else" --> block3 - block1 --> block3 - block0 --> return -``` - -## Function 6 -### Source -```python -def func(): - async for i in range(13): - break -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["break\n"] - block2["async for i in range(13): - break\n"] - - start --> block2 - block2 -- "range(13)" --> block1 - block2 -- "else" --> block0 - block1 --> block0 - block0 --> return -``` - -## Function 7 -### Source -```python -def func(): - async for i in range(1110): - if True: - break -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["break\n"] - block2["if True: - break\n"] - block3["async for i in range(1110): - if True: - break\n"] - - start --> block3 - block3 -- "range(1110)" --> block2 - block3 -- "else" --> block0 - block2 -- "True" --> block1 - block2 -- "else" --> block3 - block1 --> block0 - block0 --> return -``` - - diff --git a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__for.py.md.snap b/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__for.py.md.snap deleted file mode 100644 index 6850c5f69b04b..0000000000000 --- a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__for.py.md.snap +++ /dev/null @@ -1,302 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/ruff/rules/unreachable.rs -description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." ---- -## Function 0 -### Source -```python -def func(): - for i in range(5): - print(i) -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["print(i)\n"] - block2["for i in range(5): - print(i)\n"] - - start --> block2 - block2 -- "range(5)" --> block1 - block2 -- "else" --> block0 - block1 --> block2 - block0 --> return -``` - -## Function 1 -### Source -```python -def func(): - for i in range(20): - print(i) - else: - return 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["print(i)\n"] - block1["return 0\n"] - block2["for i in range(20): - print(i) - else: - return 0\n"] - - start --> block2 - block2 -- "range(20)" --> block0 - block2 -- "else" --> block1 - block1 --> return - block0 --> return -``` - -## Function 2 -### Source -```python -def func(): - for i in range(10): - if i == 5: - return 1 - return 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 0\n"] - block1["return 1\n"] - block2["if i == 5: - return 1\n"] - block3["for i in range(10): - if i == 5: - return 1\n"] - - start --> block3 - block3 -- "range(10)" --> block2 - block3 -- "else" --> block0 - block2 -- "i == 5" --> block1 - block2 -- "else" --> block3 - block1 --> return - block0 --> return -``` - -## Function 3 -### Source -```python -def func(): - for i in range(111): - if i == 5: - return 1 - else: - return 0 - return 2 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 2\n"] - block1["return 1\n"] - block2["if i == 5: - return 1\n"] - block3["return 0\n"] - block4["for i in range(111): - if i == 5: - return 1 - else: - return 0\n"] - - start --> block4 - block4 -- "range(111)" --> block2 - block4 -- "else" --> block3 - block3 --> return - block2 -- "i == 5" --> block1 - block2 -- "else" --> block4 - block1 --> return - block0 --> return -``` - -## Function 4 -### Source -```python -def func(): - for i in range(12): - continue -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["continue\n"] - block2["for i in range(12): - continue\n"] - - start --> block2 - block2 -- "range(12)" --> block1 - block2 -- "else" --> block0 - block1 --> block2 - block0 --> return -``` - -## Function 5 -### Source -```python -def func(): - for i in range(1110): - if True: - continue -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["continue\n"] - block2["if True: - continue\n"] - block3["for i in range(1110): - if True: - continue\n"] - - start --> block3 - block3 -- "range(1110)" --> block2 - block3 -- "else" --> block0 - block2 -- "True" --> block1 - block2 -- "else" --> block3 - block1 --> block3 - block0 --> return -``` - -## Function 6 -### Source -```python -def func(): - for i in range(13): - break -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["break\n"] - block2["for i in range(13): - break\n"] - - start --> block2 - block2 -- "range(13)" --> block1 - block2 -- "else" --> block0 - block1 --> block0 - block0 --> return -``` - -## Function 7 -### Source -```python -def func(): - for i in range(1110): - if True: - break -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["break\n"] - block2["if True: - break\n"] - block3["for i in range(1110): - if True: - break\n"] - - start --> block3 - block3 -- "range(1110)" --> block2 - block3 -- "else" --> block0 - block2 -- "True" --> block1 - block2 -- "else" --> block3 - block1 --> block0 - block0 --> return -``` - -## Function 8 -### Source -```python -def func(): - for i in range(5): - pass - else: - return 1 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["pass\n"] - block1["return 1\n"] - block2["for i in range(5): - pass - else: - return 1\n"] - - start --> block2 - block2 -- "range(5)" --> block0 - block2 -- "else" --> block1 - block1 --> return - block0 --> return -``` - -## Function 9 -### Source -```python -def func(): - for i in range(5): - pass - else: - return 1 - x = 1 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["x = 1\n"] - block1["pass\n"] - block2["return 1\n"] - block3["for i in range(5): - pass - else: - return 1\n"] - - start --> block3 - block3 -- "range(5)" --> block1 - block3 -- "else" --> block2 - block2 --> return - block1 --> block3 - block0 --> return -``` - - diff --git a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__if.py.md.snap b/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__if.py.md.snap deleted file mode 100644 index 6899a85774260..0000000000000 --- a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__if.py.md.snap +++ /dev/null @@ -1,553 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/ruff/rules/unreachable.rs -description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." ---- -## Function 0 -### Source -```python -def func(): - if False: - return 0 - return 1 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 1\n"] - block1["return 0\n"] - block2["if False: - return 0\n"] - - start --> block2 - block2 -- "False" --> block1 - block2 -- "else" --> block0 - block1 --> return - block0 --> return -``` - -## Function 1 -### Source -```python -def func(): - if True: - return 1 - return 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 0\n"] - block1["return 1\n"] - block2["if True: - return 1\n"] - - start --> block2 - block2 -- "True" --> block1 - block2 -- "else" --> block0 - block1 --> return - block0 --> return -``` - -## Function 2 -### Source -```python -def func(): - if False: - return 0 - else: - return 1 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 0\n"] - block1["return 1\n"] - block2["if False: - return 0 - else: - return 1\n"] - - start --> block2 - block2 -- "False" --> block0 - block2 -- "else" --> block1 - block1 --> return - block0 --> return -``` - -## Function 3 -### Source -```python -def func(): - if True: - return 1 - else: - return 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 1\n"] - block1["return 0\n"] - block2["if True: - return 1 - else: - return 0\n"] - - start --> block2 - block2 -- "True" --> block0 - block2 -- "else" --> block1 - block1 --> return - block0 --> return -``` - -## Function 4 -### Source -```python -def func(): - if False: - return 0 - else: - return 1 - return "unreachable" -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return #quot;unreachable#quot;\n"] - block1["return 0\n"] - block2["return 1\n"] - block3["if False: - return 0 - else: - return 1\n"] - - start --> block3 - block3 -- "False" --> block1 - block3 -- "else" --> block2 - block2 --> return - block1 --> return - block0 --> return -``` - -## Function 5 -### Source -```python -def func(): - if True: - return 1 - else: - return 0 - return "unreachable" -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return #quot;unreachable#quot;\n"] - block1["return 1\n"] - block2["return 0\n"] - block3["if True: - return 1 - else: - return 0\n"] - - start --> block3 - block3 -- "True" --> block1 - block3 -- "else" --> block2 - block2 --> return - block1 --> return - block0 --> return -``` - -## Function 6 -### Source -```python -def func(): - if True: - if True: - return 1 - return 2 - else: - return 3 - return "unreachable2" -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return #quot;unreachable2#quot;\n"] - block1["return 2\n"] - block2["return 1\n"] - block3["if True: - return 1\n"] - block4["return 3\n"] - block5["if True: - if True: - return 1 - return 2 - else: - return 3\n"] - - start --> block5 - block5 -- "True" --> block3 - block5 -- "else" --> block4 - block4 --> return - block3 -- "True" --> block2 - block3 -- "else" --> block1 - block2 --> return - block1 --> return - block0 --> return -``` - -## Function 7 -### Source -```python -def func(): - if False: - return 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["return 0\n"] - block2["if False: - return 0\n"] - - start --> block2 - block2 -- "False" --> block1 - block2 -- "else" --> block0 - block1 --> return - block0 --> return -``` - -## Function 8 -### Source -```python -def func(): - if True: - return 1 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["return 1\n"] - block2["if True: - return 1\n"] - - start --> block2 - block2 -- "True" --> block1 - block2 -- "else" --> block0 - block1 --> return - block0 --> return -``` - -## Function 9 -### Source -```python -def func(): - if True: - return 1 - elif False: - return 2 - else: - return 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 1\n"] - block1["return 0\n"] - block2["return 2\n"] - block3["if True: - return 1 - elif False: - return 2 - else: - return 0\n"] - block4["if True: - return 1 - elif False: - return 2 - else: - return 0\n"] - - start --> block4 - block4 -- "True" --> block0 - block4 -- "else" --> block3 - block3 -- "False" --> block2 - block3 -- "else" --> block1 - block2 --> return - block1 --> return - block0 --> return -``` - -## Function 10 -### Source -```python -def func(): - if False: - return 1 - elif True: - return 2 - else: - return 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 1\n"] - block1["return 0\n"] - block2["return 2\n"] - block3["if False: - return 1 - elif True: - return 2 - else: - return 0\n"] - block4["if False: - return 1 - elif True: - return 2 - else: - return 0\n"] - - start --> block4 - block4 -- "False" --> block0 - block4 -- "else" --> block3 - block3 -- "True" --> block2 - block3 -- "else" --> block1 - block2 --> return - block1 --> return - block0 --> return -``` - -## Function 11 -### Source -```python -def func(): - if True: - if False: - return 0 - elif True: - return 1 - else: - return 2 - return 3 - elif True: - return 4 - else: - return 5 - return 6 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 6\n"] - block1["return 3\n"] - block2["return 0\n"] - block3["return 2\n"] - block4["return 1\n"] - block5["if False: - return 0 - elif True: - return 1 - else: - return 2\n"] - block6["if False: - return 0 - elif True: - return 1 - else: - return 2\n"] - block7["return 5\n"] - block8["return 4\n"] - block9["if True: - if False: - return 0 - elif True: - return 1 - else: - return 2 - return 3 - elif True: - return 4 - else: - return 5\n"] - block10["if True: - if False: - return 0 - elif True: - return 1 - else: - return 2 - return 3 - elif True: - return 4 - else: - return 5\n"] - - start --> block10 - block10 -- "True" --> block6 - block10 -- "else" --> block9 - block9 -- "True" --> block8 - block9 -- "else" --> block7 - block8 --> return - block7 --> return - block6 -- "False" --> block2 - block6 -- "else" --> block5 - block5 -- "True" --> block4 - block5 -- "else" --> block3 - block4 --> return - block3 --> return - block2 --> return - block1 --> return - block0 --> return -``` - -## Function 12 -### Source -```python -def func(): - if False: - return "unreached" - elif False: - return "also unreached" - return "reached" -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return #quot;reached#quot;\n"] - block1["return #quot;unreached#quot;\n"] - block2["return #quot;also unreached#quot;\n"] - block3["if False: - return #quot;unreached#quot; - elif False: - return #quot;also unreached#quot;\n"] - block4["if False: - return #quot;unreached#quot; - elif False: - return #quot;also unreached#quot;\n"] - - start --> block4 - block4 -- "False" --> block1 - block4 -- "else" --> block3 - block3 -- "False" --> block2 - block3 -- "else" --> block0 - block2 --> return - block1 --> return - block0 --> return -``` - -## Function 13 -### Source -```python -def func(self, obj: BytesRep) -> bytes: - data = obj["data"] - - if isinstance(data, str): - return base64.b64decode(data) - elif isinstance(data, Buffer): - buffer = data - else: - id = data["id"] - - if id in self._buffers: - buffer = self._buffers[id] - else: - self.error(f"can't resolve buffer '{id}'") - - return buffer.data -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return buffer.data\n"] - block1["return base64.b64decode(data)\n"] - block2["buffer = self._buffers[id]\n"] - block3["self.error(f#quot;can't resolve buffer '{id}'#quot;)\n"] - block4["id = data[#quot;id#quot;]\nif id in self._buffers: - buffer = self._buffers[id] - else: - self.error(f#quot;can't resolve buffer '{id}'#quot;)\n"] - block5["buffer = data\n"] - block6["if isinstance(data, str): - return base64.b64decode(data) - elif isinstance(data, Buffer): - buffer = data - else: - id = data[#quot;id#quot;] - - if id in self._buffers: - buffer = self._buffers[id] - else: - self.error(f#quot;can't resolve buffer '{id}'#quot;)\n"] - block7["data = obj[#quot;data#quot;]\nif isinstance(data, str): - return base64.b64decode(data) - elif isinstance(data, Buffer): - buffer = data - else: - id = data[#quot;id#quot;] - - if id in self._buffers: - buffer = self._buffers[id] - else: - self.error(f#quot;can't resolve buffer '{id}'#quot;)\n"] - - start --> block7 - block7 -- "isinstance(data, str)" --> block1 - block7 -- "else" --> block6 - block6 -- "isinstance(data, Buffer)" --> block5 - block6 -- "else" --> block4 - block5 --> block0 - block4 -- "id in self._buffers" --> block2 - block4 -- "else" --> block3 - block3 --> block0 - block2 --> block0 - block1 --> return - block0 --> return -``` - - diff --git a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__match.py.md.snap b/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__match.py.md.snap deleted file mode 100644 index d74b08116f936..0000000000000 --- a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__match.py.md.snap +++ /dev/null @@ -1,815 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/ruff/rules/unreachable.rs -description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." ---- -## Function 0 -### Source -```python -def func(status): - match status: - case _: - return 0 - return "unreachable" -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return #quot;unreachable#quot;\n"] - block1["return 0\n"] - block2["match status: - case _: - return 0\n"] - - start --> block2 - block2 --> block1 - block1 --> return - block0 --> return -``` - -## Function 1 -### Source -```python -def func(status): - match status: - case 1: - return 1 - return 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 0\n"] - block1["return 1\n"] - block2["match status: - case 1: - return 1\n"] - - start --> block2 - block2 -- "case 1" --> block1 - block2 -- "else" --> block0 - block1 --> return - block0 --> return -``` - -## Function 2 -### Source -```python -def func(status): - match status: - case 1: - return 1 - case _: - return 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 0\n"] - block1["match status: - case 1: - return 1 - case _: - return 0\n"] - block2["return 1\n"] - block3["match status: - case 1: - return 1 - case _: - return 0\n"] - - start --> block3 - block3 -- "case 1" --> block2 - block3 -- "else" --> block1 - block2 --> return - block1 --> block0 - block0 --> return -``` - -## Function 3 -### Source -```python -def func(status): - match status: - case 1 | 2 | 3: - return 5 - return 6 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 6\n"] - block1["return 5\n"] - block2["match status: - case 1 | 2 | 3: - return 5\n"] - - start --> block2 - block2 -- "case 1 | 2 | 3" --> block1 - block2 -- "else" --> block0 - block1 --> return - block0 --> return -``` - -## Function 4 -### Source -```python -def func(status): - match status: - case 1 | 2 | 3: - return 5 - case _: - return 10 - return 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 0\n"] - block1["return 10\n"] - block2["match status: - case 1 | 2 | 3: - return 5 - case _: - return 10\n"] - block3["return 5\n"] - block4["match status: - case 1 | 2 | 3: - return 5 - case _: - return 10\n"] - - start --> block4 - block4 -- "case 1 | 2 | 3" --> block3 - block4 -- "else" --> block2 - block3 --> return - block2 --> block1 - block1 --> return - block0 --> return -``` - -## Function 5 -### Source -```python -def func(status): - match status: - case 0: - return 0 - case 1: - return 1 - case 1: - return "1 again" - case _: - return 3 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 3\n"] - block1["match status: - case 0: - return 0 - case 1: - return 1 - case 1: - return #quot;1 again#quot; - case _: - return 3\n"] - block2["return #quot;1 again#quot;\n"] - block3["match status: - case 0: - return 0 - case 1: - return 1 - case 1: - return #quot;1 again#quot; - case _: - return 3\n"] - block4["return 1\n"] - block5["match status: - case 0: - return 0 - case 1: - return 1 - case 1: - return #quot;1 again#quot; - case _: - return 3\n"] - block6["return 0\n"] - block7["match status: - case 0: - return 0 - case 1: - return 1 - case 1: - return #quot;1 again#quot; - case _: - return 3\n"] - - start --> block7 - block7 -- "case 0" --> block6 - block7 -- "else" --> block5 - block6 --> return - block5 -- "case 1" --> block4 - block5 -- "else" --> block3 - block4 --> return - block3 -- "case 1" --> block2 - block3 -- "else" --> block1 - block2 --> return - block1 --> block0 - block0 --> return -``` - -## Function 6 -### Source -```python -def func(status): - i = 0 - match status, i: - case _, _: - return 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["return 0\n"] - block2["match status, i: - case _, _: - return 0\n"] - block3["i = 0\n"] - - start --> block3 - block3 --> block2 - block2 -- "case _, _" --> block1 - block2 -- "else" --> block0 - block1 --> return - block0 --> return -``` - -## Function 7 -### Source -```python -def func(status): - i = 0 - match status, i: - case _, 0: - return 0 - case _, 2: - return 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["return 0\n"] - block2["match status, i: - case _, 0: - return 0 - case _, 2: - return 0\n"] - block3["return 0\n"] - block4["match status, i: - case _, 0: - return 0 - case _, 2: - return 0\n"] - block5["i = 0\n"] - - start --> block5 - block5 --> block4 - block4 -- "case _, 0" --> block3 - block4 -- "else" --> block2 - block3 --> return - block2 -- "case _, 2" --> block1 - block2 -- "else" --> block0 - block1 --> return - block0 --> return -``` - -## Function 8 -### Source -```python -def func(point): - match point: - case (0, 0): - print("Origin") - case _: - raise ValueError("oops") -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["raise ValueError(#quot;oops#quot;)\n"] - block2["match point: - case (0, 0): - print(#quot;Origin#quot;) - case _: - raise ValueError(#quot;oops#quot;)\n"] - block3["print(#quot;Origin#quot;)\n"] - block4["match point: - case (0, 0): - print(#quot;Origin#quot;) - case _: - raise ValueError(#quot;oops#quot;)\n"] - - start --> block4 - block4 -- "case (0, 0)" --> block3 - block4 -- "else" --> block2 - block3 --> block0 - block2 --> block1 - block1 --> return - block0 --> return -``` - -## Function 9 -### Source -```python -def func(point): - match point: - case (0, 0): - print("Origin") - case (0, y): - print(f"Y={y}") - case (x, 0): - print(f"X={x}") - case (x, y): - print(f"X={x}, Y={y}") - case _: - raise ValueError("Not a point") -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["raise ValueError(#quot;Not a point#quot;)\n"] - block2["match point: - case (0, 0): - print(#quot;Origin#quot;) - case (0, y): - print(f#quot;Y={y}#quot;) - case (x, 0): - print(f#quot;X={x}#quot;) - case (x, y): - print(f#quot;X={x}, Y={y}#quot;) - case _: - raise ValueError(#quot;Not a point#quot;)\n"] - block3["print(f#quot;X={x}, Y={y}#quot;)\n"] - block4["match point: - case (0, 0): - print(#quot;Origin#quot;) - case (0, y): - print(f#quot;Y={y}#quot;) - case (x, 0): - print(f#quot;X={x}#quot;) - case (x, y): - print(f#quot;X={x}, Y={y}#quot;) - case _: - raise ValueError(#quot;Not a point#quot;)\n"] - block5["print(f#quot;X={x}#quot;)\n"] - block6["match point: - case (0, 0): - print(#quot;Origin#quot;) - case (0, y): - print(f#quot;Y={y}#quot;) - case (x, 0): - print(f#quot;X={x}#quot;) - case (x, y): - print(f#quot;X={x}, Y={y}#quot;) - case _: - raise ValueError(#quot;Not a point#quot;)\n"] - block7["print(f#quot;Y={y}#quot;)\n"] - block8["match point: - case (0, 0): - print(#quot;Origin#quot;) - case (0, y): - print(f#quot;Y={y}#quot;) - case (x, 0): - print(f#quot;X={x}#quot;) - case (x, y): - print(f#quot;X={x}, Y={y}#quot;) - case _: - raise ValueError(#quot;Not a point#quot;)\n"] - block9["print(#quot;Origin#quot;)\n"] - block10["match point: - case (0, 0): - print(#quot;Origin#quot;) - case (0, y): - print(f#quot;Y={y}#quot;) - case (x, 0): - print(f#quot;X={x}#quot;) - case (x, y): - print(f#quot;X={x}, Y={y}#quot;) - case _: - raise ValueError(#quot;Not a point#quot;)\n"] - - start --> block10 - block10 -- "case (0, 0)" --> block9 - block10 -- "else" --> block8 - block9 --> block0 - block8 -- "case (0, y)" --> block7 - block8 -- "else" --> block6 - block7 --> block0 - block6 -- "case (x, 0)" --> block5 - block6 -- "else" --> block4 - block5 --> block0 - block4 -- "case (x, y)" --> block3 - block4 -- "else" --> block2 - block3 --> block0 - block2 --> block1 - block1 --> return - block0 --> return -``` - -## Function 10 -### Source -```python -def where_is(point): - class Point: - x: int - y: int - - match point: - case Point(x=0, y=0): - print("Origin") - case Point(x=0, y=y): - print(f"Y={y}") - case Point(x=x, y=0): - print(f"X={x}") - case Point(): - print("Somewhere else") - case _: - print("Not a point") -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["print(#quot;Not a point#quot;)\n"] - block2["match point: - case Point(x=0, y=0): - print(#quot;Origin#quot;) - case Point(x=0, y=y): - print(f#quot;Y={y}#quot;) - case Point(x=x, y=0): - print(f#quot;X={x}#quot;) - case Point(): - print(#quot;Somewhere else#quot;) - case _: - print(#quot;Not a point#quot;)\n"] - block3["print(#quot;Somewhere else#quot;)\n"] - block4["match point: - case Point(x=0, y=0): - print(#quot;Origin#quot;) - case Point(x=0, y=y): - print(f#quot;Y={y}#quot;) - case Point(x=x, y=0): - print(f#quot;X={x}#quot;) - case Point(): - print(#quot;Somewhere else#quot;) - case _: - print(#quot;Not a point#quot;)\n"] - block5["print(f#quot;X={x}#quot;)\n"] - block6["match point: - case Point(x=0, y=0): - print(#quot;Origin#quot;) - case Point(x=0, y=y): - print(f#quot;Y={y}#quot;) - case Point(x=x, y=0): - print(f#quot;X={x}#quot;) - case Point(): - print(#quot;Somewhere else#quot;) - case _: - print(#quot;Not a point#quot;)\n"] - block7["print(f#quot;Y={y}#quot;)\n"] - block8["match point: - case Point(x=0, y=0): - print(#quot;Origin#quot;) - case Point(x=0, y=y): - print(f#quot;Y={y}#quot;) - case Point(x=x, y=0): - print(f#quot;X={x}#quot;) - case Point(): - print(#quot;Somewhere else#quot;) - case _: - print(#quot;Not a point#quot;)\n"] - block9["print(#quot;Origin#quot;)\n"] - block10["match point: - case Point(x=0, y=0): - print(#quot;Origin#quot;) - case Point(x=0, y=y): - print(f#quot;Y={y}#quot;) - case Point(x=x, y=0): - print(f#quot;X={x}#quot;) - case Point(): - print(#quot;Somewhere else#quot;) - case _: - print(#quot;Not a point#quot;)\n"] - block11["class Point: - x: int - y: int\n"] - - start --> block11 - block11 --> block10 - block10 -- "case Point(x=0, y=0)" --> block9 - block10 -- "else" --> block8 - block9 --> block0 - block8 -- "case Point(x=0, y=y)" --> block7 - block8 -- "else" --> block6 - block7 --> block0 - block6 -- "case Point(x=x, y=0)" --> block5 - block6 -- "else" --> block4 - block5 --> block0 - block4 -- "case Point()" --> block3 - block4 -- "else" --> block2 - block3 --> block0 - block2 --> block1 - block1 --> block0 - block0 --> return -``` - -## Function 11 -### Source -```python -def func(points): - match points: - case []: - print("No points") - case [Point(0, 0)]: - print("The origin") - case [Point(x, y)]: - print(f"Single point {x}, {y}") - case [Point(0, y1), Point(0, y2)]: - print(f"Two on the Y axis at {y1}, {y2}") - case _: - print("Something else") -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["print(#quot;Something else#quot;)\n"] - block2["match points: - case []: - print(#quot;No points#quot;) - case [Point(0, 0)]: - print(#quot;The origin#quot;) - case [Point(x, y)]: - print(f#quot;Single point {x}, {y}#quot;) - case [Point(0, y1), Point(0, y2)]: - print(f#quot;Two on the Y axis at {y1}, {y2}#quot;) - case _: - print(#quot;Something else#quot;)\n"] - block3["print(f#quot;Two on the Y axis at {y1}, {y2}#quot;)\n"] - block4["match points: - case []: - print(#quot;No points#quot;) - case [Point(0, 0)]: - print(#quot;The origin#quot;) - case [Point(x, y)]: - print(f#quot;Single point {x}, {y}#quot;) - case [Point(0, y1), Point(0, y2)]: - print(f#quot;Two on the Y axis at {y1}, {y2}#quot;) - case _: - print(#quot;Something else#quot;)\n"] - block5["print(f#quot;Single point {x}, {y}#quot;)\n"] - block6["match points: - case []: - print(#quot;No points#quot;) - case [Point(0, 0)]: - print(#quot;The origin#quot;) - case [Point(x, y)]: - print(f#quot;Single point {x}, {y}#quot;) - case [Point(0, y1), Point(0, y2)]: - print(f#quot;Two on the Y axis at {y1}, {y2}#quot;) - case _: - print(#quot;Something else#quot;)\n"] - block7["print(#quot;The origin#quot;)\n"] - block8["match points: - case []: - print(#quot;No points#quot;) - case [Point(0, 0)]: - print(#quot;The origin#quot;) - case [Point(x, y)]: - print(f#quot;Single point {x}, {y}#quot;) - case [Point(0, y1), Point(0, y2)]: - print(f#quot;Two on the Y axis at {y1}, {y2}#quot;) - case _: - print(#quot;Something else#quot;)\n"] - block9["print(#quot;No points#quot;)\n"] - block10["match points: - case []: - print(#quot;No points#quot;) - case [Point(0, 0)]: - print(#quot;The origin#quot;) - case [Point(x, y)]: - print(f#quot;Single point {x}, {y}#quot;) - case [Point(0, y1), Point(0, y2)]: - print(f#quot;Two on the Y axis at {y1}, {y2}#quot;) - case _: - print(#quot;Something else#quot;)\n"] - - start --> block10 - block10 -- "case []" --> block9 - block10 -- "else" --> block8 - block9 --> block0 - block8 -- "case [Point(0, 0)]" --> block7 - block8 -- "else" --> block6 - block7 --> block0 - block6 -- "case [Point(x, y)]" --> block5 - block6 -- "else" --> block4 - block5 --> block0 - block4 -- "case [Point(0, y1), Point(0, y2)]" --> block3 - block4 -- "else" --> block2 - block3 --> block0 - block2 --> block1 - block1 --> block0 - block0 --> return -``` - -## Function 12 -### Source -```python -def func(point): - match point: - case Point(x, y) if x == y: - print(f"Y=X at {x}") - case Point(x, y): - print(f"Not on the diagonal") -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["print(f#quot;Not on the diagonal#quot;)\n"] - block2["match point: - case Point(x, y) if x == y: - print(f#quot;Y=X at {x}#quot;) - case Point(x, y): - print(f#quot;Not on the diagonal#quot;)\n"] - block3["print(f#quot;Y=X at {x}#quot;)\n"] - block4["match point: - case Point(x, y) if x == y: - print(f#quot;Y=X at {x}#quot;) - case Point(x, y): - print(f#quot;Not on the diagonal#quot;)\n"] - - start --> block4 - block4 -- "case Point(x, y) if x == y" --> block3 - block4 -- "else" --> block2 - block3 --> block0 - block2 -- "case Point(x, y)" --> block1 - block2 -- "else" --> block0 - block1 --> block0 - block0 --> return -``` - -## Function 13 -### Source -```python -def func(): - from enum import Enum - class Color(Enum): - RED = 'red' - GREEN = 'green' - BLUE = 'blue' - - color = Color(input("Enter your choice of 'red', 'blue' or 'green': ")) - - match color: - case Color.RED: - print("I see red!") - case Color.GREEN: - print("Grass is green") - case Color.BLUE: - print("I'm feeling the blues :(") -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["print(#quot;I'm feeling the blues :(#quot;)\n"] - block2["match color: - case Color.RED: - print(#quot;I see red!#quot;) - case Color.GREEN: - print(#quot;Grass is green#quot;) - case Color.BLUE: - print(#quot;I'm feeling the blues :(#quot;)\n"] - block3["print(#quot;Grass is green#quot;)\n"] - block4["match color: - case Color.RED: - print(#quot;I see red!#quot;) - case Color.GREEN: - print(#quot;Grass is green#quot;) - case Color.BLUE: - print(#quot;I'm feeling the blues :(#quot;)\n"] - block5["print(#quot;I see red!#quot;)\n"] - block6["match color: - case Color.RED: - print(#quot;I see red!#quot;) - case Color.GREEN: - print(#quot;Grass is green#quot;) - case Color.BLUE: - print(#quot;I'm feeling the blues :(#quot;)\n"] - block7["from enum import Enum\nclass Color(Enum): - RED = 'red' - GREEN = 'green' - BLUE = 'blue'\ncolor = Color(input(#quot;Enter your choice of 'red', 'blue' or 'green': #quot;))\n"] - - start --> block7 - block7 --> block6 - block6 -- "case Color.RED" --> block5 - block6 -- "else" --> block4 - block5 --> block0 - block4 -- "case Color.GREEN" --> block3 - block4 -- "else" --> block2 - block3 --> block0 - block2 -- "case Color.BLUE" --> block1 - block2 -- "else" --> block0 - block1 --> block0 - block0 --> return -``` - -## Function 14 -### Source -```python -def func(point): - match point: - case (0, 0): - print("Origin") - case foo: - raise ValueError("oops") -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["raise ValueError(#quot;oops#quot;)\n"] - block2["match point: - case (0, 0): - print(#quot;Origin#quot;) - case foo: - raise ValueError(#quot;oops#quot;)\n"] - block3["print(#quot;Origin#quot;)\n"] - block4["match point: - case (0, 0): - print(#quot;Origin#quot;) - case foo: - raise ValueError(#quot;oops#quot;)\n"] - - start --> block4 - block4 -- "case (0, 0)" --> block3 - block4 -- "else" --> block2 - block3 --> block0 - block2 --> block1 - block1 --> return - block0 --> return -``` - - diff --git a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__raise.py.md.snap b/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__raise.py.md.snap deleted file mode 100644 index d0265fa698679..0000000000000 --- a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__raise.py.md.snap +++ /dev/null @@ -1,41 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/ruff/rules/unreachable.rs -description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." ---- -## Function 0 -### Source -```python -def func(): - raise Exception -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["raise Exception\n"] - - start --> block0 - block0 --> return -``` - -## Function 1 -### Source -```python -def func(): - raise "a glass!" -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["raise #quot;a glass!#quot;\n"] - - start --> block0 - block0 --> return -``` - - diff --git a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__simple.py.md.snap b/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__simple.py.md.snap deleted file mode 100644 index 015cf43dcc54f..0000000000000 --- a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__simple.py.md.snap +++ /dev/null @@ -1,136 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/ruff/rules/unreachable.rs -description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." ---- -## Function 0 -### Source -```python -def func(): - pass -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["pass\n"] - - start --> block0 - block0 --> return -``` - -## Function 1 -### Source -```python -def func(): - pass -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["pass\n"] - - start --> block0 - block0 --> return -``` - -## Function 2 -### Source -```python -def func(): - return -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return\n"] - - start --> block0 - block0 --> return -``` - -## Function 3 -### Source -```python -def func(): - return 1 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 1\n"] - - start --> block0 - block0 --> return -``` - -## Function 4 -### Source -```python -def func(): - return 1 - return "unreachable" -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return #quot;unreachable#quot;\n"] - block1["return 1\n"] - - start --> block1 - block1 --> return - block0 --> return -``` - -## Function 5 -### Source -```python -def func(): - i = 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["i = 0\n"] - - start --> block0 - block0 --> return -``` - -## Function 6 -### Source -```python -def func(): - i = 0 - i += 2 - return i -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["i = 0\ni += 2\nreturn i\n"] - - start --> block0 - block0 --> return -``` - - diff --git a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__while.py.md.snap b/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__while.py.md.snap deleted file mode 100644 index 3491d2b7e22f9..0000000000000 --- a/crates/ruff_linter/src/rules/ruff/rules/snapshots/ruff_linter__rules__ruff__rules__unreachable__tests__while.py.md.snap +++ /dev/null @@ -1,527 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/ruff/rules/unreachable.rs -description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." ---- -## Function 0 -### Source -```python -def func(): - while False: - return "unreachable" - return 1 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 1\n"] - block1["return #quot;unreachable#quot;\n"] - block2["while False: - return #quot;unreachable#quot;\n"] - - start --> block2 - block2 -- "False" --> block1 - block2 -- "else" --> block0 - block1 --> return - block0 --> return -``` - -## Function 1 -### Source -```python -def func(): - while False: - return "unreachable" - else: - return 1 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return #quot;unreachable#quot;\n"] - block1["return 1\n"] - block2["while False: - return #quot;unreachable#quot; - else: - return 1\n"] - - start --> block2 - block2 -- "False" --> block0 - block2 -- "else" --> block1 - block1 --> return - block0 --> return -``` - -## Function 2 -### Source -```python -def func(): - while False: - return "unreachable" - else: - return 1 - return "also unreachable" -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return #quot;also unreachable#quot;\n"] - block1["return #quot;unreachable#quot;\n"] - block2["return 1\n"] - block3["while False: - return #quot;unreachable#quot; - else: - return 1\n"] - - start --> block3 - block3 -- "False" --> block1 - block3 -- "else" --> block2 - block2 --> return - block1 --> return - block0 --> return -``` - -## Function 3 -### Source -```python -def func(): - while True: - return 1 - return "unreachable" -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return #quot;unreachable#quot;\n"] - block1["return 1\n"] - block2["while True: - return 1\n"] - - start --> block2 - block2 -- "True" --> block1 - block2 -- "else" --> block0 - block1 --> return - block0 --> return -``` - -## Function 4 -### Source -```python -def func(): - while True: - return 1 - else: - return "unreachable" -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 1\n"] - block1["return #quot;unreachable#quot;\n"] - block2["while True: - return 1 - else: - return #quot;unreachable#quot;\n"] - - start --> block2 - block2 -- "True" --> block0 - block2 -- "else" --> block1 - block1 --> return - block0 --> return -``` - -## Function 5 -### Source -```python -def func(): - while True: - return 1 - else: - return "unreachable" - return "also unreachable" -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return #quot;also unreachable#quot;\n"] - block1["return 1\n"] - block2["return #quot;unreachable#quot;\n"] - block3["while True: - return 1 - else: - return #quot;unreachable#quot;\n"] - - start --> block3 - block3 -- "True" --> block1 - block3 -- "else" --> block2 - block2 --> return - block1 --> return - block0 --> return -``` - -## Function 6 -### Source -```python -def func(): - i = 0 - while False: - i += 1 - return i -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return i\n"] - block1["i += 1\n"] - block2["i = 0\nwhile False: - i += 1\n"] - - start --> block2 - block2 -- "False" --> block1 - block2 -- "else" --> block0 - block1 --> block2 - block0 --> return -``` - -## Function 7 -### Source -```python -def func(): - i = 0 - while True: - i += 1 - return i -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return i\n"] - block1["i += 1\n"] - block2["i = 0\nwhile True: - i += 1\n"] - - start --> block2 - block2 -- "True" --> block1 - block2 -- "else" --> block0 - block1 --> block2 - block0 --> return -``` - -## Function 8 -### Source -```python -def func(): - while True: - pass - return 1 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 1\n"] - block1["pass\n"] - block2["while True: - pass\n"] - - start --> block2 - block2 -- "True" --> block1 - block2 -- "else" --> block0 - block1 --> block2 - block0 --> return -``` - -## Function 9 -### Source -```python -def func(): - i = 0 - while True: - if True: - print("ok") - i += 1 - return i -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return i\n"] - block1["i += 1\n"] - block2["print(#quot;ok#quot;)\n"] - block3["if True: - print(#quot;ok#quot;)\n"] - block4["i = 0\nwhile True: - if True: - print(#quot;ok#quot;) - i += 1\n"] - - start --> block4 - block4 -- "True" --> block3 - block4 -- "else" --> block0 - block3 -- "True" --> block2 - block3 -- "else" --> block1 - block2 --> block1 - block1 --> block4 - block0 --> return -``` - -## Function 10 -### Source -```python -def func(): - i = 0 - while True: - if False: - print("ok") - i += 1 - return i -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return i\n"] - block1["i += 1\n"] - block2["print(#quot;ok#quot;)\n"] - block3["if False: - print(#quot;ok#quot;)\n"] - block4["i = 0\nwhile True: - if False: - print(#quot;ok#quot;) - i += 1\n"] - - start --> block4 - block4 -- "True" --> block3 - block4 -- "else" --> block0 - block3 -- "False" --> block2 - block3 -- "else" --> block1 - block2 --> block1 - block1 --> block4 - block0 --> return -``` - -## Function 11 -### Source -```python -def func(): - while True: - if True: - return 1 - return 0 -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0["return 0\n"] - block1["return 1\n"] - block2["if True: - return 1\n"] - block3["while True: - if True: - return 1\n"] - - start --> block3 - block3 -- "True" --> block2 - block3 -- "else" --> block0 - block2 -- "True" --> block1 - block2 -- "else" --> block3 - block1 --> return - block0 --> return -``` - -## Function 12 -### Source -```python -def func(): - while True: - continue -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["continue\n"] - block2["while True: - continue\n"] - - start --> block2 - block2 -- "True" --> block1 - block2 -- "else" --> block0 - block1 --> block2 - block0 --> return -``` - -## Function 13 -### Source -```python -def func(): - while False: - continue -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["continue\n"] - block2["while False: - continue\n"] - - start --> block2 - block2 -- "False" --> block1 - block2 -- "else" --> block0 - block1 --> block2 - block0 --> return -``` - -## Function 14 -### Source -```python -def func(): - while True: - break -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["break\n"] - block2["while True: - break\n"] - - start --> block2 - block2 -- "True" --> block1 - block2 -- "else" --> block0 - block1 --> block0 - block0 --> return -``` - -## Function 15 -### Source -```python -def func(): - while False: - break -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["break\n"] - block2["while False: - break\n"] - - start --> block2 - block2 -- "False" --> block1 - block2 -- "else" --> block0 - block1 --> block0 - block0 --> return -``` - -## Function 16 -### Source -```python -def func(): - while True: - if True: - continue -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["continue\n"] - block2["if True: - continue\n"] - block3["while True: - if True: - continue\n"] - - start --> block3 - block3 -- "True" --> block2 - block3 -- "else" --> block0 - block2 -- "True" --> block1 - block2 -- "else" --> block3 - block1 --> block3 - block0 --> return -``` - -## Function 17 -### Source -```python -def func(): - while True: - if True: - break -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1["break\n"] - block2["if True: - break\n"] - block3["while True: - if True: - break\n"] - - start --> block3 - block3 -- "True" --> block2 - block3 -- "else" --> block0 - block2 -- "True" --> block1 - block2 -- "else" --> block3 - block1 --> block0 - block0 --> return -``` - - diff --git a/crates/ruff_linter/src/rules/ruff/rules/unreachable.rs b/crates/ruff_linter/src/rules/ruff/rules/unreachable.rs deleted file mode 100644 index 8504016848827..0000000000000 --- a/crates/ruff_linter/src/rules/ruff/rules/unreachable.rs +++ /dev/null @@ -1,1114 +0,0 @@ -use std::{fmt, iter, usize}; - -use log::error; -use ruff_python_ast::{ - Expr, ExprBooleanLiteral, Identifier, MatchCase, Pattern, PatternMatchAs, PatternMatchOr, Stmt, - StmtFor, StmtMatch, StmtReturn, StmtTry, StmtWhile, StmtWith, -}; -use ruff_text_size::{Ranged, TextRange, TextSize}; - -use ruff_diagnostics::{Diagnostic, Violation}; -use ruff_index::{IndexSlice, IndexVec}; -use ruff_macros::{derive_message_formats, newtype_index, violation}; - -/// ## What it does -/// Checks for unreachable code. -/// -/// ## Why is this bad? -/// Unreachable code can be a maintenance burden without ever being used. -/// -/// ## Example -/// ```python -/// def function(): -/// if False: -/// return "unreachable" -/// return "reachable" -/// ``` -/// -/// Use instead: -/// ```python -/// def function(): -/// return "reachable" -/// ``` -#[violation] -pub struct UnreachableCode { - name: String, -} - -impl Violation for UnreachableCode { - #[derive_message_formats] - fn message(&self) -> String { - let UnreachableCode { name } = self; - format!("Unreachable code in {name}") - } -} - -pub(crate) fn in_function(name: &Identifier, body: &[Stmt]) -> Vec { - // Create basic code blocks from the body. - let basic_blocks = BasicBlocks::from(body); - - // Basic on the code blocks we can (more) easily follow what statements are - // and aren't reached, we'll mark them as such in `reached_map`. - let mut reached_map = Bitmap::with_capacity(basic_blocks.len()); - - if let Some(start_index) = basic_blocks.start_index() { - mark_reached(&mut reached_map, &basic_blocks.blocks, start_index); - } - - // For each unreached code block create a diagnostic. - reached_map - .unset() - .filter_map(|idx| { - let block = &basic_blocks.blocks[idx]; - if block.is_sentinel() { - return None; - } - - // TODO: add more information to the diagnostic. Include the entire - // code block, not just the first line. Maybe something to indicate - // the code flow and where it prevents this block from being reached - // for example. - let Some(stmt) = block.stmts.first() else { - // This should never happen. - error!("Got an unexpected empty code block"); - return None; - }; - Some(Diagnostic::new( - UnreachableCode { - name: name.as_str().to_owned(), - }, - stmt.range(), - )) - }) - .collect() -} - -/// Simple bitmap. -#[derive(Debug)] -struct Bitmap { - bits: Box<[usize]>, - capacity: usize, -} - -impl Bitmap { - /// Create a new `Bitmap` with `capacity` capacity. - fn with_capacity(capacity: usize) -> Bitmap { - let mut size = capacity / usize::BITS as usize; - if (capacity % usize::BITS as usize) != 0 { - size += 1; - } - Bitmap { - bits: vec![0; size].into_boxed_slice(), - capacity, - } - } - - /// Set bit at index `idx` to true. - /// - /// Returns a boolean indicating if the bit was already set. - fn set(&mut self, idx: BlockIndex) -> bool { - let bits_index = (idx.as_u32() / usize::BITS) as usize; - let shift = idx.as_u32() % usize::BITS; - if (self.bits[bits_index] & (1 << shift)) == 0 { - self.bits[bits_index] |= 1 << shift; - false - } else { - true - } - } - - /// Returns an iterator of all unset indices. - fn unset(&self) -> impl Iterator + '_ { - let mut index = 0; - let mut shift = 0; - let last_max_shift = self.capacity % usize::BITS as usize; - iter::from_fn(move || loop { - if shift >= usize::BITS as usize { - shift = 0; - index += 1; - } - if self.bits.len() <= index || (index >= self.bits.len() - 1 && shift >= last_max_shift) - { - return None; - } - - let is_set = (self.bits[index] & (1 << shift)) != 0; - shift += 1; - if !is_set { - return Some(BlockIndex::from_usize( - (index * usize::BITS as usize) + shift - 1, - )); - } - }) - } -} - -/// Set bits in `reached_map` for all blocks that are reached in `blocks` -/// starting with block at index `idx`. -fn mark_reached( - reached_map: &mut Bitmap, - blocks: &IndexSlice>, - start_index: BlockIndex, -) { - let mut idx = start_index; - - loop { - let block = &blocks[idx]; - if reached_map.set(idx) { - return; // Block already visited, no needed to do it again. - } - - match &block.next { - NextBlock::Always(next) => idx = *next, - NextBlock::If { - condition, - next, - orelse, - } => { - match taken(condition) { - Some(true) => idx = *next, // Always taken. - Some(false) => idx = *orelse, // Never taken. - None => { - // Don't know, both branches might be taken. - idx = *next; - mark_reached(reached_map, blocks, *orelse); - } - } - } - NextBlock::Terminate => return, - } - } -} - -/// Determines if `condition` is taken. -/// Returns `Some(true)` if the condition is always true, e.g. `if True`, same -/// with `Some(false)` if it's never taken. If it can't be determined it returns -/// `None`, e.g. `If i == 100`. -fn taken(condition: &Condition) -> Option { - // TODO: add more cases to this where we can determine a condition - // statically. For now we only consider constant booleans. - match condition { - Condition::Test(expr) => match expr { - Expr::BooleanLiteral(ExprBooleanLiteral { value, .. }) => Some(*value), - _ => None, - }, - Condition::Iterator(_) => None, - Condition::Match { .. } => None, - } -} - -/// Index into [`BasicBlocks::blocks`]. -#[newtype_index] -#[derive(PartialOrd, Ord)] -struct BlockIndex; - -/// Collection of basic block. -#[derive(Debug, PartialEq)] -struct BasicBlocks<'stmt> { - /// # Notes - /// - /// The order of these block is unspecified. However it's guaranteed that - /// the last block is the first statement in the function and the first - /// block is the last statement. The block are more or less in reverse - /// order, but it gets fussy around control flow statements (e.g. `while` - /// statements). - /// - /// For loop blocks, and similar recurring control flows, the end of the - /// body will point to the loop block again (to create the loop). However an - /// oddity here is that this block might contain statements before the loop - /// itself which, of course, won't be executed again. - /// - /// For example: - /// ```python - /// i = 0 # block 0 - /// while True: # - /// continue # block 1 - /// ``` - /// Will create a connection between block 1 (loop body) and block 0, which - /// includes the `i = 0` statement. - /// - /// To keep `NextBlock` simple(r) `NextBlock::If`'s `next` and `orelse` - /// fields only use `BlockIndex`, which means that they can't terminate - /// themselves. To support this we insert *empty*/fake blocks before the end - /// of the function that we can link to. - /// - /// Finally `BasicBlock` can also be a sentinel node, see the associated - /// constants of [`BasicBlock`]. - blocks: IndexVec>, -} - -impl BasicBlocks<'_> { - fn len(&self) -> usize { - self.blocks.len() - } - - fn start_index(&self) -> Option { - self.blocks.indices().last() - } -} - -impl<'stmt> From<&'stmt [Stmt]> for BasicBlocks<'stmt> { - /// # Notes - /// - /// This assumes that `stmts` is a function body. - fn from(stmts: &'stmt [Stmt]) -> BasicBlocks<'stmt> { - let mut blocks = BasicBlocksBuilder::with_capacity(stmts.len()); - - blocks.create_blocks(stmts, None); - - blocks.finish() - } -} - -/// Basic code block, sequence of statements unconditionally executed -/// "together". -#[derive(Debug, PartialEq)] -struct BasicBlock<'stmt> { - stmts: &'stmt [Stmt], - next: NextBlock<'stmt>, -} - -/// Edge between basic blocks (in the control-flow graph). -#[derive(Debug, PartialEq)] -enum NextBlock<'stmt> { - /// Always continue with a block. - Always(BlockIndex), - /// Condition jump. - If { - /// Condition that needs to be evaluated to jump to the `next` or - /// `orelse` block. - condition: Condition<'stmt>, - /// Next block if `condition` is true. - next: BlockIndex, - /// Next block if `condition` is false. - orelse: BlockIndex, - }, - /// The end. - Terminate, -} - -/// Condition used to determine to take the `next` or `orelse` branch in -/// [`NextBlock::If`]. -#[derive(Clone, Debug, PartialEq)] -enum Condition<'stmt> { - /// Conditional statement, this should evaluate to a boolean, for e.g. `if` - /// or `while`. - Test(&'stmt Expr), - /// Iterator for `for` statements, e.g. for `i in range(10)` this will be - /// `range(10)`. - Iterator(&'stmt Expr), - Match { - /// `match $subject`. - subject: &'stmt Expr, - /// `case $case`, include pattern, guard, etc. - case: &'stmt MatchCase, - }, -} - -impl<'stmt> Ranged for Condition<'stmt> { - fn range(&self) -> TextRange { - match self { - Condition::Test(expr) | Condition::Iterator(expr) => expr.range(), - // The case of the match statement, without the body. - Condition::Match { subject: _, case } => TextRange::new( - case.start(), - case.guard - .as_ref() - .map_or(case.pattern.end(), |guard| guard.end()), - ), - } - } -} - -impl<'stmt> BasicBlock<'stmt> { - /// A sentinel block indicating an empty termination block. - const EMPTY: BasicBlock<'static> = BasicBlock { - stmts: &[], - next: NextBlock::Terminate, - }; - - /// A sentinel block indicating an exception was raised. - const EXCEPTION: BasicBlock<'static> = BasicBlock { - stmts: &[Stmt::Return(StmtReturn { - range: TextRange::new(TextSize::new(0), TextSize::new(0)), - value: None, - })], - next: NextBlock::Terminate, - }; - - /// Return true if the block is a sentinel or fake block. - fn is_sentinel(&self) -> bool { - self.is_empty() || self.is_exception() - } - - /// Returns an empty block that terminates. - fn is_empty(&self) -> bool { - matches!(self.next, NextBlock::Terminate) && self.stmts.is_empty() - } - - /// Returns true if `self` an [`BasicBlock::EXCEPTION`]. - fn is_exception(&self) -> bool { - matches!(self.next, NextBlock::Terminate) && BasicBlock::EXCEPTION.stmts == self.stmts - } -} - -/// Handle a loop block, such as a `while`, `for` or `async for` statement. -fn loop_block<'stmt>( - blocks: &mut BasicBlocksBuilder<'stmt>, - condition: Condition<'stmt>, - body: &'stmt [Stmt], - orelse: &'stmt [Stmt], - after: Option, -) -> NextBlock<'stmt> { - let after_block = blocks.maybe_next_block_index(after, || orelse.is_empty()); - // NOTE: a while loop's body must not be empty, so we can safely - // create at least one block from it. - let last_statement_index = blocks.append_blocks(body, after); - let last_orelse_statement = blocks.append_blocks_if_not_empty(orelse, after_block); - // `create_blocks` always continues to the next block by - // default. However in a while loop we want to continue with the - // while block (we're about to create) to create the loop. - // NOTE: `blocks.len()` is an invalid index at time of creation - // as it points to the block which we're about to create. - blocks.change_next_block( - last_statement_index, - after_block, - blocks.blocks.next_index(), - |block| { - // For `break` statements we don't want to continue with the - // loop, but instead with the statement after the loop (i.e. - // not change anything). - !block.stmts.last().is_some_and(Stmt::is_break_stmt) - }, - ); - NextBlock::If { - condition, - next: last_statement_index, - orelse: last_orelse_statement, - } -} - -/// Handle a single match case. -/// -/// `next_after_block` is the block *after* the entire match statement that is -/// taken after this case is taken. -/// `orelse_after_block` is the next match case (or the block after the match -/// statement if this is the last case). -fn match_case<'stmt>( - blocks: &mut BasicBlocksBuilder<'stmt>, - match_stmt: &'stmt Stmt, - subject: &'stmt Expr, - case: &'stmt MatchCase, - next_after_block: BlockIndex, - orelse_after_block: BlockIndex, -) -> BasicBlock<'stmt> { - // FIXME: this is not ideal, we want to only use the `case` statement here, - // but that is type `MatchCase`, not `Stmt`. For now we'll point to the - // entire match statement. - let stmts = std::slice::from_ref(match_stmt); - let next_block_index = if case.body.is_empty() { - next_after_block - } else { - let from = blocks.last_index(); - let last_statement_index = blocks.append_blocks(&case.body, Some(next_after_block)); - if let Some(from) = from { - blocks.change_next_block(last_statement_index, from, next_after_block, |_| true); - } - last_statement_index - }; - let next = if is_wildcard(case) { - // Wildcard case is always taken. - NextBlock::Always(next_block_index) - } else { - NextBlock::If { - condition: Condition::Match { subject, case }, - next: next_block_index, - orelse: orelse_after_block, - } - }; - BasicBlock { stmts, next } -} - -/// Returns true if the [`MatchCase`] is a wildcard pattern. -fn is_wildcard(pattern: &MatchCase) -> bool { - /// Returns true if the [`Pattern`] is a wildcard pattern. - fn is_wildcard_pattern(pattern: &Pattern) -> bool { - match pattern { - Pattern::MatchValue(_) - | Pattern::MatchSingleton(_) - | Pattern::MatchSequence(_) - | Pattern::MatchMapping(_) - | Pattern::MatchClass(_) - | Pattern::MatchStar(_) => false, - Pattern::MatchAs(PatternMatchAs { pattern, .. }) => pattern.is_none(), - Pattern::MatchOr(PatternMatchOr { patterns, .. }) => { - patterns.iter().all(is_wildcard_pattern) - } - } - } - - pattern.guard.is_none() && is_wildcard_pattern(&pattern.pattern) -} - -#[derive(Debug, Default)] -struct BasicBlocksBuilder<'stmt> { - blocks: IndexVec>, -} - -impl<'stmt> BasicBlocksBuilder<'stmt> { - fn with_capacity(capacity: usize) -> Self { - Self { - blocks: IndexVec::with_capacity(capacity), - } - } - - /// Creates basic blocks from `stmts` and appends them to `blocks`. - fn create_blocks( - &mut self, - stmts: &'stmt [Stmt], - mut after: Option, - ) -> Option { - // We process the statements in reverse so that we can always point to the - // next block (as that should always be processed). - let mut stmts_iter = stmts.iter().enumerate().rev().peekable(); - while let Some((i, stmt)) = stmts_iter.next() { - let next = match stmt { - // Statements that continue to the next statement after execution. - Stmt::FunctionDef(_) - | Stmt::Import(_) - | Stmt::ImportFrom(_) - | Stmt::ClassDef(_) - | Stmt::Global(_) - | Stmt::Nonlocal(_) - | Stmt::Delete(_) - | Stmt::Assign(_) - | Stmt::AugAssign(_) - | Stmt::AnnAssign(_) - | Stmt::Break(_) - | Stmt::TypeAlias(_) - | Stmt::IpyEscapeCommand(_) - | Stmt::Pass(_) => self.unconditional_next_block(after), - Stmt::Continue(_) => { - // NOTE: the next branch gets fixed up in `change_next_block`. - self.unconditional_next_block(after) - } - // Statements that (can) divert the control flow. - Stmt::If(stmt_if) => { - let after_consequent_block = - self.maybe_next_block_index(after, || needs_next_block(&stmt_if.body)); - let after_alternate_block = self.maybe_next_block_index(after, || { - stmt_if - .elif_else_clauses - .last() - .map_or(true, |clause| needs_next_block(&clause.body)) - }); - - let consequent = - self.append_blocks_if_not_empty(&stmt_if.body, after_consequent_block); - - // Block ID of the next elif or else clause. - let mut next_branch = after_alternate_block; - - for clause in stmt_if.elif_else_clauses.iter().rev() { - let consequent = - self.append_blocks_if_not_empty(&clause.body, after_consequent_block); - - next_branch = if let Some(test) = &clause.test { - let next = NextBlock::If { - condition: Condition::Test(test), - next: consequent, - orelse: next_branch, - }; - let stmts = std::slice::from_ref(stmt); - let block = BasicBlock { stmts, next }; - self.blocks.push(block) - } else { - consequent - }; - } - - NextBlock::If { - condition: Condition::Test(&stmt_if.test), - next: consequent, - orelse: next_branch, - } - } - Stmt::While(StmtWhile { - test: condition, - body, - orelse, - .. - }) => loop_block(self, Condition::Test(condition), body, orelse, after), - Stmt::For(StmtFor { - iter: condition, - body, - orelse, - .. - }) => loop_block(self, Condition::Iterator(condition), body, orelse, after), - Stmt::Try(StmtTry { - body, - handlers, - orelse, - finalbody, - .. - }) => { - // TODO: handle `try` statements. The `try` control flow is very - // complex, what blocks are and aren't taken and from which - // block the control flow is actually returns is **very** - // specific to the contents of the block. Read - // - // very carefully. - // For now we'll skip over it. - let _ = (body, handlers, orelse, finalbody); // Silence unused code warnings. - self.unconditional_next_block(after) - } - Stmt::With(StmtWith { items, body, .. }) => { - // TODO: handle `with` statements, see - // . - // I recommend to `try` statements first as `with` can desugar - // to a `try` statement. - // For now we'll skip over it. - let _ = (items, body); // Silence unused code warnings. - self.unconditional_next_block(after) - } - Stmt::Match(StmtMatch { subject, cases, .. }) => { - let next_after_block = self.maybe_next_block_index(after, || { - // We don't need need a next block if all cases don't need a - // next block, i.e. if no cases need a next block, and we - // have a wildcard case (to ensure one of the block is - // always taken). - // NOTE: match statement require at least one case, so we - // don't have to worry about empty `cases`. - // TODO: support exhaustive cases without a wildcard. - cases.iter().any(|case| needs_next_block(&case.body)) - || !cases.iter().any(is_wildcard) - }); - let mut orelse_after_block = next_after_block; - for case in cases.iter().rev() { - let block = match_case( - self, - stmt, - subject, - case, - next_after_block, - orelse_after_block, - ); - // For the case above this use the just added case as the - // `orelse` branch, this convert the match statement to - // (essentially) a bunch of if statements. - orelse_after_block = self.blocks.push(block); - } - // TODO: currently we don't include the lines before the match - // statement in the block, unlike what we do for other - // statements. - after = Some(orelse_after_block); - continue; - } - Stmt::Raise(_) => { - // TODO: this needs special handling within `try` and `with` - // statements. For now we just terminate the execution, it's - // possible it's continued in an `catch` or `finally` block, - // possibly outside of the function. - // Also see `Stmt::Assert` handling. - NextBlock::Terminate - } - Stmt::Assert(stmt) => { - // TODO: this needs special handling within `try` and `with` - // statements. For now we just terminate the execution if the - // assertion fails, it's possible it's continued in an `catch` - // or `finally` block, possibly outside of the function. - // Also see `Stmt::Raise` handling. - let next = self.force_next_block_index(); - let orelse = self.fake_exception_block_index(); - NextBlock::If { - condition: Condition::Test(&stmt.test), - next, - orelse, - } - } - Stmt::Expr(stmt) => { - match &*stmt.value { - Expr::BoolOp(_) - | Expr::BinOp(_) - | Expr::UnaryOp(_) - | Expr::Dict(_) - | Expr::Set(_) - | Expr::Compare(_) - | Expr::Call(_) - | Expr::FString(_) - | Expr::StringLiteral(_) - | Expr::BytesLiteral(_) - | Expr::NumberLiteral(_) - | Expr::BooleanLiteral(_) - | Expr::NoneLiteral(_) - | Expr::EllipsisLiteral(_) - | Expr::Attribute(_) - | Expr::Subscript(_) - | Expr::Starred(_) - | Expr::Name(_) - | Expr::List(_) - | Expr::IpyEscapeCommand(_) - | Expr::Tuple(_) - | Expr::Slice(_) => self.unconditional_next_block(after), - // TODO: handle these expressions. - Expr::NamedExpr(_) - | Expr::Lambda(_) - | Expr::IfExp(_) - | Expr::ListComp(_) - | Expr::SetComp(_) - | Expr::DictComp(_) - | Expr::GeneratorExp(_) - | Expr::Await(_) - | Expr::Yield(_) - | Expr::YieldFrom(_) => self.unconditional_next_block(after), - } - } - // The tough branches are done, here is an easy one. - Stmt::Return(_) => NextBlock::Terminate, - }; - - // Include any statements in the block that don't divert the control flow. - let mut start = i; - let end = i + 1; - while stmts_iter - .next_if(|(_, stmt)| !is_control_flow_stmt(stmt)) - .is_some() - { - start -= 1; - } - - let block = BasicBlock { - stmts: &stmts[start..end], - next, - }; - after = Some(self.blocks.push(block)); - } - - after - } - - /// Calls [`create_blocks`] and returns this first block reached (i.e. the last - /// block). - fn append_blocks(&mut self, stmts: &'stmt [Stmt], after: Option) -> BlockIndex { - assert!(!stmts.is_empty()); - self.create_blocks(stmts, after) - .expect("Expect `create_blocks` to create a block if `stmts` is not empty") - } - - /// If `stmts` is not empty this calls [`create_blocks`] and returns this first - /// block reached (i.e. the last block). If `stmts` is empty this returns - /// `after` and doesn't change `blocks`. - fn append_blocks_if_not_empty( - &mut self, - stmts: &'stmt [Stmt], - after: BlockIndex, - ) -> BlockIndex { - if stmts.is_empty() { - after // Empty body, continue with block `after` it. - } else { - self.append_blocks(stmts, Some(after)) - } - } - - /// Select the next block from `blocks` unconditionally. - fn unconditional_next_block(&self, after: Option) -> NextBlock<'static> { - if let Some(after) = after { - return NextBlock::Always(after); - } - - // Either we continue with the next block (that is the last block `blocks`). - // Or it's the last statement, thus we terminate. - self.blocks - .last_index() - .map_or(NextBlock::Terminate, NextBlock::Always) - } - - /// Select the next block index from `blocks`. If there is no next block it will - /// add a fake/empty block. - fn force_next_block_index(&mut self) -> BlockIndex { - self.maybe_next_block_index(None, || true) - } - - /// Select the next block index from `blocks`. If there is no next block it will - /// add a fake/empty block if `condition` returns true. If `condition` returns - /// false the returned index may not be used. - fn maybe_next_block_index( - &mut self, - after: Option, - condition: impl FnOnce() -> bool, - ) -> BlockIndex { - if let Some(after) = after { - // Next block is already determined. - after - } else if let Some(idx) = self.blocks.last_index() { - // Otherwise we either continue with the next block (that is the last - // block in `blocks`). - idx - } else if condition() { - // Or if there are no blocks, but need one based on `condition` than we - // add a fake end block. - self.blocks.push(BasicBlock::EMPTY) - } else { - // NOTE: invalid, but because `condition` returned false this shouldn't - // be used. This only used as an optimisation to avoid adding fake end - // blocks. - BlockIndex::MAX - } - } - - /// Returns a block index for a fake exception block in `blocks`. - fn fake_exception_block_index(&mut self) -> BlockIndex { - for (i, block) in self.blocks.iter_enumerated() { - if block.is_exception() { - return i; - } - } - self.blocks.push(BasicBlock::EXCEPTION) - } - - /// Change the next basic block for the block, or chain of blocks, in index - /// `fixup_index` from `from` to `to`. - /// - /// This doesn't change the target if it's `NextBlock::Terminate`. - fn change_next_block( - &mut self, - mut fixup_index: BlockIndex, - from: BlockIndex, - to: BlockIndex, - check_condition: impl Fn(&BasicBlock) -> bool + Copy, - ) { - /// Check if we found our target and if `check_condition` is met. - fn is_target( - block: &BasicBlock<'_>, - got: BlockIndex, - expected: BlockIndex, - check_condition: impl Fn(&BasicBlock) -> bool, - ) -> bool { - got == expected && check_condition(block) - } - - loop { - match self.blocks.get(fixup_index).map(|b| &b.next) { - Some(NextBlock::Always(next)) => { - let next = *next; - if is_target(&self.blocks[fixup_index], next, from, check_condition) { - // Found our target, change it. - self.blocks[fixup_index].next = NextBlock::Always(to); - } - return; - } - Some(NextBlock::If { - condition, - next, - orelse, - }) => { - let idx = fixup_index; - let condition = condition.clone(); - let next = *next; - let orelse = *orelse; - let new_next = if is_target(&self.blocks[idx], next, from, check_condition) { - // Found our target in the next branch, change it (below). - Some(to) - } else { - // Follow the chain. - fixup_index = next; - None - }; - - let new_orelse = if is_target(&self.blocks[idx], orelse, from, check_condition) - { - // Found our target in the else branch, change it (below). - Some(to) - } else if new_next.is_none() { - // If we done with the next branch we only continue with the - // else branch. - fixup_index = orelse; - None - } else { - // If we're not done with the next and else branches we need - // to deal with the else branch before deal with the next - // branch (in the next iteration). - self.change_next_block(orelse, from, to, check_condition); - None - }; - - let (next, orelse) = match (new_next, new_orelse) { - (Some(new_next), Some(new_orelse)) => (new_next, new_orelse), - (Some(new_next), None) => (new_next, orelse), - (None, Some(new_orelse)) => (next, new_orelse), - (None, None) => continue, // Not changing anything. - }; - - self.blocks[idx].next = NextBlock::If { - condition, - next, - orelse, - }; - } - Some(NextBlock::Terminate) | None => return, - } - } - } - - fn finish(mut self) -> BasicBlocks<'stmt> { - if self.blocks.is_empty() { - self.blocks.push(BasicBlock::EMPTY); - } - - BasicBlocks { - blocks: self.blocks, - } - } -} - -impl<'stmt> std::ops::Deref for BasicBlocksBuilder<'stmt> { - type Target = IndexSlice>; - - fn deref(&self) -> &Self::Target { - &self.blocks - } -} - -impl<'stmt> std::ops::DerefMut for BasicBlocksBuilder<'stmt> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.blocks - } -} - -/// Returns true if `stmts` need a next block, false otherwise. -fn needs_next_block(stmts: &[Stmt]) -> bool { - // No statements, we automatically continue with the next block. - let Some(last) = stmts.last() else { - return true; - }; - - match last { - Stmt::Return(_) | Stmt::Raise(_) => false, - Stmt::If(stmt) => needs_next_block(&stmt.body) || stmt.elif_else_clauses.last().map_or(true, |clause| needs_next_block(&clause.body)), - Stmt::FunctionDef(_) - | Stmt::Import(_) - | Stmt::ImportFrom(_) - | Stmt::ClassDef(_) - | Stmt::Global(_) - | Stmt::Nonlocal(_) - | Stmt::Delete(_) - | Stmt::Assign(_) - | Stmt::AugAssign(_) - | Stmt::AnnAssign(_) - | Stmt::Expr(_) - | Stmt::Pass(_) - | Stmt::TypeAlias(_) - | Stmt::IpyEscapeCommand(_) - // TODO: check below. - | Stmt::Break(_) - | Stmt::Continue(_) - | Stmt::For(_) - | Stmt::While(_) - | Stmt::With(_) - | Stmt::Match(_) - | Stmt::Try(_) - | Stmt::Assert(_) => true, - } -} - -/// Returns true if `stmt` contains a control flow statement, e.g. an `if` or -/// `return` statement. -fn is_control_flow_stmt(stmt: &Stmt) -> bool { - match stmt { - Stmt::FunctionDef(_) - | Stmt::Import(_) - | Stmt::ImportFrom(_) - | Stmt::ClassDef(_) - | Stmt::Global(_) - | Stmt::Nonlocal(_) - | Stmt::Delete(_) - | Stmt::Assign(_) - | Stmt::AugAssign(_) - | Stmt::AnnAssign(_) - | Stmt::Expr(_) - | Stmt::TypeAlias(_) - | Stmt::IpyEscapeCommand(_) - | Stmt::Pass(_) => false, - Stmt::Return(_) - | Stmt::For(_) - | Stmt::While(_) - | Stmt::If(_) - | Stmt::With(_) - | Stmt::Match(_) - | Stmt::Raise(_) - | Stmt::Try(_) - | Stmt::Assert(_) - | Stmt::Break(_) - | Stmt::Continue(_) => true, - } -} - -/// Type to create a Mermaid graph. -/// -/// To learn amount Mermaid see , for the syntax -/// see . -struct MermaidGraph<'stmt, 'source> { - graph: &'stmt BasicBlocks<'stmt>, - source: &'source str, -} - -impl<'stmt, 'source> fmt::Display for MermaidGraph<'stmt, 'source> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Flowchart type of graph, top down. - writeln!(f, "flowchart TD")?; - - // List all blocks. - writeln!(f, " start((\"Start\"))")?; - writeln!(f, " return((\"End\"))")?; - for (i, block) in self.graph.blocks.iter().enumerate() { - let (open, close) = if block.is_sentinel() { - ("[[", "]]") - } else { - ("[", "]") - }; - write!(f, " block{i}{open}\"")?; - if block.is_empty() { - write!(f, "`*(empty)*`")?; - } else if block.is_exception() { - write!(f, "Exception raised")?; - } else { - for stmt in block.stmts { - let code_line = &self.source[stmt.range()].trim(); - mermaid_write_quoted_str(f, code_line)?; - write!(f, "\\n")?; - } - } - writeln!(f, "\"{close}")?; - } - writeln!(f)?; - - // Then link all the blocks. - writeln!(f, " start --> block{}", self.graph.blocks.len() - 1)?; - for (i, block) in self.graph.blocks.iter_enumerated().rev() { - let i = i.as_u32(); - match &block.next { - NextBlock::Always(target) => { - writeln!(f, " block{i} --> block{target}", target = target.as_u32())?; - } - NextBlock::If { - condition, - next, - orelse, - } => { - let condition_code = &self.source[condition.range()].trim(); - writeln!( - f, - " block{i} -- \"{condition_code}\" --> block{next}", - next = next.as_u32() - )?; - writeln!( - f, - " block{i} -- \"else\" --> block{orelse}", - orelse = orelse.as_u32() - )?; - } - NextBlock::Terminate => writeln!(f, " block{i} --> return")?, - } - } - - Ok(()) - } -} - -/// Escape double quotes (`"`) in `value` using `#quot;`. -fn mermaid_write_quoted_str(f: &mut fmt::Formatter<'_>, value: &str) -> fmt::Result { - let mut parts = value.split('"'); - if let Some(v) = parts.next() { - write!(f, "{v}")?; - } - for v in parts { - write!(f, "#quot;{v}")?; - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use std::fs; - use std::path::PathBuf; - - use ruff_python_parser::{parse, Mode}; - use ruff_text_size::Ranged; - use std::fmt::Write; - use test_case::test_case; - - use crate::rules::ruff::rules::unreachable::{ - BasicBlocks, BlockIndex, MermaidGraph, NextBlock, - }; - - #[test_case("simple.py")] - #[test_case("if.py")] - #[test_case("while.py")] - #[test_case("for.py")] - #[test_case("async-for.py")] - //#[test_case("try.py")] // TODO. - #[test_case("raise.py")] - #[test_case("assert.py")] - #[test_case("match.py")] - fn control_flow_graph(filename: &str) { - let path = PathBuf::from_iter(["resources/test/fixtures/control-flow-graph", filename]); - let source = fs::read_to_string(path).expect("failed to read file"); - let stmts = parse(&source, Mode::Module) - .unwrap_or_else(|err| panic!("failed to parse source: '{source}': {err}")) - .expect_module() - .body; - - let mut output = String::new(); - - for (i, stmts) in stmts.into_iter().enumerate() { - let Some(func) = stmts.function_def_stmt() else { - use std::io::Write; - let _ = std::io::stderr().write_all(b"unexpected statement kind, ignoring"); - continue; - }; - - let got = BasicBlocks::from(&*func.body); - // Basic sanity checks. - assert!(!got.blocks.is_empty(), "basic blocks should never be empty"); - assert_eq!( - got.blocks.first().unwrap().next, - NextBlock::Terminate, - "first block should always terminate" - ); - - let got_mermaid = MermaidGraph { - graph: &got, - source: &source, - }; - - // All block index should be valid. - let valid = BlockIndex::from_usize(got.blocks.len()); - for block in &got.blocks { - match block.next { - NextBlock::Always(index) => assert!(index < valid, "invalid block index"), - NextBlock::If { next, orelse, .. } => { - assert!(next < valid, "invalid next block index"); - assert!(orelse < valid, "invalid orelse block index"); - } - NextBlock::Terminate => {} - } - } - - writeln!( - output, - "## Function {i}\n### Source\n```python\n{}\n```\n\n### Control Flow Graph\n```mermaid\n{}```\n", - &source[func.range()], - got_mermaid - ) - .unwrap(); - } - - insta::with_settings!({ - omit_expression => true, - input_file => filename, - description => "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." - }, { - insta::assert_snapshot!(format!("{filename}.md"), output); - }); - } -} diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF014_RUF014.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF014_RUF014.py.snap deleted file mode 100644 index a30be8cd6e71b..0000000000000 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF014_RUF014.py.snap +++ /dev/null @@ -1,249 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/ruff/mod.rs ---- -RUF014.py:3:5: RUF014 Unreachable code in after_return - | -1 | def after_return(): -2 | return "reachable" -3 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -4 | -5 | async def also_works_on_async_functions(): - | - -RUF014.py:7:5: RUF014 Unreachable code in also_works_on_async_functions - | -5 | async def also_works_on_async_functions(): -6 | return "reachable" -7 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -8 | -9 | def if_always_true(): - | - -RUF014.py:12:5: RUF014 Unreachable code in if_always_true - | -10 | if True: -11 | return "reachable" -12 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -13 | -14 | def if_always_false(): - | - -RUF014.py:16:9: RUF014 Unreachable code in if_always_false - | -14 | def if_always_false(): -15 | if False: -16 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -17 | return "reachable" - | - -RUF014.py:21:9: RUF014 Unreachable code in if_elif_always_false - | -19 | def if_elif_always_false(): -20 | if False: -21 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -22 | elif False: -23 | return "also unreachable" - | - -RUF014.py:23:9: RUF014 Unreachable code in if_elif_always_false - | -21 | return "unreachable" -22 | elif False: -23 | return "also unreachable" - | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF014 -24 | return "reachable" - | - -RUF014.py:28:9: RUF014 Unreachable code in if_elif_always_true - | -26 | def if_elif_always_true(): -27 | if False: -28 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -29 | elif True: -30 | return "reachable" - | - -RUF014.py:31:5: RUF014 Unreachable code in if_elif_always_true - | -29 | elif True: -30 | return "reachable" -31 | return "also unreachable" - | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF014 -32 | -33 | def ends_with_if(): - | - -RUF014.py:35:9: RUF014 Unreachable code in ends_with_if - | -33 | def ends_with_if(): -34 | if False: -35 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -36 | else: -37 | return "reachable" - | - -RUF014.py:42:5: RUF014 Unreachable code in infinite_loop - | -40 | while True: -41 | continue -42 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -43 | -44 | ''' TODO: we could determine these, but we don't yet. - | - -RUF014.py:75:5: RUF014 Unreachable code in match_wildcard - | -73 | case _: -74 | return "reachable" -75 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -76 | -77 | def match_case_and_wildcard(status): - | - -RUF014.py:83:5: RUF014 Unreachable code in match_case_and_wildcard - | -81 | case _: -82 | return "reachable" -83 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -84 | -85 | def raise_exception(): - | - -RUF014.py:87:5: RUF014 Unreachable code in raise_exception - | -85 | def raise_exception(): -86 | raise Exception -87 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -88 | -89 | def while_false(): - | - -RUF014.py:91:9: RUF014 Unreachable code in while_false - | -89 | def while_false(): -90 | while False: -91 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -92 | return "reachable" - | - -RUF014.py:96:9: RUF014 Unreachable code in while_false_else - | -94 | def while_false_else(): -95 | while False: -96 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -97 | else: -98 | return "reachable" - | - -RUF014.py:102:9: RUF014 Unreachable code in while_false_else_return - | -100 | def while_false_else_return(): -101 | while False: -102 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -103 | else: -104 | return "reachable" - | - -RUF014.py:105:5: RUF014 Unreachable code in while_false_else_return - | -103 | else: -104 | return "reachable" -105 | return "also unreachable" - | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF014 -106 | -107 | def while_true(): - | - -RUF014.py:110:5: RUF014 Unreachable code in while_true - | -108 | while True: -109 | return "reachable" -110 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -111 | -112 | def while_true_else(): - | - -RUF014.py:116:9: RUF014 Unreachable code in while_true_else - | -114 | return "reachable" -115 | else: -116 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -117 | -118 | def while_true_else_return(): - | - -RUF014.py:122:9: RUF014 Unreachable code in while_true_else_return - | -120 | return "reachable" -121 | else: -122 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -123 | return "also unreachable" - | - -RUF014.py:123:5: RUF014 Unreachable code in while_true_else_return - | -121 | else: -122 | return "unreachable" -123 | return "also unreachable" - | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF014 -124 | -125 | def while_false_var_i(): - | - -RUF014.py:128:9: RUF014 Unreachable code in while_false_var_i - | -126 | i = 0 -127 | while False: -128 | i += 1 - | ^^^^^^ RUF014 -129 | return i - | - -RUF014.py:135:5: RUF014 Unreachable code in while_true_var_i - | -133 | while True: -134 | i += 1 -135 | return i - | ^^^^^^^^ RUF014 -136 | -137 | def while_infinite(): - | - -RUF014.py:140:5: RUF014 Unreachable code in while_infinite - | -138 | while True: -139 | pass -140 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -141 | -142 | def while_if_true(): - | - -RUF014.py:146:5: RUF014 Unreachable code in while_if_true - | -144 | if True: -145 | return "reachable" -146 | return "unreachable" - | ^^^^^^^^^^^^^^^^^^^^ RUF014 -147 | -148 | # Test case found in the Bokeh repository that trigger a false positive. - | - - diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index b0f5f8bb7cb62..5325e9ba7260d 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -1213,8 +1213,6 @@ mod tests { } .as_rule_table(preview.map(|preview| preview.mode).unwrap_or_default()) .iter_enabled() - // Filter out rule gated behind `#[cfg(feature = "unreachable-code")]`, which is off-by-default - .filter(|rule| rule.noqa_code() != "RUF014") .collect() } From a31a314b2be5170c591abbd8819ab17444b7a723 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 11 Jan 2024 21:16:19 -0500 Subject: [PATCH 04/10] Account for possibly-empty f-string values in truthiness logic (#9484) Closes https://github.com/astral-sh/ruff/issues/9479. --- .../test/fixtures/flake8_simplify/SIM222.py | 7 ++ .../test/fixtures/flake8_simplify/SIM223.py | 6 + ...ke8_simplify__tests__SIM222_SIM222.py.snap | 20 +++ ...ke8_simplify__tests__SIM223_SIM223.py.snap | 20 +++ crates/ruff_python_ast/src/helpers.rs | 119 +++++++++++++++--- 5 files changed, 155 insertions(+), 17 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM222.py b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM222.py index dcf730f4a75ec..bd1282ce0f1b8 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM222.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM222.py @@ -160,3 +160,10 @@ def secondToTime(s0: int) -> (int, int, int) or str: def secondToTime(s0: int) -> ((int, int, int) or str): m, s = divmod(s0, 60) + + +# Regression test for: https://github.com/astral-sh/ruff/issues/9479 +print(f"{a}{b}" or "bar") +print(f"{a}{''}" or "bar") +print(f"{''}{''}" or "bar") +print(f"{1}{''}" or "bar") diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM223.py b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM223.py index 98ad6d6e9e645..1842f06994c1f 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM223.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM223.py @@ -147,3 +147,9 @@ if f(a and [] and False and []): # SIM223 pass + +# Regression test for: https://github.com/astral-sh/ruff/issues/9479 +print(f"{a}{b}" and "bar") +print(f"{a}{''}" and "bar") +print(f"{''}{''}" and "bar") +print(f"{1}{''}" and "bar") diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM222_SIM222.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM222_SIM222.py.snap index 585be6c06eabb..ddf4598e13b59 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM222_SIM222.py.snap +++ b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM222_SIM222.py.snap @@ -1040,5 +1040,25 @@ SIM222.py:161:31: SIM222 [*] Use `(int, int, int)` instead of `(int, int, int) o 161 |-def secondToTime(s0: int) -> ((int, int, int) or str): 161 |+def secondToTime(s0: int) -> ((int, int, int)): 162 162 | m, s = divmod(s0, 60) +163 163 | +164 164 | + +SIM222.py:168:7: SIM222 [*] Use `"bar"` instead of `... or "bar"` + | +166 | print(f"{a}{b}" or "bar") +167 | print(f"{a}{''}" or "bar") +168 | print(f"{''}{''}" or "bar") + | ^^^^^^^^^^^^^^^^^^^^ SIM222 +169 | print(f"{1}{''}" or "bar") + | + = help: Replace with `"bar"` + +ℹ Unsafe fix +165 165 | # Regression test for: https://github.com/astral-sh/ruff/issues/9479 +166 166 | print(f"{a}{b}" or "bar") +167 167 | print(f"{a}{''}" or "bar") +168 |-print(f"{''}{''}" or "bar") + 168 |+print("bar") +169 169 | print(f"{1}{''}" or "bar") diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM223_SIM223.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM223_SIM223.py.snap index d1d2fae78d113..855b2091823cb 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM223_SIM223.py.snap +++ b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM223_SIM223.py.snap @@ -1003,5 +1003,25 @@ SIM223.py:148:12: SIM223 [*] Use `[]` instead of `[] and ...` 148 |-if f(a and [] and False and []): # SIM223 148 |+if f(a and []): # SIM223 149 149 | pass +150 150 | +151 151 | # Regression test for: https://github.com/astral-sh/ruff/issues/9479 + +SIM223.py:154:7: SIM223 [*] Use `f"{''}{''}"` instead of `f"{''}{''}" and ...` + | +152 | print(f"{a}{b}" and "bar") +153 | print(f"{a}{''}" and "bar") +154 | print(f"{''}{''}" and "bar") + | ^^^^^^^^^^^^^^^^^^^^^ SIM223 +155 | print(f"{1}{''}" and "bar") + | + = help: Replace with `f"{''}{''}"` + +ℹ Unsafe fix +151 151 | # Regression test for: https://github.com/astral-sh/ruff/issues/9479 +152 152 | print(f"{a}{b}" and "bar") +153 153 | print(f"{a}{''}" and "bar") +154 |-print(f"{''}{''}" and "bar") + 154 |+print(f"{''}{''}") +155 155 | print(f"{1}{''}" and "bar") diff --git a/crates/ruff_python_ast/src/helpers.rs b/crates/ruff_python_ast/src/helpers.rs index bb463c95b3f45..8926f54138cbd 100644 --- a/crates/ruff_python_ast/src/helpers.rs +++ b/crates/ruff_python_ast/src/helpers.rs @@ -308,10 +308,13 @@ pub fn any_over_pattern(pattern: &Pattern, func: &dyn Fn(&Expr) -> bool) -> bool } } -pub fn any_over_f_string_element(element: &FStringElement, func: &dyn Fn(&Expr) -> bool) -> bool { +pub fn any_over_f_string_element( + element: &ast::FStringElement, + func: &dyn Fn(&Expr) -> bool, +) -> bool { match element { - FStringElement::Literal(_) => false, - FStringElement::Expression(ast::FStringExpressionElement { + ast::FStringElement::Literal(_) => false, + ast::FStringElement::Expression(ast::FStringExpressionElement { expression, format_spec, .. @@ -1171,21 +1174,10 @@ impl Truthiness { } Expr::NoneLiteral(_) => Self::Falsey, Expr::EllipsisLiteral(_) => Self::Truthy, - Expr::FString(ast::ExprFString { value, .. }) => { - if value.iter().all(|part| match part { - ast::FStringPart::Literal(string_literal) => string_literal.is_empty(), - ast::FStringPart::FString(f_string) => f_string.elements.is_empty(), - }) { + Expr::FString(f_string) => { + if is_empty_f_string(f_string) { Self::Falsey - } else if value - .elements() - .any(|f_string_element| match f_string_element { - ast::FStringElement::Literal(ast::FStringLiteralElement { - value, .. - }) => !value.is_empty(), - ast::FStringElement::Expression(_) => true, - }) - { + } else if is_non_empty_f_string(f_string) { Self::Truthy } else { Self::Unknown @@ -1243,6 +1235,99 @@ impl Truthiness { } } +/// Returns `true` if the expression definitely resolves to a non-empty string, when used as an +/// f-string expression, or `false` if the expression may resolve to an empty string. +fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool { + fn inner(expr: &Expr) -> bool { + match expr { + // When stringified, these expressions are always non-empty. + Expr::Lambda(_) => true, + Expr::Dict(_) => true, + Expr::Set(_) => true, + Expr::ListComp(_) => true, + Expr::SetComp(_) => true, + Expr::DictComp(_) => true, + Expr::Compare(_) => true, + Expr::NumberLiteral(_) => true, + Expr::BooleanLiteral(_) => true, + Expr::NoneLiteral(_) => true, + Expr::EllipsisLiteral(_) => true, + Expr::List(_) => true, + Expr::Tuple(_) => true, + + // These expressions must resolve to the inner expression. + Expr::IfExp(ast::ExprIfExp { body, orelse, .. }) => inner(body) && inner(orelse), + Expr::NamedExpr(ast::ExprNamedExpr { value, .. }) => inner(value), + + // These expressions are complex. We can't determine whether they're empty or not. + Expr::BoolOp(ast::ExprBoolOp { .. }) => false, + Expr::BinOp(ast::ExprBinOp { .. }) => false, + Expr::UnaryOp(ast::ExprUnaryOp { .. }) => false, + Expr::GeneratorExp(_) => false, + Expr::Await(_) => false, + Expr::Yield(_) => false, + Expr::YieldFrom(_) => false, + Expr::Call(_) => false, + Expr::Attribute(_) => false, + Expr::Subscript(_) => false, + Expr::Starred(_) => false, + Expr::Name(_) => false, + Expr::Slice(_) => false, + Expr::IpyEscapeCommand(_) => false, + + // These literals may or may not be empty. + Expr::FString(f_string) => is_non_empty_f_string(f_string), + Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => !value.is_empty(), + Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => !value.is_empty(), + } + } + + expr.value.iter().any(|part| match part { + ast::FStringPart::Literal(string_literal) => !string_literal.is_empty(), + ast::FStringPart::FString(f_string) => { + f_string.elements.iter().all(|element| match element { + FStringElement::Literal(string_literal) => !string_literal.is_empty(), + FStringElement::Expression(f_string) => inner(&f_string.expression), + }) + } + }) +} + +/// Returns `true` if the expression definitely resolves to the empty string, when used as an f-string +/// expression. +fn is_empty_f_string(expr: &ast::ExprFString) -> bool { + fn inner(expr: &Expr) -> bool { + match expr { + Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.is_empty(), + Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => value.is_empty(), + Expr::FString(ast::ExprFString { value, .. }) => { + value + .elements() + .all(|f_string_element| match f_string_element { + FStringElement::Literal(ast::FStringLiteralElement { value, .. }) => { + value.is_empty() + } + FStringElement::Expression(ast::FStringExpressionElement { + expression, + .. + }) => inner(expression), + }) + } + _ => false, + } + } + + expr.value.iter().all(|part| match part { + ast::FStringPart::Literal(string_literal) => string_literal.is_empty(), + ast::FStringPart::FString(f_string) => { + f_string.elements.iter().all(|element| match element { + FStringElement::Literal(string_literal) => string_literal.is_empty(), + FStringElement::Expression(f_string) => inner(&f_string.expression), + }) + } + }) +} + pub fn generate_comparison( left: &Expr, ops: &[CmpOp], From 3daf6e1b6ddef48ee6d429c5db16e0283ab01b84 Mon Sep 17 00:00:00 2001 From: KotlinIsland <65446343+KotlinIsland@users.noreply.github.com> Date: Fri, 12 Jan 2024 14:43:46 +1000 Subject: [PATCH 05/10] =?UTF-8?q?(=F0=9F=90=9E)=20Add=20the=20missing=20pe?= =?UTF-8?q?riod=20in=20error=20message=20(#9485)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary I noticed that there should be a missing period added to some of the new error messages for Unnecessary dunder call: ``` sandpit\test.py:6:16: PLC2801 Unnecessary dunder call to `__getattribute__`. Access attribute directly or use getattr built-in function.. ``` ## Test Plan Static analysis of the implementation, as this has no existing test cases. --- .../src/rules/pylint/rules/unnecessary_dunder_call.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs index 9c81143a38871..ea88f7b9121d0 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs @@ -283,15 +283,15 @@ impl DunderReplacement { "__delitem__" => Some(Self::MessageOnly("Use `del` statement")), "__divmod__" => Some(Self::MessageOnly("Use `divmod()` builtin")), "__format__" => Some(Self::MessageOnly( - "Use `format` builtin, format string method, or f-string.", + "Use `format` builtin, format string method, or f-string", )), "__fspath__" => Some(Self::MessageOnly("Use `os.fspath` function")), "__get__" => Some(Self::MessageOnly("Use `get` method")), "__getattr__" => Some(Self::MessageOnly( - "Access attribute directly or use getattr built-in function.", + "Access attribute directly or use getattr built-in function", )), "__getattribute__" => Some(Self::MessageOnly( - "Access attribute directly or use getattr built-in function.", + "Access attribute directly or use getattr built-in function", )), "__getitem__" => Some(Self::MessageOnly("Access item via subscript")), "__init__" => Some(Self::MessageOnly("Instantiate class directly")), @@ -304,7 +304,7 @@ impl DunderReplacement { "__rpow__" => Some(Self::MessageOnly("Use ** operator or `pow()` builtin")), "__set__" => Some(Self::MessageOnly("Use subscript assignment")), "__setattr__" => Some(Self::MessageOnly( - "Mutate attribute directly or use setattr built-in function.", + "Mutate attribute directly or use setattr built-in function", )), "__setitem__" => Some(Self::MessageOnly("Use subscript assignment")), "__truncate__" => Some(Self::MessageOnly("Use `math.trunc()` function")), From 395cdf04e519ca516b7f9822effd9275ea12ea7d Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 12 Jan 2024 09:00:44 +0000 Subject: [PATCH 06/10] Fix backticks in RUF021 docs (#9488) The docs for this rule aren't generating properly: https://docs.astral.sh/ruff/rules/parenthesize-chained-operators/#why-is-this-bad. I assume this is the reason why! --- .../src/rules/ruff/rules/parenthesize_logical_operators.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/ruff/rules/parenthesize_logical_operators.rs b/crates/ruff_linter/src/rules/ruff/rules/parenthesize_logical_operators.rs index 4a96af1cdf4a3..04e140bf360bf 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/parenthesize_logical_operators.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/parenthesize_logical_operators.rs @@ -32,7 +32,7 @@ use crate::checkers::ast::Checker; /// /// d, e, f = 0, 1, 2 /// y = (d and e) or f -/// ```` +/// ``` #[violation] pub struct ParenthesizeChainedOperators; From 1602df164359e050f0f8fdbbaa60ae5ffb357be5 Mon Sep 17 00:00:00 2001 From: Aleksei Latyshev Date: Fri, 12 Jan 2024 14:48:45 +0100 Subject: [PATCH 07/10] Fix message for __aenter__ in PLC2801 (#9492) ## Summary Fix the message for `__aenter__ ` in PLC2801 (introduced in https://github.com/astral-sh/ruff/pull/9166) There is no `aenter` builtin in Python, so the current message is misleading. I take the message from original lint https://github.com/pylint-dev/pylint/blob/main/pylint/constants.py#L211 P.S. I think here should be more accurate synchronization with original lint (e.g. the current implementation will not lint `__enter__` on my first sight), but it is out-of-scope of this change. ## Test Plan --- .../src/rules/pylint/rules/unnecessary_dunder_call.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs index ea88f7b9121d0..2d8ebd9516691 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs @@ -273,7 +273,7 @@ impl DunderReplacement { "__str__" => Some(Self::Builtin("str", "Use `str()` builtin")), "__subclasscheck__" => Some(Self::Builtin("issubclass", "Use `issubclass()` builtin")), - "__aenter__" => Some(Self::MessageOnly("Use `aenter()` builtin")), + "__aenter__" => Some(Self::MessageOnly("Invoke context manager directly")), "__ceil__" => Some(Self::MessageOnly("Use `math.ceil()` function")), "__copy__" => Some(Self::MessageOnly("Use `copy.copy()` function")), "__deepcopy__" => Some(Self::MessageOnly("Use `copy.deepcopy()` function")), From d16c4a2d25a27dad1ef26c0e496471139adb4522 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 12 Jan 2024 09:27:39 -0500 Subject: [PATCH 08/10] Bump version to v0.1.13 (#9493) --- CHANGELOG.md | 9 +++++++++ Cargo.lock | 6 +++--- README.md | 2 +- crates/ruff_cli/Cargo.toml | 2 +- crates/ruff_linter/Cargo.toml | 2 +- crates/ruff_shrinking/Cargo.toml | 2 +- docs/integrations.md | 6 +++--- pyproject.toml | 2 +- scripts/benchmarks/pyproject.toml | 2 +- 9 files changed, 21 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddb00205aeb5b..884f73300713d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.1.13 + +### Bug fixes + +- Include base pyproject when initializing cache settings ([#9480](https://github.com/astral-sh/ruff/pull/9480)) +- \[`flake8-simplify`\] Account for possibly-empty f-string values in truthiness logic ([#9484](https://github.com/astral-sh/ruff/pull/9484)) +- \[`pylint`\] Add the missing period in `unnecessary-dunder-call` ([#9485](https://github.com/astral-sh/ruff/pull/9485)) +- \[`pylint`\] Fix `__aenter__` message in `unnecessary-dunder-call` ([#9492](https://github.com/astral-sh/ruff/pull/9492)) + ## 0.1.12 ### Preview features diff --git a/Cargo.lock b/Cargo.lock index c72947bb034ee..a74e03b9e7c6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2038,7 +2038,7 @@ dependencies = [ [[package]] name = "ruff_cli" -version = "0.1.12" +version = "0.1.13" dependencies = [ "anyhow", "argfile", @@ -2165,7 +2165,7 @@ dependencies = [ [[package]] name = "ruff_linter" -version = "0.1.12" +version = "0.1.13" dependencies = [ "aho-corasick", "annotate-snippets 0.9.2", @@ -2417,7 +2417,7 @@ dependencies = [ [[package]] name = "ruff_shrinking" -version = "0.1.12" +version = "0.1.13" dependencies = [ "anyhow", "clap", diff --git a/README.md b/README.md index 4813b5a6c376a..f212a97a46651 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.1.12 + rev: v0.1.13 hooks: # Run the linter. - id: ruff diff --git a/crates/ruff_cli/Cargo.toml b/crates/ruff_cli/Cargo.toml index cc4aabeee250f..cb92c81ad94b6 100644 --- a/crates/ruff_cli/Cargo.toml +++ b/crates/ruff_cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_cli" -version = "0.1.12" +version = "0.1.13" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index 1232ac8fb11bf..1a1fe1445cc79 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_linter" -version = "0.1.12" +version = "0.1.13" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_shrinking/Cargo.toml b/crates/ruff_shrinking/Cargo.toml index d7b6f3e4b073a..38caa432248c4 100644 --- a/crates/ruff_shrinking/Cargo.toml +++ b/crates/ruff_shrinking/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_shrinking" -version = "0.1.12" +version = "0.1.13" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/docs/integrations.md b/docs/integrations.md index 9675f11958cb0..efefdb0255ff9 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -14,7 +14,7 @@ Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-c ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.1.12 + rev: v0.1.13 hooks: # Run the linter. - id: ruff @@ -27,7 +27,7 @@ To enable lint fixes, add the `--fix` argument to the lint hook: ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.1.12 + rev: v0.1.13 hooks: # Run the linter. - id: ruff @@ -41,7 +41,7 @@ To run the hooks over Jupyter Notebooks too, add `jupyter` to the list of allowe ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.1.12 + rev: v0.1.13 hooks: # Run the linter. - id: ruff diff --git a/pyproject.toml b/pyproject.toml index cff313b8b6954..670cfbd2525d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "ruff" -version = "0.1.12" +version = "0.1.13" description = "An extremely fast Python linter and code formatter, written in Rust." authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] readme = "README.md" diff --git a/scripts/benchmarks/pyproject.toml b/scripts/benchmarks/pyproject.toml index f050ed4413ede..ff9e7c4f38604 100644 --- a/scripts/benchmarks/pyproject.toml +++ b/scripts/benchmarks/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "scripts" -version = "0.1.12" +version = "0.1.13" description = "" authors = ["Charles Marsh "] From 3261d16e6199550b465bb0053cd0baa26a894cef Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 12 Jan 2024 13:53:25 -0500 Subject: [PATCH 09/10] Add `--extension` support to the formatter (#9483) ## Summary We added `--extension` to `ruff check`, but it's equally applicable to `ruff format`. Closes https://github.com/astral-sh/ruff/issues/9482. Resolves https://github.com/astral-sh/ruff/discussions/9481. ## Test Plan `cargo test` --- crates/ruff_cli/src/args.rs | 14 ++-- crates/ruff_cli/src/commands/format.rs | 16 +++-- crates/ruff_cli/src/commands/format_stdin.rs | 19 ++++-- crates/ruff_cli/src/diagnostics.rs | 26 +++---- crates/ruff_cli/tests/format.rs | 70 +++++++++++++++++++ crates/ruff_cli/tests/lint.rs | 72 ++++++++++++++++++++ crates/ruff_linter/src/settings/mod.rs | 2 +- crates/ruff_linter/src/settings/types.rs | 7 +- crates/ruff_workspace/src/configuration.rs | 36 ++++++---- crates/ruff_workspace/src/settings.rs | 6 +- docs/configuration.md | 4 ++ 11 files changed, 221 insertions(+), 51 deletions(-) diff --git a/crates/ruff_cli/src/args.rs b/crates/ruff_cli/src/args.rs index 6c5ed14456ab9..4276fb0cc93a9 100644 --- a/crates/ruff_cli/src/args.rs +++ b/crates/ruff_cli/src/args.rs @@ -290,6 +290,10 @@ pub struct CheckCommand { /// The name of the file when passing it through stdin. #[arg(long, help_heading = "Miscellaneous")] pub stdin_filename: Option, + /// List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]). For + /// example, to treat `.ipy` files as IPython notebooks, use `--extension ipy:ipynb`. + #[arg(long, value_delimiter = ',')] + pub extension: Option>, /// Exit with status code "0", even upon detecting lint violations. #[arg( short, @@ -352,9 +356,6 @@ pub struct CheckCommand { conflicts_with = "watch", )] pub show_settings: bool, - /// List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]). - #[arg(long, value_delimiter = ',', hide = true)] - pub extension: Option>, /// Dev-only argument to show fixes #[arg(long, hide = true)] pub ecosystem_ci: bool, @@ -423,6 +424,10 @@ pub struct FormatCommand { /// The name of the file when passing it through stdin. #[arg(long, help_heading = "Miscellaneous")] pub stdin_filename: Option, + /// List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]). For + /// example, to treat `.ipy` files as IPython notebooks, use `--extension ipy:ipynb`. + #[arg(long, value_delimiter = ',')] + pub extension: Option>, /// The minimum Python version that should be supported. #[arg(long, value_enum)] pub target_version: Option, @@ -571,6 +576,7 @@ impl FormatCommand { force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude), target_version: self.target_version, cache_dir: self.cache_dir, + extension: self.extension, // Unsupported on the formatter CLI, but required on `Overrides`. ..CliOverrides::default() @@ -739,7 +745,7 @@ impl ConfigurationTransformer for CliOverrides { config.target_version = Some(*target_version); } if let Some(extension) = &self.extension { - config.lint.extension = Some(extension.clone().into_iter().collect()); + config.extension = Some(extension.iter().cloned().collect()); } config diff --git a/crates/ruff_cli/src/commands/format.rs b/crates/ruff_cli/src/commands/format.rs index c9287e459c431..f8ecadaf3f24f 100644 --- a/crates/ruff_cli/src/commands/format.rs +++ b/crates/ruff_cli/src/commands/format.rs @@ -106,13 +106,19 @@ pub(crate) fn format( match entry { Ok(resolved_file) => { let path = resolved_file.path(); - let SourceType::Python(source_type) = SourceType::from(&path) else { - // Ignore any non-Python files. - return None; - }; - let settings = resolver.resolve(path); + let source_type = match settings.formatter.extension.get(path) { + None => match SourceType::from(path) { + SourceType::Python(source_type) => source_type, + SourceType::Toml(_) => { + // Ignore any non-Python files. + return None; + } + }, + Some(language) => PySourceType::from(language), + }; + // Ignore files that are excluded from formatting if (settings.file_resolver.force_exclude || !resolved_file.is_root()) && match_exclusion( diff --git a/crates/ruff_cli/src/commands/format_stdin.rs b/crates/ruff_cli/src/commands/format_stdin.rs index 33fa5160424fe..41695ae8b5443 100644 --- a/crates/ruff_cli/src/commands/format_stdin.rs +++ b/crates/ruff_cli/src/commands/format_stdin.rs @@ -53,16 +53,23 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R } let path = cli.stdin_filename.as_deref(); + let settings = &resolver.base_settings().formatter; - let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else { - if mode.is_write() { - parrot_stdin()?; - } - return Ok(ExitStatus::Success); + let source_type = match path.and_then(|path| settings.extension.get(path)) { + None => match path.map(SourceType::from).unwrap_or_default() { + SourceType::Python(source_type) => source_type, + SourceType::Toml(_) => { + if mode.is_write() { + parrot_stdin()?; + } + return Ok(ExitStatus::Success); + } + }, + Some(language) => PySourceType::from(language), }; // Format the file. - match format_source_code(path, &resolver.base_settings().formatter, source_type, mode) { + match format_source_code(path, settings, source_type, mode) { Ok(result) => match mode { FormatMode::Write => Ok(ExitStatus::Success), FormatMode::Check | FormatMode::Diff => { diff --git a/crates/ruff_cli/src/diagnostics.rs b/crates/ruff_cli/src/diagnostics.rs index ad9913f083f8b..58d26e894ad14 100644 --- a/crates/ruff_cli/src/diagnostics.rs +++ b/crates/ruff_cli/src/diagnostics.rs @@ -17,7 +17,7 @@ use ruff_linter::logging::DisplayParseError; use ruff_linter::message::Message; use ruff_linter::pyproject_toml::lint_pyproject_toml; use ruff_linter::registry::AsRule; -use ruff_linter::settings::types::{ExtensionMapping, UnsafeFixes}; +use ruff_linter::settings::types::UnsafeFixes; use ruff_linter::settings::{flags, LinterSettings}; use ruff_linter::source_kind::{SourceError, SourceKind}; use ruff_linter::{fs, IOError, SyntaxError}; @@ -179,11 +179,6 @@ impl AddAssign for FixMap { } } -fn override_source_type(path: Option<&Path>, extension: &ExtensionMapping) -> Option { - let ext = path?.extension()?.to_str()?; - extension.get(ext).map(PySourceType::from) -} - /// Lint the source code at the given `Path`. pub(crate) fn lint_path( path: &Path, @@ -228,7 +223,7 @@ pub(crate) fn lint_path( debug!("Checking: {}", path.display()); - let source_type = match override_source_type(Some(path), &settings.extension) { + let source_type = match settings.extension.get(path).map(PySourceType::from) { Some(source_type) => source_type, None => match SourceType::from(path) { SourceType::Toml(TomlSourceType::Pyproject) => { @@ -398,15 +393,14 @@ pub(crate) fn lint_stdin( fix_mode: flags::FixMode, ) -> Result { // TODO(charlie): Support `pyproject.toml`. - let source_type = if let Some(source_type) = - override_source_type(path, &settings.linter.extension) - { - source_type - } else { - let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else { - return Ok(Diagnostics::default()); - }; - source_type + let source_type = match path.and_then(|path| settings.linter.extension.get(path)) { + None => match path.map(SourceType::from).unwrap_or_default() { + SourceType::Python(source_type) => source_type, + SourceType::Toml(_) => { + return Ok(Diagnostics::default()); + } + }, + Some(language) => PySourceType::from(language), }; // Extract the sources from the file. diff --git a/crates/ruff_cli/tests/format.rs b/crates/ruff_cli/tests/format.rs index 232635b8e3047..d7a255fb555ac 100644 --- a/crates/ruff_cli/tests/format.rs +++ b/crates/ruff_cli/tests/format.rs @@ -1468,3 +1468,73 @@ fn test_notebook_trailing_semicolon() { ----- stderr ----- "###); } + +#[test] +fn extension() -> Result<()> { + let tempdir = TempDir::new()?; + + let ruff_toml = tempdir.path().join("ruff.toml"); + fs::write( + &ruff_toml, + r#" +include = ["*.ipy"] +"#, + )?; + + fs::write( + tempdir.path().join("main.ipy"), + r#" +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "ad6f36d9-4b7d-4562-8d00-f15a0f1fbb6d", + "metadata": {}, + "outputs": [], + "source": [ + "x=1" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} +"#, + )?; + + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .current_dir(tempdir.path()) + .arg("format") + .arg("--no-cache") + .args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()]) + .args(["--extension", "ipy:ipynb"]) + .arg("."), @r###" + success: true + exit_code: 0 + ----- stdout ----- + 1 file reformatted + + ----- stderr ----- + "###); + Ok(()) +} diff --git a/crates/ruff_cli/tests/lint.rs b/crates/ruff_cli/tests/lint.rs index 5dfbd56f83c55..7787c9a234ec6 100644 --- a/crates/ruff_cli/tests/lint.rs +++ b/crates/ruff_cli/tests/lint.rs @@ -436,3 +436,75 @@ ignore = ["D203", "D212"] "###); Ok(()) } + +#[test] +fn extension() -> Result<()> { + let tempdir = TempDir::new()?; + + let ruff_toml = tempdir.path().join("ruff.toml"); + fs::write( + &ruff_toml, + r#" +include = ["*.ipy"] +"#, + )?; + + fs::write( + tempdir.path().join("main.ipy"), + r#" +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "ad6f36d9-4b7d-4562-8d00-f15a0f1fbb6d", + "metadata": {}, + "outputs": [], + "source": [ + "import os" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} +"#, + )?; + + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .current_dir(tempdir.path()) + .arg("check") + .args(STDIN_BASE_OPTIONS) + .args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()]) + .args(["--extension", "ipy:ipynb"]) + .arg("."), @r###" + success: false + exit_code: 1 + ----- stdout ----- + main.ipy:cell 1:1:8: F401 [*] `os` imported but unused + Found 1 error. + [*] 1 fixable with the `--fix` option. + + ----- stderr ----- + "###); + Ok(()) +} diff --git a/crates/ruff_linter/src/settings/mod.rs b/crates/ruff_linter/src/settings/mod.rs index 66214a3a65201..a1d34de5b1b86 100644 --- a/crates/ruff_linter/src/settings/mod.rs +++ b/crates/ruff_linter/src/settings/mod.rs @@ -41,6 +41,7 @@ pub mod types; #[derive(Debug, CacheKey)] pub struct LinterSettings { pub exclude: FilePatternSet, + pub extension: ExtensionMapping, pub project_root: PathBuf, pub rules: RuleTable, @@ -50,7 +51,6 @@ pub struct LinterSettings { pub target_version: PythonVersion, pub preview: PreviewMode, pub explicit_preview_rules: bool, - pub extension: ExtensionMapping, // Rule-specific settings pub allowed_confusables: FxHashSet, diff --git a/crates/ruff_linter/src/settings/types.rs b/crates/ruff_linter/src/settings/types.rs index ed0e83e5c64f7..1ade2f5068c6d 100644 --- a/crates/ruff_linter/src/settings/types.rs +++ b/crates/ruff_linter/src/settings/types.rs @@ -388,9 +388,10 @@ pub struct ExtensionMapping { } impl ExtensionMapping { - /// Return the [`Language`] for the given extension. - pub fn get(&self, extension: &str) -> Option { - self.mapping.get(extension).copied() + /// Return the [`Language`] for the given file. + pub fn get(&self, path: &Path) -> Option { + let ext = path.extension()?.to_str()?; + self.mapping.get(ext).copied() } } diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 5325e9ba7260d..6430f8d481b02 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -117,6 +117,7 @@ pub struct Configuration { pub output_format: Option, pub preview: Option, pub required_version: Option, + pub extension: Option, pub show_fixes: Option, pub show_source: Option, @@ -174,6 +175,7 @@ impl Configuration { let formatter = FormatterSettings { exclude: FilePatternSet::try_from_iter(format.exclude.unwrap_or_default())?, + extension: self.extension.clone().unwrap_or_default(), preview: format_preview, target_version: match target_version { PythonVersion::Py37 => ruff_python_formatter::PythonVersion::Py37, @@ -241,7 +243,7 @@ impl Configuration { linter: LinterSettings { rules: lint.as_rule_table(lint_preview), exclude: FilePatternSet::try_from_iter(lint.exclude.unwrap_or_default())?, - extension: lint.extension.unwrap_or_default(), + extension: self.extension.unwrap_or_default(), preview: lint_preview, target_version, project_root: project_root.to_path_buf(), @@ -496,6 +498,9 @@ impl Configuration { .map(|src| resolve_src(&src, project_root)) .transpose()?, target_version: options.target_version, + // `--extension` is a hidden command-line argument that isn't supported in configuration + // files at present. + extension: None, lint: LintConfiguration::from_options(lint, project_root)?, format: FormatConfiguration::from_options( @@ -538,6 +543,7 @@ impl Configuration { src: self.src.or(config.src), target_version: self.target_version.or(config.target_version), preview: self.preview.or(config.preview), + extension: self.extension.or(config.extension), lint: self.lint.combine(config.lint), format: self.format.combine(config.format), @@ -549,7 +555,6 @@ impl Configuration { pub struct LintConfiguration { pub exclude: Option>, pub preview: Option, - pub extension: Option, // Rule selection pub extend_per_file_ignores: Vec, @@ -616,9 +621,6 @@ impl LintConfiguration { .chain(options.common.extend_unfixable.into_iter().flatten()) .collect(); Ok(LintConfiguration { - // `--extension` is a hidden command-line argument that isn't supported in configuration - // files at present. - extension: None, exclude: options.exclude.map(|paths| { paths .into_iter() @@ -954,7 +956,6 @@ impl LintConfiguration { Self { exclude: self.exclude.or(config.exclude), preview: self.preview.or(config.preview), - extension: self.extension.or(config.extension), rule_selections: config .rule_selections .into_iter() @@ -1031,6 +1032,7 @@ impl LintConfiguration { pub struct FormatConfiguration { pub exclude: Option>, pub preview: Option, + pub extension: Option, pub indent_style: Option, pub quote_style: Option, @@ -1044,6 +1046,9 @@ impl FormatConfiguration { #[allow(clippy::needless_pass_by_value)] pub fn from_options(options: FormatOptions, project_root: &Path) -> Result { Ok(Self { + // `--extension` is a hidden command-line argument that isn't supported in configuration + // files at present. + extension: None, exclude: options.exclude.map(|paths| { paths .into_iter() @@ -1077,18 +1082,19 @@ impl FormatConfiguration { #[must_use] #[allow(clippy::needless_pass_by_value)] - pub fn combine(self, other: Self) -> Self { + pub fn combine(self, config: Self) -> Self { Self { - exclude: self.exclude.or(other.exclude), - preview: self.preview.or(other.preview), - indent_style: self.indent_style.or(other.indent_style), - quote_style: self.quote_style.or(other.quote_style), - magic_trailing_comma: self.magic_trailing_comma.or(other.magic_trailing_comma), - line_ending: self.line_ending.or(other.line_ending), - docstring_code_format: self.docstring_code_format.or(other.docstring_code_format), + exclude: self.exclude.or(config.exclude), + preview: self.preview.or(config.preview), + extension: self.extension.or(config.extension), + indent_style: self.indent_style.or(config.indent_style), + quote_style: self.quote_style.or(config.quote_style), + magic_trailing_comma: self.magic_trailing_comma.or(config.magic_trailing_comma), + line_ending: self.line_ending.or(config.line_ending), + docstring_code_format: self.docstring_code_format.or(config.docstring_code_format), docstring_code_line_width: self .docstring_code_line_width - .or(other.docstring_code_line_width), + .or(config.docstring_code_line_width), } } } diff --git a/crates/ruff_workspace/src/settings.rs b/crates/ruff_workspace/src/settings.rs index 446cc95173bd0..3e2560285a5b0 100644 --- a/crates/ruff_workspace/src/settings.rs +++ b/crates/ruff_workspace/src/settings.rs @@ -1,7 +1,9 @@ use path_absolutize::path_dedot; use ruff_cache::cache_dir; use ruff_formatter::{FormatOptions, IndentStyle, IndentWidth, LineWidth}; -use ruff_linter::settings::types::{FilePattern, FilePatternSet, SerializationFormat, UnsafeFixes}; +use ruff_linter::settings::types::{ + ExtensionMapping, FilePattern, FilePatternSet, SerializationFormat, UnsafeFixes, +}; use ruff_linter::settings::LinterSettings; use ruff_macros::CacheKey; use ruff_python_ast::PySourceType; @@ -116,6 +118,7 @@ impl FileResolverSettings { #[derive(CacheKey, Clone, Debug)] pub struct FormatterSettings { pub exclude: FilePatternSet, + pub extension: ExtensionMapping, pub preview: PreviewMode, pub target_version: ruff_python_formatter::PythonVersion, @@ -177,6 +180,7 @@ impl Default for FormatterSettings { Self { exclude: FilePatternSet::default(), + extension: ExtensionMapping::default(), target_version: default_options.target_version(), preview: PreviewMode::Disabled, line_width: default_options.line_width(), diff --git a/docs/configuration.md b/docs/configuration.md index ced72b235fe3f..39c12e331b3a6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -528,6 +528,8 @@ Options: Enable preview mode; checks will include unstable rules and fixes. Use `--no-preview` to disable --config Path to the `pyproject.toml` or `ruff.toml` file to use for configuration + --extension + List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]). For example, to treat `.ipy` files as IPython notebooks, use `--extension ipy:ipynb` --statistics Show counts for every rule with at least one violation --add-noqa @@ -604,6 +606,8 @@ Options: Avoid writing any formatted files back; instead, exit with a non-zero status code and the difference between the current file and how the formatted file would look like --config Path to the `pyproject.toml` or `ruff.toml` file to use for configuration + --extension + List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]). For example, to treat `.ipy` files as IPython notebooks, use `--extension ipy:ipynb` --target-version The minimum Python version that should be supported [possible values: py37, py38, py39, py310, py311, py312] --preview From fee64b52baf42cf2de0e6cfccb122b84d4f17fed Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 12 Jan 2024 14:12:54 -0500 Subject: [PATCH 10/10] Limit inplace diagnostics to methods that accept inplace (#9495) ## Summary This should reduce false positives like https://github.com/astral-sh/ruff/issues/9491, by ignoring methods that are clearly not on a DataFrame. Closes https://github.com/astral-sh/ruff/issues/9491. --- .../test/fixtures/pandas_vet/PD002.py | 3 ++ .../pandas_vet/rules/inplace_argument.rs | 41 +++++++++++++++++++ ...es__pandas_vet__tests__PD002_PD002.py.snap | 5 +++ 3 files changed, 49 insertions(+) diff --git a/crates/ruff_linter/resources/test/fixtures/pandas_vet/PD002.py b/crates/ruff_linter/resources/test/fixtures/pandas_vet/PD002.py index 70424437c36df..6ed910cd14efd 100644 --- a/crates/ruff_linter/resources/test/fixtures/pandas_vet/PD002.py +++ b/crates/ruff_linter/resources/test/fixtures/pandas_vet/PD002.py @@ -31,3 +31,6 @@ torch.m.ReLU(inplace=True) # safe because this isn't a pandas call (x.drop(["a"], axis=1, inplace=True)) + +# This method doesn't take exist in Pandas, so ignore it. +x.rotate_z(45, inplace=True) diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs index f4924b0a6653d..656cb3e92d836 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs @@ -61,6 +61,15 @@ pub(crate) fn inplace_argument(checker: &mut Checker, call: &ast::ExprCall) { return; } + // If the function doesn't take an `inplace` argument, abort. + if !call + .func + .as_attribute_expr() + .is_some_and(|func| accepts_inplace_argument(&func.attr)) + { + return; + } + let mut seen_star = false; for keyword in call.arguments.keywords.iter().rev() { let Some(arg) = &keyword.arg else { @@ -134,3 +143,35 @@ fn convert_inplace_argument_to_assignment( Some(Fix::unsafe_edits(insert_assignment, [remove_argument])) } + +/// Returns `true` if the given method accepts an `inplace` argument when used on a Pandas +/// `DataFrame`, `Series`, or `Index`. +/// +/// See: +fn accepts_inplace_argument(method: &str) -> bool { + matches!( + method, + "where" + | "mask" + | "query" + | "clip" + | "eval" + | "backfill" + | "bfill" + | "ffill" + | "fillna" + | "interpolate" + | "dropna" + | "pad" + | "replace" + | "drop" + | "drop_duplicates" + | "rename" + | "rename_axis" + | "reset_index" + | "set_index" + | "sort_values" + | "sort_index" + | "set_names" + ) +} diff --git a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD002_PD002.py.snap b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD002_PD002.py.snap index e1c17dabed9dd..4b95a1c29a686 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD002_PD002.py.snap +++ b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD002_PD002.py.snap @@ -164,6 +164,8 @@ PD002.py:33:24: PD002 [*] `inplace=True` should be avoided; it has inconsistent 32 | 33 | (x.drop(["a"], axis=1, inplace=True)) | ^^^^^^^^^^^^ PD002 +34 | +35 | # This method doesn't take exist in Pandas, so ignore it. | = help: Assign to variable; remove `inplace` arg @@ -173,5 +175,8 @@ PD002.py:33:24: PD002 [*] `inplace=True` should be avoided; it has inconsistent 32 32 | 33 |-(x.drop(["a"], axis=1, inplace=True)) 33 |+x = (x.drop(["a"], axis=1)) +34 34 | +35 35 | # This method doesn't take exist in Pandas, so ignore it. +36 36 | x.rotate_z(45, inplace=True)