From c6a442c7f338057d59300e148e1e38af495730d8 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 19 Feb 2023 16:21:25 -0500 Subject: [PATCH] Add TODOs --- Cargo.lock | 4 + Cargo.toml | 4 +- .../test/fixtures/flake8_bugbear/B012.py | 22 ++++ .../test/fixtures/flake8_bugbear/B904.py | 8 ++ .../test/fixtures/flake8_return/RET503.py | 9 ++ .../test/fixtures/pycodestyle/E70.py | 3 + .../test/fixtures/pyflakes/F401_0.py | 7 ++ .../test/fixtures/pyflakes/F811_20.py | 6 +- crates/ruff/src/ast/comparable.rs | 117 +++++++++++++++++- crates/ruff/src/ast/helpers.rs | 59 ++++++++- crates/ruff/src/ast/operations.rs | 5 +- crates/ruff/src/ast/visitor.rs | 1 - crates/ruff/src/autofix/helpers.rs | 13 ++ .../rules/jump_statement_in_finally.rs | 5 + .../rules/raise_without_from_inside_except.rs | 6 + ...__flake8_bugbear__tests__B012_B012.py.snap | 13 +- ...__flake8_bugbear__tests__B904_B904.py.snap | 10 ++ .../rules/flake8_pytest_style/rules/raises.rs | 1 + crates/ruff/src/rules/flake8_return/rules.rs | 7 ++ ...lake8_return__tests__RET503_RET503.py.snap | 19 +++ crates/ruff/src/rules/isort/track.rs | 3 +- crates/ruff/src/rules/mccabe/rules.rs | 6 + .../pycodestyle/rules/compound_statements.rs | 18 ++- ...ules__pycodestyle__tests__E701_E70.py.snap | 10 ++ ...ules__pyflakes__tests__F401_F401_0.py.snap | 44 ++++++- .../rules/pylint/rules/too_many_branches.rs | 7 +- .../rules/pylint/rules/too_many_statements.rs | 7 +- crates/ruff/src/source_code/generator.rs | 1 + 28 files changed, 396 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e10d748ad22b70..73d3df848d41b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2146,6 +2146,7 @@ dependencies = [ [[package]] name = "rustpython-ast" version = "0.2.0" +source = "git+https://github.com/charliermarsh/RustPython.git?rev=0893e73d16471cad83a0fbc40c8f97e40d723ddf#0893e73d16471cad83a0fbc40c8f97e40d723ddf" dependencies = [ "num-bigint", "rustpython-compiler-core", @@ -2154,6 +2155,7 @@ dependencies = [ [[package]] name = "rustpython-common" version = "0.2.0" +source = "git+https://github.com/charliermarsh/RustPython.git?rev=0893e73d16471cad83a0fbc40c8f97e40d723ddf#0893e73d16471cad83a0fbc40c8f97e40d723ddf" dependencies = [ "ascii", "bitflags", @@ -2178,6 +2180,7 @@ dependencies = [ [[package]] name = "rustpython-compiler-core" version = "0.2.0" +source = "git+https://github.com/charliermarsh/RustPython.git?rev=0893e73d16471cad83a0fbc40c8f97e40d723ddf#0893e73d16471cad83a0fbc40c8f97e40d723ddf" dependencies = [ "bincode", "bitflags", @@ -2194,6 +2197,7 @@ dependencies = [ [[package]] name = "rustpython-parser" version = "0.2.0" +source = "git+https://github.com/charliermarsh/RustPython.git?rev=0893e73d16471cad83a0fbc40c8f97e40d723ddf#0893e73d16471cad83a0fbc40c8f97e40d723ddf" dependencies = [ "ahash", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 32e35016bd0984..2a4becc599aa66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,8 @@ libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a87 once_cell = { version = "1.16.0" } regex = { version = "1.6.0" } rustc-hash = { version = "1.1.0" } -rustpython-common = { path = "../RustPython/common" } -rustpython-parser = { path = "../RustPython/compiler/parser" } +rustpython-common = { git = "https://github.com/charliermarsh/RustPython.git", rev = "0893e73d16471cad83a0fbc40c8f97e40d723ddf" } +rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "0893e73d16471cad83a0fbc40c8f97e40d723ddf" } schemars = { version = "0.8.11" } serde = { version = "1.0.147", features = ["derive"] } serde_json = { version = "1.0.87" } diff --git a/crates/ruff/resources/test/fixtures/flake8_bugbear/B012.py b/crates/ruff/resources/test/fixtures/flake8_bugbear/B012.py index c03ff4ed1e8384..78aec07d979369 100644 --- a/crates/ruff/resources/test/fixtures/flake8_bugbear/B012.py +++ b/crates/ruff/resources/test/fixtures/flake8_bugbear/B012.py @@ -105,3 +105,25 @@ def k(): pass finally: break # warning + + +while True: + try: + pass + finally: + match *0, 1, *2: + case 0,: + y = 0 + case 0, *x: + break # warning + + +while True: + try: + pass + finally: + match *0, 1, *2: + case 0,: + y = 0 + case 0, *x: + pass # no warning diff --git a/crates/ruff/resources/test/fixtures/flake8_bugbear/B904.py b/crates/ruff/resources/test/fixtures/flake8_bugbear/B904.py index f4615806e58587..c35d6e5fc41b1e 100644 --- a/crates/ruff/resources/test/fixtures/flake8_bugbear/B904.py +++ b/crates/ruff/resources/test/fixtures/flake8_bugbear/B904.py @@ -62,3 +62,11 @@ def context_switch(): raise RuntimeError("boom!") else: raise RuntimeError("bang!") + + +try: + ... +except Exception as e: + match 0: + case 0: + raise RuntimeError("boom!") diff --git a/crates/ruff/resources/test/fixtures/flake8_return/RET503.py b/crates/ruff/resources/test/fixtures/flake8_return/RET503.py index 0f01cf486d1e7a..b07e1935ccf01e 100644 --- a/crates/ruff/resources/test/fixtures/flake8_return/RET503.py +++ b/crates/ruff/resources/test/fixtures/flake8_return/RET503.py @@ -259,3 +259,12 @@ def nested(values): for value in values: print(value) + + +# match +def x(y): + match y: + case 0: + return 1 + case 1: + print() # error diff --git a/crates/ruff/resources/test/fixtures/pycodestyle/E70.py b/crates/ruff/resources/test/fixtures/pycodestyle/E70.py index 1ec5d27d2be0ec..7b3d9afb586aa3 100644 --- a/crates/ruff/resources/test/fixtures/pycodestyle/E70.py +++ b/crates/ruff/resources/test/fixtures/pycodestyle/E70.py @@ -54,3 +54,6 @@ def f(): ... class C: ...; x = 1 #: E701:1:8 E702:1:13 class C: ...; ... +#: E701:2:12 +match *0, 1, *2: + case 0,: y = 0 diff --git a/crates/ruff/resources/test/fixtures/pyflakes/F401_0.py b/crates/ruff/resources/test/fixtures/pyflakes/F401_0.py index 8694df15b9a7ac..79c56bc09910af 100644 --- a/crates/ruff/resources/test/fixtures/pyflakes/F401_0.py +++ b/crates/ruff/resources/test/fixtures/pyflakes/F401_0.py @@ -85,3 +85,10 @@ def b(self) -> None: CustomInt: TypeAlias = "np.int8 | np.int16" + + +# Test: match statements. +match *0, 1, *2: + case 0,: + import x + import y diff --git a/crates/ruff/resources/test/fixtures/pyflakes/F811_20.py b/crates/ruff/resources/test/fixtures/pyflakes/F811_20.py index ee2b7a133c5e31..f5194b3ce9fac4 100644 --- a/crates/ruff/resources/test/fixtures/pyflakes/F811_20.py +++ b/crates/ruff/resources/test/fixtures/pyflakes/F811_20.py @@ -1,7 +1,7 @@ """ - Test that shadowing a global with a class attribute does not produce a - warning. - """ +Test that shadowing a global with a class attribute does not produce a +warning. +""" import fu diff --git a/crates/ruff/src/ast/comparable.rs b/crates/ruff/src/ast/comparable.rs index a6b15bc6ef8b28..2303513e458f76 100644 --- a/crates/ruff/src/ast/comparable.rs +++ b/crates/ruff/src/ast/comparable.rs @@ -4,8 +4,8 @@ use num_bigint::BigInt; use rustpython_parser::ast::{ Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, Excepthandler, - ExcepthandlerKind, Expr, ExprContext, ExprKind, Keyword, Operator, Stmt, StmtKind, Unaryop, - Withitem, + ExcepthandlerKind, Expr, ExprContext, ExprKind, Keyword, MatchCase, Operator, Pattern, + PatternKind, Stmt, StmtKind, Unaryop, Withitem, }; #[derive(Debug, PartialEq, Eq, Hash)] @@ -157,6 +157,110 @@ impl<'a> From<&'a Withitem> for ComparableWithitem<'a> { } } +#[allow(clippy::enum_variant_names)] +#[derive(Debug, PartialEq, Eq, Hash)] +pub enum ComparablePattern<'a> { + MatchValue { + value: ComparableExpr<'a>, + }, + MatchSingleton { + value: ComparableConstant<'a>, + }, + MatchSequence { + patterns: Vec>, + }, + MatchMapping { + keys: Vec>, + patterns: Vec>, + rest: Option<&'a str>, + }, + MatchClass { + cls: ComparableExpr<'a>, + patterns: Vec>, + kwd_attrs: Vec<&'a str>, + kwd_patterns: Vec>, + }, + MatchStar { + name: Option<&'a str>, + }, + MatchAs { + pattern: Option>>, + name: Option<&'a str>, + }, + MatchOr { + patterns: Vec>, + }, +} + +impl<'a> From<&'a Pattern> for ComparablePattern<'a> { + fn from(pattern: &'a Pattern) -> Self { + match &pattern.node { + PatternKind::MatchValue { value } => Self::MatchValue { + value: value.into(), + }, + PatternKind::MatchSingleton { value } => Self::MatchSingleton { + value: value.into(), + }, + PatternKind::MatchSequence { patterns } => Self::MatchSequence { + patterns: patterns.iter().map(Into::into).collect(), + }, + PatternKind::MatchMapping { + keys, + patterns, + rest, + } => Self::MatchMapping { + keys: keys.iter().map(Into::into).collect(), + patterns: patterns.iter().map(Into::into).collect(), + rest: rest.as_deref(), + }, + PatternKind::MatchClass { + cls, + patterns, + kwd_attrs, + kwd_patterns, + } => Self::MatchClass { + cls: cls.into(), + patterns: patterns.iter().map(Into::into).collect(), + kwd_attrs: kwd_attrs.iter().map(String::as_str).collect(), + kwd_patterns: kwd_patterns.iter().map(Into::into).collect(), + }, + PatternKind::MatchStar { name } => Self::MatchStar { + name: name.as_deref(), + }, + PatternKind::MatchAs { pattern, name } => Self::MatchAs { + pattern: pattern.as_ref().map(Into::into), + name: name.as_deref(), + }, + PatternKind::MatchOr { patterns } => Self::MatchOr { + patterns: patterns.iter().map(Into::into).collect(), + }, + } + } +} + +impl<'a> From<&'a Box> for Box> { + fn from(pattern: &'a Box) -> Self { + Box::new((&**pattern).into()) + } +} + +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct ComparableMatchCase<'a> { + pub pattern: ComparablePattern<'a>, + pub guard: Option>, + pub body: Vec>, +} + +impl<'a> From<&'a MatchCase> for ComparableMatchCase<'a> { + fn from(match_case: &'a MatchCase) -> Self { + Self { + pattern: (&match_case.pattern).into(), + guard: match_case.guard.as_ref().map(Into::into), + body: match_case.body.iter().map(Into::into).collect(), + } + } +} + #[derive(Debug, PartialEq, Eq, Hash)] pub enum ComparableConstant<'a> { None, @@ -644,6 +748,10 @@ pub enum ComparableStmt<'a> { body: Vec>, type_comment: Option<&'a str>, }, + Match { + subject: ComparableExpr<'a>, + cases: Vec>, + }, Raise { exc: Option>, cause: Option>, @@ -811,7 +919,10 @@ impl<'a> From<&'a Stmt> for ComparableStmt<'a> { body: body.iter().map(Into::into).collect(), type_comment: type_comment.as_ref().map(String::as_str), }, - StmtKind::Match { .. } => unreachable!("StmtKind::Match is not supported"), + StmtKind::Match { subject, cases } => Self::Match { + subject: subject.into(), + cases: cases.iter().map(Into::into).collect(), + }, StmtKind::Raise { exc, cause } => Self::Raise { exc: exc.as_ref().map(Into::into), cause: cause.as_ref().map(Into::into), diff --git a/crates/ruff/src/ast/helpers.rs b/crates/ruff/src/ast/helpers.rs index e55728b691becf..6e218d750d8dc9 100644 --- a/crates/ruff/src/ast/helpers.rs +++ b/crates/ruff/src/ast/helpers.rs @@ -7,7 +7,7 @@ use regex::Regex; use rustc_hash::{FxHashMap, FxHashSet}; use rustpython_parser::ast::{ Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword, KeywordData, - Located, Location, Stmt, StmtKind, + Located, Location, MatchCase, Pattern, PatternKind, Stmt, StmtKind, }; use rustpython_parser::lexer; use rustpython_parser::lexer::Tok; @@ -249,6 +249,46 @@ where } } +pub fn any_over_pattern(pattern: &Pattern, func: &F) -> bool +where + F: Fn(&Expr) -> bool, +{ + match &pattern.node { + PatternKind::MatchValue { value } => any_over_expr(value, func), + PatternKind::MatchSingleton { .. } => false, + PatternKind::MatchSequence { patterns } => patterns + .iter() + .any(|pattern| any_over_pattern(pattern, func)), + PatternKind::MatchMapping { keys, patterns, .. } => { + keys.iter().any(|key| any_over_expr(key, func)) + || patterns + .iter() + .any(|pattern| any_over_pattern(pattern, func)) + } + PatternKind::MatchClass { + cls, + patterns, + kwd_patterns, + .. + } => { + any_over_expr(cls, func) + || patterns + .iter() + .any(|pattern| any_over_pattern(pattern, func)) + || kwd_patterns + .iter() + .any(|pattern| any_over_pattern(pattern, func)) + } + PatternKind::MatchStar { .. } => false, + PatternKind::MatchAs { pattern, .. } => pattern + .as_ref() + .map_or(false, |pattern| any_over_pattern(pattern, func)), + PatternKind::MatchOr { patterns } => patterns + .iter() + .any(|pattern| any_over_pattern(pattern, func)), + } +} + pub fn any_over_stmt(stmt: &Stmt, func: &F) -> bool where F: Fn(&Expr) -> bool, @@ -409,8 +449,21 @@ where .as_ref() .map_or(false, |value| any_over_expr(value, func)) } - // TODO(charlie): Handle match statements. - StmtKind::Match { .. } => false, + StmtKind::Match { subject, cases } => { + any_over_expr(subject, func) + || cases.iter().any(|case| { + let MatchCase { + pattern, + guard, + body, + } = case; + any_over_pattern(pattern, func) + || guard + .as_ref() + .map_or(false, |expr| any_over_expr(expr, func)) + || any_over_body(body, func) + }) + } StmtKind::Import { .. } => false, StmtKind::ImportFrom { .. } => false, StmtKind::Global { .. } => false, diff --git a/crates/ruff/src/ast/operations.rs b/crates/ruff/src/ast/operations.rs index 213c026470ffbe..434b47bb775fd8 100644 --- a/crates/ruff/src/ast/operations.rs +++ b/crates/ruff/src/ast/operations.rs @@ -181,7 +181,10 @@ pub fn extract_globals(body: &[Stmt]) -> FxHashMap<&str, &Stmt> { /// Check if a node is parent of a conditional branch. pub fn on_conditional_branch<'a>(parents: &mut impl Iterator) -> bool { parents.any(|parent| { - if matches!(parent.node, StmtKind::If { .. } | StmtKind::While { .. }) { + if matches!( + parent.node, + StmtKind::If { .. } | StmtKind::While { .. } | StmtKind::Match { .. } + ) { return true; } if let StmtKind::Expr { value } = &parent.node { diff --git a/crates/ruff/src/ast/visitor.rs b/crates/ruff/src/ast/visitor.rs index e491f153062759..a60331971a6b23 100644 --- a/crates/ruff/src/ast/visitor.rs +++ b/crates/ruff/src/ast/visitor.rs @@ -205,7 +205,6 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) { visitor.visit_body(body); } StmtKind::Match { subject, cases } => { - // TODO(charlie): Handle `cases`. visitor.visit_expr(subject); for match_case in cases { visitor.visit_match_case(match_case); diff --git a/crates/ruff/src/autofix/helpers.rs b/crates/ruff/src/autofix/helpers.rs index b1cbf04ef73d14..cd38aa19dcb444 100644 --- a/crates/ruff/src/autofix/helpers.rs +++ b/crates/ruff/src/autofix/helpers.rs @@ -74,6 +74,19 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result bail!("Unable to find child in parent body") } } + StmtKind::Match { cases, .. } => { + if let Some(body) = cases.iter().find_map(|case| { + if case.body.iter().contains(child) { + Some(&case.body) + } else { + None + } + }) { + Ok(has_single_child(body, deleted)) + } else { + bail!("Unable to find child in parent body") + } + } _ => bail!("Unable to find child in parent body"), } } diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/jump_statement_in_finally.rs b/crates/ruff/src/rules/flake8_bugbear/rules/jump_statement_in_finally.rs index fb2526fe9e94fe..22ceb58717992f 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/jump_statement_in_finally.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/jump_statement_in_finally.rs @@ -50,6 +50,11 @@ fn walk_stmt(checker: &mut Checker, body: &[Stmt], f: fn(&Stmt) -> bool) { | StmtKind::AsyncWith { body, .. } => { walk_stmt(checker, body, f); } + StmtKind::Match { cases, .. } => { + for case in cases { + walk_stmt(checker, &case.body, f); + } + } _ => {} } } diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/raise_without_from_inside_except.rs b/crates/ruff/src/rules/flake8_bugbear/rules/raise_without_from_inside_except.rs index 67cd5cee16304e..699d57ab27ac06 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/raise_without_from_inside_except.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/raise_without_from_inside_except.rs @@ -56,11 +56,17 @@ impl<'a> Visitor<'a> for RaiseVisitor { | StmtKind::AsyncFor { body, .. } => { visitor::walk_body(self, body); } + StmtKind::Match { cases, .. } => { + for case in cases { + visitor::walk_body(self, &case.body); + } + } _ => {} } } } +/// B904 pub fn raise_without_from_inside_except(checker: &mut Checker, body: &[Stmt]) { let mut visitor = RaiseVisitor { diagnostics: vec![], diff --git a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B012_B012.py.snap b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B012_B012.py.snap index 1eaa81d2a88ace..9b87a721c900e3 100644 --- a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B012_B012.py.snap +++ b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B012_B012.py.snap @@ -1,5 +1,5 @@ --- -source: src/rules/flake8_bugbear/mod.rs +source: crates/ruff/src/rules/flake8_bugbear/mod.rs expression: diagnostics --- - kind: @@ -112,4 +112,15 @@ expression: diagnostics column: 13 fix: ~ parent: ~ +- kind: + JumpStatementInFinally: + name: break + location: + row: 118 + column: 16 + end_location: + row: 118 + column: 21 + fix: ~ + parent: ~ diff --git a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B904_B904.py.snap b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B904_B904.py.snap index db1715b2a15f5c..e8a026c4229a21 100644 --- a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B904_B904.py.snap +++ b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B904_B904.py.snap @@ -52,4 +52,14 @@ expression: diagnostics column: 35 fix: ~ parent: ~ +- kind: + RaiseWithoutFromInsideExcept: ~ + location: + row: 72 + column: 12 + end_location: + row: 72 + column: 39 + fix: ~ + parent: ~ diff --git a/crates/ruff/src/rules/flake8_pytest_style/rules/raises.rs b/crates/ruff/src/rules/flake8_pytest_style/rules/raises.rs index 01fd3c08c23586..f0cc70a8cc36f7 100644 --- a/crates/ruff/src/rules/flake8_pytest_style/rules/raises.rs +++ b/crates/ruff/src/rules/flake8_pytest_style/rules/raises.rs @@ -114,6 +114,7 @@ pub fn complex_raises(checker: &mut Checker, stmt: &Stmt, items: &[Withitem], bo } StmtKind::If { .. } | StmtKind::For { .. } + | StmtKind::Match { .. } | StmtKind::AsyncFor { .. } | StmtKind::While { .. } | StmtKind::Try { .. } => { diff --git a/crates/ruff/src/rules/flake8_return/rules.rs b/crates/ruff/src/rules/flake8_return/rules.rs index 3137977db423af..058a21bea2193f 100644 --- a/crates/ruff/src/rules/flake8_return/rules.rs +++ b/crates/ruff/src/rules/flake8_return/rules.rs @@ -239,6 +239,13 @@ fn implicit_return(checker: &mut Checker, stmt: &Stmt) { checker.diagnostics.push(diagnostic); } } + StmtKind::Match { cases, .. } => { + for case in cases { + if let Some(last_stmt) = case.body.last() { + implicit_return(checker, last_stmt); + } + } + } StmtKind::With { body, .. } | StmtKind::AsyncWith { body, .. } => { if let Some(last_stmt) = body.last() { implicit_return(checker, last_stmt); diff --git a/crates/ruff/src/rules/flake8_return/snapshots/ruff__rules__flake8_return__tests__RET503_RET503.py.snap b/crates/ruff/src/rules/flake8_return/snapshots/ruff__rules__flake8_return__tests__RET503_RET503.py.snap index 62f5aba3bf193c..e274500fa77cfc 100644 --- a/crates/ruff/src/rules/flake8_return/snapshots/ruff__rules__flake8_return__tests__RET503_RET503.py.snap +++ b/crates/ruff/src/rules/flake8_return/snapshots/ruff__rules__flake8_return__tests__RET503_RET503.py.snap @@ -249,4 +249,23 @@ expression: diagnostics row: 261 column: 20 parent: ~ +- kind: + ImplicitReturn: ~ + location: + row: 270 + column: 12 + end_location: + row: 270 + column: 19 + fix: + content: + - "" + - " return None" + location: + row: 270 + column: 19 + end_location: + row: 270 + column: 19 + parent: ~ diff --git a/crates/ruff/src/rules/isort/track.rs b/crates/ruff/src/rules/isort/track.rs index befdd3c92bac3d..634e444b53183a 100644 --- a/crates/ruff/src/rules/isort/track.rs +++ b/crates/ruff/src/rules/isort/track.rs @@ -216,7 +216,8 @@ where } self.finalize(None); } - StmtKind::Match { cases, .. } => { + StmtKind::Match { subject, cases } => { + self.visit_expr(subject); for match_case in cases { self.visit_match_case(match_case); } diff --git a/crates/ruff/src/rules/mccabe/rules.rs b/crates/ruff/src/rules/mccabe/rules.rs index c5b0022c97e2ea..79f031179a3f88 100644 --- a/crates/ruff/src/rules/mccabe/rules.rs +++ b/crates/ruff/src/rules/mccabe/rules.rs @@ -82,6 +82,12 @@ fn get_complexity_number(stmts: &[Stmt]) -> usize { complexity += 1; } } + StmtKind::Match { cases, .. } => { + complexity += 1; + for case in cases { + complexity += get_complexity_number(&case.body); + } + } StmtKind::Try { body, handlers, diff --git a/crates/ruff/src/rules/pycodestyle/rules/compound_statements.rs b/crates/ruff/src/rules/pycodestyle/rules/compound_statements.rs index 29d07bf9799b32..0ef79114e61352 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/compound_statements.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/compound_statements.rs @@ -52,6 +52,7 @@ pub fn compound_statements( // Track the last seen instance of a variety of tokens. let mut colon = None; let mut semi = None; + let mut case = None; let mut class = None; let mut elif = None; let mut else_ = None; @@ -59,6 +60,7 @@ pub fn compound_statements( let mut finally = None; let mut for_ = None; let mut if_ = None; + let mut match_ = None; let mut try_ = None; let mut while_ = None; let mut with = None; @@ -114,6 +116,7 @@ pub fn compound_statements( // Reset. colon = None; semi = None; + case = None; class = None; elif = None; else_ = None; @@ -121,18 +124,21 @@ pub fn compound_statements( finally = None; for_ = None; if_ = None; + match_ = None; try_ = None; while_ = None; with = None; } Tok::Colon => { - if class.is_some() + if case.is_some() + || class.is_some() || elif.is_some() || else_.is_some() || except.is_some() || finally.is_some() || for_.is_some() || if_.is_some() + || match_.is_some() || try_.is_some() || while_.is_some() || with.is_some() @@ -168,6 +174,7 @@ pub fn compound_statements( // Reset. colon = None; + case = None; class = None; elif = None; else_ = None; @@ -175,6 +182,7 @@ pub fn compound_statements( finally = None; for_ = None; if_ = None; + match_ = None; try_ = None; while_ = None; with = None; @@ -186,6 +194,7 @@ pub fn compound_statements( Tok::Lambda => { // Reset. colon = None; + case = None; class = None; elif = None; else_ = None; @@ -193,10 +202,14 @@ pub fn compound_statements( finally = None; for_ = None; if_ = None; + match_ = None; try_ = None; while_ = None; with = None; } + Tok::Case => { + case = Some((start, end)); + } Tok::If => { if_ = Some((start, end)); } @@ -227,6 +240,9 @@ pub fn compound_statements( Tok::With => { with = Some((start, end)); } + Tok::Match => { + match_ = Some((start, end)); + } _ => {} }; } diff --git a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E701_E70.py.snap b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E701_E70.py.snap index be7e0261bed625..5d25ebec2e5897 100644 --- a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E701_E70.py.snap +++ b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E701_E70.py.snap @@ -132,4 +132,14 @@ expression: diagnostics column: 8 fix: ~ parent: ~ +- kind: + MultipleStatementsOnOneLineColon: ~ + location: + row: 59 + column: 11 + end_location: + row: 59 + column: 12 + fix: ~ + parent: ~ diff --git a/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_0.py.snap b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_0.py.snap index d42a9e481b4dc8..9e9329a4483997 100644 --- a/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_0.py.snap +++ b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_0.py.snap @@ -1,5 +1,5 @@ --- -source: src/rules/pyflakes/mod.rs +source: crates/ruff/src/rules/pyflakes/mod.rs expression: diagnostics --- - kind: @@ -154,4 +154,46 @@ expression: diagnostics row: 52 column: 21 parent: ~ +- kind: + UnusedImport: + name: x + ignore_init: false + multiple: false + location: + row: 93 + column: 15 + end_location: + row: 93 + column: 16 + fix: + content: + - "" + location: + row: 93 + column: 0 + end_location: + row: 94 + column: 0 + parent: ~ +- kind: + UnusedImport: + name: y + ignore_init: false + multiple: false + location: + row: 94 + column: 15 + end_location: + row: 94 + column: 16 + fix: + content: + - pass + location: + row: 94 + column: 8 + end_location: + row: 94 + column: 16 + parent: ~ diff --git a/crates/ruff/src/rules/pylint/rules/too_many_branches.rs b/crates/ruff/src/rules/pylint/rules/too_many_branches.rs index 81bb7ddd78211f..acf099be0d5cb2 100644 --- a/crates/ruff/src/rules/pylint/rules/too_many_branches.rs +++ b/crates/ruff/src/rules/pylint/rules/too_many_branches.rs @@ -28,7 +28,6 @@ fn num_branches(stmts: &[Stmt]) -> usize { stmts .iter() .map(|stmt| { - // TODO(charlie): Account for pattern match statement. match &stmt.node { StmtKind::If { body, orelse, .. } => { 1 + num_branches(body) @@ -41,6 +40,12 @@ fn num_branches(stmts: &[Stmt]) -> usize { }) + num_branches(orelse) } + StmtKind::Match { cases, .. } => { + 1 + cases + .iter() + .map(|case| num_branches(&case.body)) + .sum::() + } StmtKind::For { body, orelse, .. } | StmtKind::AsyncFor { body, orelse, .. } | StmtKind::While { body, orelse, .. } => { diff --git a/crates/ruff/src/rules/pylint/rules/too_many_statements.rs b/crates/ruff/src/rules/pylint/rules/too_many_statements.rs index 07549c973f40fd..b65137b6b4eefe 100644 --- a/crates/ruff/src/rules/pylint/rules/too_many_statements.rs +++ b/crates/ruff/src/rules/pylint/rules/too_many_statements.rs @@ -26,7 +26,6 @@ impl Violation for TooManyStatements { fn num_statements(stmts: &[Stmt]) -> usize { let mut count = 0; for stmt in stmts { - // TODO(charlie): Account for pattern match statement. match &stmt.node { StmtKind::If { body, orelse, .. } => { count += 1; @@ -49,6 +48,12 @@ fn num_statements(stmts: &[Stmt]) -> usize { count += num_statements(body); count += num_statements(orelse); } + StmtKind::Match { cases, .. } => { + count += 1; + for case in cases { + count += num_statements(&case.body); + } + } StmtKind::Try { body, handlers, diff --git a/crates/ruff/src/source_code/generator.rs b/crates/ruff/src/source_code/generator.rs index e25b28a44fcdc8..12c7a10ba37b71 100644 --- a/crates/ruff/src/source_code/generator.rs +++ b/crates/ruff/src/source_code/generator.rs @@ -456,6 +456,7 @@ impl<'a> Generator<'a> { }); self.body(body); } + // STOPSHIP(charlie): Support match statements. StmtKind::Match { .. } => {} StmtKind::Raise { exc, cause } => { statement!({