From df50951f28ff7087cea40c4fb81d0ad35310207f Mon Sep 17 00:00:00 2001 From: konstin Date: Thu, 2 Mar 2023 15:09:32 +0100 Subject: [PATCH] Add flake-pyi PYI033 "Do not use type comments in stubs" I think this is also a case where people like want this for .py files, too, at least on 3.7+ (https://docs.python.org/3/whatsnew/3.7.html#pep-563-postponed-evaluation-of-annotations) On that note, does none of the tools upgrade all type comments to type annotations automatically? I looked in pyupgrade and googled but couldn't find anything --- .cargo/config.toml | 4 ++ .../test/fixtures/flake8_pyi/PYI033.py | 7 +++ .../test/fixtures/flake8_pyi/PYI033.pyi | 7 +++ crates/ruff/src/checkers/tokens.rs | 11 +++- crates/ruff/src/codes.rs | 1 + crates/ruff/src/linter.rs | 10 +++- crates/ruff/src/registry.rs | 4 +- crates/ruff/src/rules/flake8_pyi/mod.rs | 2 + crates/ruff/src/rules/flake8_pyi/rules/mod.rs | 2 + .../flake8_pyi/rules/type_comment_in_stub.rs | 59 +++++++++++++++++++ ...__flake8_pyi__tests__PYI033_PYI033.py.snap | 6 ++ ..._flake8_pyi__tests__PYI033_PYI033.pyi.snap | 25 ++++++++ docs/configuration.md | 2 + ruff.schema.json | 2 + 14 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 crates/ruff/resources/test/fixtures/flake8_pyi/PYI033.py create mode 100644 crates/ruff/resources/test/fixtures/flake8_pyi/PYI033.pyi create mode 100644 crates/ruff/src/rules/flake8_pyi/rules/type_comment_in_stub.rs create mode 100644 crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI033_PYI033.py.snap create mode 100644 crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI033_PYI033.pyi.snap diff --git a/.cargo/config.toml b/.cargo/config.toml index 5fef0dd0b68c34..2cfc9a91b1199d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,10 @@ [alias] dev = "run --package ruff_dev --bin ruff_dev" +[target.x86_64-unknown-linux-gnu] +linker = "clang" +rustflags = ["-C", "link-arg=-fuse-ld=/home/konsti/.local/bin/mold"] + [target.'cfg(all())'] rustflags = [ # CLIPPY LINT SETTINGS diff --git a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI033.py b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI033.py new file mode 100644 index 00000000000000..9c835234a052ac --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI033.py @@ -0,0 +1,7 @@ +# OK +x = 1 # type: int + + +# OK +def reverse(x): # type:(str) -> str + ... diff --git a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI033.pyi b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI033.pyi new file mode 100644 index 00000000000000..bea289726e4c93 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI033.pyi @@ -0,0 +1,7 @@ +# ERROR: PYI033 Don't use type comments in stub file +x = 1 # type: int + + +# ERROR: PYI033 Don't use type comments in stub file +def reverse(x): # type:(str) -> str + ... diff --git a/crates/ruff/src/checkers/tokens.rs b/crates/ruff/src/checkers/tokens.rs index 4572b9c765e301..a8f8e0c2d33a28 100644 --- a/crates/ruff/src/checkers/tokens.rs +++ b/crates/ruff/src/checkers/tokens.rs @@ -7,8 +7,8 @@ use crate::lex::docstring_detection::StateMachine; use crate::registry::{Diagnostic, Rule}; use crate::rules::ruff::rules::Context; use crate::rules::{ - eradicate, flake8_commas, flake8_implicit_str_concat, flake8_quotes, pycodestyle, pyupgrade, - ruff, + eradicate, flake8_commas, flake8_implicit_str_concat, flake8_pyi, flake8_quotes, pycodestyle, + pyupgrade, ruff, }; use crate::settings::{flags, Settings}; use crate::source_code::Locator; @@ -18,6 +18,7 @@ pub fn check_tokens( tokens: &[LexResult], settings: &Settings, autofix: flags::Autofix, + is_interface_definition: bool, ) -> Vec { let mut diagnostics: Vec = vec![]; @@ -55,6 +56,7 @@ pub fn check_tokens( .enabled(&Rule::TrailingCommaOnBareTupleProhibited) || settings.rules.enabled(&Rule::TrailingCommaProhibited); let enforce_extraneous_parenthesis = settings.rules.enabled(&Rule::ExtraneousParentheses); + let enforce_type_comment_in_stub = settings.rules.enabled(&Rule::TypeCommentInStub); // RUF001, RUF002, RUF003 if enforce_ambiguous_unicode_character { @@ -161,5 +163,10 @@ pub fn check_tokens( ); } + // PYI033 + if enforce_type_comment_in_stub && is_interface_definition { + diagnostics.extend(flake8_pyi::rules::type_comment_in_stub(tokens)); + } + diagnostics } diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 28f11f26054b79..dcef46b1d133d0 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -500,6 +500,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option { (Flake8Pyi, "011") => Rule::TypedArgumentSimpleDefaults, (Flake8Pyi, "014") => Rule::ArgumentSimpleDefaults, (Flake8Pyi, "021") => Rule::DocstringInStub, + (Flake8Pyi, "033") => Rule::TypeCommentInStub, // flake8-pytest-style (Flake8PytestStyle, "001") => Rule::IncorrectFixtureParenthesesStyle, diff --git a/crates/ruff/src/linter.rs b/crates/ruff/src/linter.rs index 799a53921b7b8c..07d2390e8fc985 100644 --- a/crates/ruff/src/linter.rs +++ b/crates/ruff/src/linter.rs @@ -21,6 +21,7 @@ use crate::doc_lines::{doc_lines_from_ast, doc_lines_from_tokens}; use crate::message::{Message, Source}; use crate::noqa::{add_noqa, rule_is_ignored}; use crate::registry::{Diagnostic, Rule}; +use crate::resolver::is_interface_definition_path; use crate::rules::pycodestyle; use crate::settings::{flags, Settings}; use crate::source_code::{Indexer, Locator, Stylist}; @@ -83,7 +84,14 @@ pub fn check_path( .iter_enabled() .any(|rule_code| rule_code.lint_source().is_tokens()) { - diagnostics.extend(check_tokens(locator, &tokens, settings, autofix)); + let is_interface_definition = is_interface_definition_path(path); + diagnostics.extend(check_tokens( + locator, + &tokens, + settings, + autofix, + is_interface_definition, + )); } // Run the filesystem-based rules. diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 49c4f84f7f3ed8..36f4815d4af864 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -474,6 +474,7 @@ ruff_macros::register_rules!( rules::flake8_pyi::rules::DocstringInStub, rules::flake8_pyi::rules::TypedArgumentSimpleDefaults, rules::flake8_pyi::rules::ArgumentSimpleDefaults, + rules::flake8_pyi::rules::TypeCommentInStub, // flake8-pytest-style rules::flake8_pytest_style::rules::IncorrectFixtureParenthesesStyle, rules::flake8_pytest_style::rules::FixturePositionalArgs, @@ -827,7 +828,8 @@ impl Rule { | Rule::MultipleStatementsOnOneLineColon | Rule::UselessSemicolon | Rule::MultipleStatementsOnOneLineSemicolon - | Rule::TrailingCommaProhibited => &LintSource::Tokens, + | Rule::TrailingCommaProhibited + | Rule::TypeCommentInStub => &LintSource::Tokens, Rule::IOError => &LintSource::Io, Rule::UnsortedImports | Rule::MissingRequiredImport => &LintSource::Imports, Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => &LintSource::Filesystem, diff --git a/crates/ruff/src/rules/flake8_pyi/mod.rs b/crates/ruff/src/rules/flake8_pyi/mod.rs index a699938131b4ef..2ee0ec97bae6bb 100644 --- a/crates/ruff/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff/src/rules/flake8_pyi/mod.rs @@ -31,6 +31,8 @@ mod tests { #[test_case(Rule::ArgumentSimpleDefaults, Path::new("PYI014.pyi"))] #[test_case(Rule::DocstringInStub, Path::new("PYI021.py"))] #[test_case(Rule::DocstringInStub, Path::new("PYI021.pyi"))] + #[test_case(Rule::TypeCommentInStub, Path::new("PYI033.py"))] + #[test_case(Rule::TypeCommentInStub, Path::new("PYI033.pyi"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff/src/rules/flake8_pyi/rules/mod.rs b/crates/ruff/src/rules/flake8_pyi/rules/mod.rs index bda523cf1d4441..9327b956b3c568 100644 --- a/crates/ruff/src/rules/flake8_pyi/rules/mod.rs +++ b/crates/ruff/src/rules/flake8_pyi/rules/mod.rs @@ -7,6 +7,7 @@ pub use simple_defaults::{ argument_simple_defaults, typed_argument_simple_defaults, ArgumentSimpleDefaults, TypedArgumentSimpleDefaults, }; +pub use type_comment_in_stub::{type_comment_in_stub, TypeCommentInStub}; pub use unrecognized_platform::{ unrecognized_platform, UnrecognizedPlatformCheck, UnrecognizedPlatformName, }; @@ -17,4 +18,5 @@ mod non_empty_stub_body; mod pass_statement_stub_body; mod prefix_type_params; mod simple_defaults; +mod type_comment_in_stub; mod unrecognized_platform; diff --git a/crates/ruff/src/rules/flake8_pyi/rules/type_comment_in_stub.rs b/crates/ruff/src/rules/flake8_pyi/rules/type_comment_in_stub.rs new file mode 100644 index 00000000000000..1cd6c6ee85d20b --- /dev/null +++ b/crates/ruff/src/rules/flake8_pyi/rules/type_comment_in_stub.rs @@ -0,0 +1,59 @@ +use rustpython_parser::lexer::LexResult; +use rustpython_parser::Tok; + +use ruff_macros::{define_violation, derive_message_formats}; + +use crate::registry::Diagnostic; +use crate::violation::Violation; +use crate::Range; + +define_violation!( + /// ## What it does + /// Do not use type comments (e.g. `x = 1 # type: int`) in stubs, even if + /// the stub supports Python 2. Always use annotations instead (e.g. + /// `x: int = 1`). + /// + /// ## Why is this bad? + /// You should use type annotation directly in pyi files. + /// + /// ## Example + /// ```python + /// x = 1 # type: int + /// ``` + /// + /// Use instead: + /// ```python + /// x: int = 1 + /// ``` + pub struct TypeCommentInStub; +); +impl Violation for TypeCommentInStub { + #[derive_message_formats] + fn message(&self) -> String { + format!("Don't use type comments in stub file") + } +} + +/// PYI033 +pub fn type_comment_in_stub(tokens: &[LexResult]) -> Vec { + let mut diagnostics = vec![]; + + for token in tokens.iter().flatten() { + if let (location, Tok::Comment(comment), end_location) = token { + // I couldn't find any PEP on the exact syntax (the closest being + // https://peps.python.org/pep-0484/#type-comments), but every case I saw used + // `# type:` verbatim so this seems to be the right thing to pick + if comment.starts_with("# type:") { + diagnostics.push(Diagnostic::new( + TypeCommentInStub, + Range { + location: *location, + end_location: *end_location, + }, + )); + } + } + } + + diagnostics +} diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI033_PYI033.py.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI033_PYI033.py.snap new file mode 100644 index 00000000000000..efcc2d0c99b2f3 --- /dev/null +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI033_PYI033.py.snap @@ -0,0 +1,6 @@ +--- +source: crates/ruff/src/rules/flake8_pyi/mod.rs +expression: diagnostics +--- +[] + diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI033_PYI033.pyi.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI033_PYI033.pyi.snap new file mode 100644 index 00000000000000..58b8ef5438694d --- /dev/null +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI033_PYI033.pyi.snap @@ -0,0 +1,25 @@ +--- +source: crates/ruff/src/rules/flake8_pyi/mod.rs +expression: diagnostics +--- +- kind: + TypeCommentInStub: ~ + location: + row: 2 + column: 6 + end_location: + row: 2 + column: 17 + fix: ~ + parent: ~ +- kind: + TypeCommentInStub: ~ + location: + row: 6 + column: 17 + end_location: + row: 6 + column: 36 + fix: ~ + parent: ~ + diff --git a/docs/configuration.md b/docs/configuration.md index 4417c093e690a2..5587637c9198dd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -206,6 +206,8 @@ Options: Run in watch mode by re-running whenever files change --fix-only Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix` + --ignore-noqa + Ignore any `# noqa` comments --format Output serialization format for violations [env: RUFF_FORMAT=] [possible values: text, json, junit, grouped, github, gitlab, pylint] --target-version diff --git a/ruff.schema.json b/ruff.schema.json index 82c130e1029fa4..1ab755d5da6555 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1938,6 +1938,8 @@ "PYI014", "PYI02", "PYI021", + "PYI03", + "PYI033", "Q", "Q0", "Q00",