From 00f82b11a91d5b296d0481b51f55fb1d81719bb1 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 12 Sep 2022 21:34:12 -0400 Subject: [PATCH] Implement F722 --- README.md | 7 ++++--- examples/generate_rules_table.rs | 1 + resources/test/fixtures/F722.py | 10 ++++++++++ resources/test/fixtures/pyproject.toml | 1 + src/check_ast.rs | 5 +++++ src/checks.rs | 8 ++++++++ src/linter.rs | 25 +++++++++++++++++++++++++ src/pyproject.rs | 1 + src/settings.rs | 1 + 9 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 resources/test/fixtures/F722.py diff --git a/README.md b/README.md index b2a013b53b..a871d01156 100644 --- a/README.md +++ b/README.md @@ -124,13 +124,13 @@ ruff's goal is to achieve feature-parity with Flake8 when used (1) without any p stylistic checks; limiting to Python 3 obviates the need for certain compatibility checks.) Under those conditions, Flake8 implements about 60 rules, give or take. At time of writing, ruff -implements 37 rules. (Note that these 37 rules likely cover a disproportionate share of errors: +implements 38 rules. (Note that these 38 rules likely cover a disproportionate share of errors: unused imports, undefined variables, etc.) -The 23 unimplemented rules are tracked in #170, and include: +The unimplemented rules are tracked in #170, and include: - 14 rules related to string `.format` calls. -- 3 rules related to parsing and syntax errors. +- 1 rule related to parsing and syntax. - 6 logical rules. Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8: @@ -172,6 +172,7 @@ Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis F | F704 | YieldOutsideFunction | a `yield` or `yield from` statement outside of a function/method | | F706 | ReturnOutsideFunction | a `return` statement outside of a function/method | | F707 | DefaultExceptNotLast | an `except:` block as not the last exception handler | +| F722 | ForwardAnnotationSyntaxError | syntax error in forward annotation '...' | | F821 | UndefinedName | Undefined name `...` | | F822 | UndefinedExport | Undefined name `...` in `__all__` | | F823 | UndefinedLocal | Local variable `...` referenced before assignment | diff --git a/examples/generate_rules_table.rs b/examples/generate_rules_table.rs index d0dde1bc78..594210795f 100644 --- a/examples/generate_rules_table.rs +++ b/examples/generate_rules_table.rs @@ -13,6 +13,7 @@ fn main() { CheckKind::DoNotAssignLambda, CheckKind::DoNotUseBareExcept, CheckKind::DuplicateArgumentName, + CheckKind::ForwardAnnotationSyntaxError("...".to_string()), CheckKind::FStringMissingPlaceholders, CheckKind::FutureFeatureNotDefined("...".to_string()), CheckKind::IOError("...".to_string()), diff --git a/resources/test/fixtures/F722.py b/resources/test/fixtures/F722.py new file mode 100644 index 0000000000..3ff1e983f3 --- /dev/null +++ b/resources/test/fixtures/F722.py @@ -0,0 +1,10 @@ +class A: + pass + + +def f() -> "A": + pass + + +def g() -> "///": + pass diff --git a/resources/test/fixtures/pyproject.toml b/resources/test/fixtures/pyproject.toml index fdc66f1de2..c863b26cfd 100644 --- a/resources/test/fixtures/pyproject.toml +++ b/resources/test/fixtures/pyproject.toml @@ -31,6 +31,7 @@ select = [ "F704", "F706", "F707", + "F722", "F821", "F822", "F823", diff --git a/src/check_ast.rs b/src/check_ast.rs index e32645eabf..38d541a29d 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -1181,6 +1181,11 @@ impl<'a> Checker<'a> { if let Ok(mut expr) = parser::parse_expression(expression, path) { relocate_expr(&mut expr, location); allocator.push(expr); + } else if self.settings.select.contains(&CheckCode::F722) { + self.checks.push(Check::new( + CheckKind::ForwardAnnotationSyntaxError(expression.to_string()), + location, + )); } } for expr in allocator { diff --git a/src/checks.rs b/src/checks.rs index 1f94177776..9dc7f51cde 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -37,6 +37,7 @@ pub enum CheckCode { F704, F706, F707, + F722, F821, F822, F823, @@ -126,6 +127,7 @@ impl CheckCode { CheckCode::F704 => "F704", CheckCode::F706 => "F706", CheckCode::F707 => "F707", + CheckCode::F722 => "F722", CheckCode::F821 => "F821", CheckCode::F822 => "F822", CheckCode::F823 => "F823", @@ -172,6 +174,7 @@ pub enum CheckKind { DoNotAssignLambda, DoNotUseBareExcept, DuplicateArgumentName, + ForwardAnnotationSyntaxError(String), FStringMissingPlaceholders, FutureFeatureNotDefined(String), IOError(String), @@ -215,6 +218,7 @@ impl CheckKind { CheckKind::DoNotAssignLambda => "DoNotAssignLambda", CheckKind::DoNotUseBareExcept => "DoNotUseBareExcept", CheckKind::DuplicateArgumentName => "DuplicateArgumentName", + CheckKind::ForwardAnnotationSyntaxError(_) => "ForwardAnnotationSyntaxError", CheckKind::FStringMissingPlaceholders => "FStringMissingPlaceholders", CheckKind::FutureFeatureNotDefined(_) => "FutureFeatureNotDefined", CheckKind::IOError(_) => "IOError", @@ -260,6 +264,7 @@ impl CheckKind { CheckKind::DoNotAssignLambda => &CheckCode::E731, CheckKind::DoNotUseBareExcept => &CheckCode::E722, CheckKind::DuplicateArgumentName => &CheckCode::F831, + CheckKind::ForwardAnnotationSyntaxError(_) => &CheckCode::F722, CheckKind::FStringMissingPlaceholders => &CheckCode::F541, CheckKind::FutureFeatureNotDefined(_) => &CheckCode::F407, CheckKind::IOError(_) => &CheckCode::E902, @@ -317,6 +322,9 @@ impl CheckKind { CheckKind::DuplicateArgumentName => { "Duplicate argument name in function definition".to_string() } + CheckKind::ForwardAnnotationSyntaxError(body) => { + format!("syntax error in forward annotation '{body}'") + } CheckKind::FStringMissingPlaceholders => { "f-string without any placeholders".to_string() } diff --git a/src/linter.rs b/src/linter.rs index 4bd5812324..d8e994ce21 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -1073,6 +1073,31 @@ mod tests { Ok(()) } + #[test] + fn f722() -> Result<()> { + let mut actual = check_path( + Path::new("./resources/test/fixtures/F722.py"), + &settings::Settings { + line_length: 88, + exclude: vec![], + select: BTreeSet::from([CheckCode::F722]), + }, + &fixer::Mode::Generate, + )?; + actual.sort_by_key(|check| check.location); + let expected = vec![Check { + kind: CheckKind::ForwardAnnotationSyntaxError("///".to_string()), + location: Location::new(9, 13), + fix: None, + }]; + assert_eq!(actual.len(), expected.len()); + for i in 0..actual.len() { + assert_eq!(actual[i], expected[i]); + } + + Ok(()) + } + #[test] fn f821() -> Result<()> { let mut actual = check_path( diff --git a/src/pyproject.rs b/src/pyproject.rs index fc74e16147..250ce6f939 100644 --- a/src/pyproject.rs +++ b/src/pyproject.rs @@ -288,6 +288,7 @@ other-attribute = 1 CheckCode::F704, CheckCode::F706, CheckCode::F707, + CheckCode::F722, CheckCode::F821, CheckCode::F822, CheckCode::F823, diff --git a/src/settings.rs b/src/settings.rs index 8da2026e87..3fc8eac682 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -72,6 +72,7 @@ impl Settings { CheckCode::F704, CheckCode::F706, CheckCode::F707, + CheckCode::F722, CheckCode::F821, CheckCode::F822, CheckCode::F823,