mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 14:21:24 +00:00
Add flake-pyi PYI033 "Do not use type comments in stubs" (#3302)
This commit is contained in:
parent
98209be8aa
commit
3bcffb5bdd
12 changed files with 279 additions and 4 deletions
33
crates/ruff/resources/test/fixtures/flake8_pyi/PYI033.py
vendored
Normal file
33
crates/ruff/resources/test/fixtures/flake8_pyi/PYI033.py
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# From https://github.com/PyCQA/flake8-pyi/blob/4212bec43dbc4020a59b90e2957c9488575e57ba/tests/type_comments.pyi
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
|
A: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
B: TypeAlias = None # type: str # And here's an extra comment about why it's that type # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
C: TypeAlias = None #type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
D: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
E: TypeAlias = None# type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
F: TypeAlias = None#type:int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
|
||||||
|
def func(
|
||||||
|
arg1, # type: dict[str, int] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
arg2 # type: Sequence[bytes] # And here's some more info about this arg # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
): ...
|
||||||
|
|
||||||
|
class Foo:
|
||||||
|
Attr: TypeAlias = None # type: set[str] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
|
||||||
|
G: TypeAlias = None # type: ignore
|
||||||
|
H: TypeAlias = None # type: ignore[attr-defined]
|
||||||
|
I: TypeAlias = None #type: ignore
|
||||||
|
J: TypeAlias = None # type: ignore
|
||||||
|
K: TypeAlias = None# type: ignore
|
||||||
|
L: TypeAlias = None#type:ignore
|
||||||
|
|
||||||
|
# Whole line commented out # type: int
|
||||||
|
M: TypeAlias = None # type: can't parse me!
|
||||||
|
|
||||||
|
class Bar:
|
||||||
|
N: TypeAlias = None # type: can't parse me either!
|
||||||
|
# This whole line is commented out and indented # type: str
|
33
crates/ruff/resources/test/fixtures/flake8_pyi/PYI033.pyi
vendored
Normal file
33
crates/ruff/resources/test/fixtures/flake8_pyi/PYI033.pyi
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# From https://github.com/PyCQA/flake8-pyi/blob/4212bec43dbc4020a59b90e2957c9488575e57ba/tests/type_comments.pyi
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
|
A: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
B: TypeAlias = None # type: str # And here's an extra comment about why it's that type # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
C: TypeAlias = None #type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
D: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
E: TypeAlias = None# type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
F: TypeAlias = None#type:int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
|
||||||
|
def func(
|
||||||
|
arg1, # type: dict[str, int] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
arg2 # type: Sequence[bytes] # And here's some more info about this arg # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
): ...
|
||||||
|
|
||||||
|
class Foo:
|
||||||
|
Attr: TypeAlias = None # type: set[str] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||||
|
|
||||||
|
G: TypeAlias = None # type: ignore
|
||||||
|
H: TypeAlias = None # type: ignore[attr-defined]
|
||||||
|
I: TypeAlias = None #type: ignore
|
||||||
|
J: TypeAlias = None # type: ignore
|
||||||
|
K: TypeAlias = None# type: ignore
|
||||||
|
L: TypeAlias = None#type:ignore
|
||||||
|
|
||||||
|
# Whole line commented out # type: int
|
||||||
|
M: TypeAlias = None # type: can't parse me!
|
||||||
|
|
||||||
|
class Bar:
|
||||||
|
N: TypeAlias = None # type: can't parse me either!
|
||||||
|
# This whole line is commented out and indented # type: str
|
|
@ -7,8 +7,8 @@ use crate::lex::docstring_detection::StateMachine;
|
||||||
use crate::registry::{Diagnostic, Rule};
|
use crate::registry::{Diagnostic, Rule};
|
||||||
use crate::rules::ruff::rules::Context;
|
use crate::rules::ruff::rules::Context;
|
||||||
use crate::rules::{
|
use crate::rules::{
|
||||||
eradicate, flake8_commas, flake8_implicit_str_concat, flake8_quotes, pycodestyle, pyupgrade,
|
eradicate, flake8_commas, flake8_implicit_str_concat, flake8_pyi, flake8_quotes, pycodestyle,
|
||||||
ruff,
|
pyupgrade, ruff,
|
||||||
};
|
};
|
||||||
use crate::settings::{flags, Settings};
|
use crate::settings::{flags, Settings};
|
||||||
use crate::source_code::Locator;
|
use crate::source_code::Locator;
|
||||||
|
@ -18,6 +18,7 @@ pub fn check_tokens(
|
||||||
tokens: &[LexResult],
|
tokens: &[LexResult],
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
autofix: flags::Autofix,
|
autofix: flags::Autofix,
|
||||||
|
is_interface_definition: bool,
|
||||||
) -> Vec<Diagnostic> {
|
) -> Vec<Diagnostic> {
|
||||||
let mut diagnostics: Vec<Diagnostic> = vec![];
|
let mut diagnostics: Vec<Diagnostic> = vec![];
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@ pub fn check_tokens(
|
||||||
.enabled(&Rule::TrailingCommaOnBareTupleProhibited)
|
.enabled(&Rule::TrailingCommaOnBareTupleProhibited)
|
||||||
|| settings.rules.enabled(&Rule::TrailingCommaProhibited);
|
|| settings.rules.enabled(&Rule::TrailingCommaProhibited);
|
||||||
let enforce_extraneous_parenthesis = settings.rules.enabled(&Rule::ExtraneousParentheses);
|
let enforce_extraneous_parenthesis = settings.rules.enabled(&Rule::ExtraneousParentheses);
|
||||||
|
let enforce_type_comment_in_stub = settings.rules.enabled(&Rule::TypeCommentInStub);
|
||||||
|
|
||||||
// RUF001, RUF002, RUF003
|
// RUF001, RUF002, RUF003
|
||||||
if enforce_ambiguous_unicode_character {
|
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
|
diagnostics
|
||||||
}
|
}
|
||||||
|
|
|
@ -511,6 +511,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||||
(Flake8Pyi, "011") => Rule::TypedArgumentSimpleDefaults,
|
(Flake8Pyi, "011") => Rule::TypedArgumentSimpleDefaults,
|
||||||
(Flake8Pyi, "014") => Rule::ArgumentSimpleDefaults,
|
(Flake8Pyi, "014") => Rule::ArgumentSimpleDefaults,
|
||||||
(Flake8Pyi, "021") => Rule::DocstringInStub,
|
(Flake8Pyi, "021") => Rule::DocstringInStub,
|
||||||
|
(Flake8Pyi, "033") => Rule::TypeCommentInStub,
|
||||||
|
|
||||||
// flake8-pytest-style
|
// flake8-pytest-style
|
||||||
(Flake8PytestStyle, "001") => Rule::IncorrectFixtureParenthesesStyle,
|
(Flake8PytestStyle, "001") => Rule::IncorrectFixtureParenthesesStyle,
|
||||||
|
|
|
@ -21,6 +21,7 @@ use crate::doc_lines::{doc_lines_from_ast, doc_lines_from_tokens};
|
||||||
use crate::message::{Message, Source};
|
use crate::message::{Message, Source};
|
||||||
use crate::noqa::{add_noqa, rule_is_ignored};
|
use crate::noqa::{add_noqa, rule_is_ignored};
|
||||||
use crate::registry::{Diagnostic, Rule};
|
use crate::registry::{Diagnostic, Rule};
|
||||||
|
use crate::resolver::is_interface_definition_path;
|
||||||
use crate::rules::pycodestyle;
|
use crate::rules::pycodestyle;
|
||||||
use crate::settings::{flags, Settings};
|
use crate::settings::{flags, Settings};
|
||||||
use crate::source_code::{Indexer, Locator, Stylist};
|
use crate::source_code::{Indexer, Locator, Stylist};
|
||||||
|
@ -83,7 +84,14 @@ pub fn check_path(
|
||||||
.iter_enabled()
|
.iter_enabled()
|
||||||
.any(|rule_code| rule_code.lint_source().is_tokens())
|
.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.
|
// Run the filesystem-based rules.
|
||||||
|
|
|
@ -485,6 +485,7 @@ ruff_macros::register_rules!(
|
||||||
rules::flake8_pyi::rules::DocstringInStub,
|
rules::flake8_pyi::rules::DocstringInStub,
|
||||||
rules::flake8_pyi::rules::TypedArgumentSimpleDefaults,
|
rules::flake8_pyi::rules::TypedArgumentSimpleDefaults,
|
||||||
rules::flake8_pyi::rules::ArgumentSimpleDefaults,
|
rules::flake8_pyi::rules::ArgumentSimpleDefaults,
|
||||||
|
rules::flake8_pyi::rules::TypeCommentInStub,
|
||||||
// flake8-pytest-style
|
// flake8-pytest-style
|
||||||
rules::flake8_pytest_style::rules::IncorrectFixtureParenthesesStyle,
|
rules::flake8_pytest_style::rules::IncorrectFixtureParenthesesStyle,
|
||||||
rules::flake8_pytest_style::rules::FixturePositionalArgs,
|
rules::flake8_pytest_style::rules::FixturePositionalArgs,
|
||||||
|
@ -838,7 +839,8 @@ impl Rule {
|
||||||
| Rule::MultipleStatementsOnOneLineColon
|
| Rule::MultipleStatementsOnOneLineColon
|
||||||
| Rule::UselessSemicolon
|
| Rule::UselessSemicolon
|
||||||
| Rule::MultipleStatementsOnOneLineSemicolon
|
| Rule::MultipleStatementsOnOneLineSemicolon
|
||||||
| Rule::TrailingCommaProhibited => &LintSource::Tokens,
|
| Rule::TrailingCommaProhibited
|
||||||
|
| Rule::TypeCommentInStub => &LintSource::Tokens,
|
||||||
Rule::IOError => &LintSource::Io,
|
Rule::IOError => &LintSource::Io,
|
||||||
Rule::UnsortedImports | Rule::MissingRequiredImport => &LintSource::Imports,
|
Rule::UnsortedImports | Rule::MissingRequiredImport => &LintSource::Imports,
|
||||||
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => &LintSource::Filesystem,
|
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => &LintSource::Filesystem,
|
||||||
|
|
|
@ -31,6 +31,8 @@ mod tests {
|
||||||
#[test_case(Rule::ArgumentSimpleDefaults, Path::new("PYI014.pyi"))]
|
#[test_case(Rule::ArgumentSimpleDefaults, Path::new("PYI014.pyi"))]
|
||||||
#[test_case(Rule::DocstringInStub, Path::new("PYI021.py"))]
|
#[test_case(Rule::DocstringInStub, Path::new("PYI021.py"))]
|
||||||
#[test_case(Rule::DocstringInStub, Path::new("PYI021.pyi"))]
|
#[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<()> {
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub use simple_defaults::{
|
||||||
argument_simple_defaults, typed_argument_simple_defaults, ArgumentSimpleDefaults,
|
argument_simple_defaults, typed_argument_simple_defaults, ArgumentSimpleDefaults,
|
||||||
TypedArgumentSimpleDefaults,
|
TypedArgumentSimpleDefaults,
|
||||||
};
|
};
|
||||||
|
pub use type_comment_in_stub::{type_comment_in_stub, TypeCommentInStub};
|
||||||
pub use unrecognized_platform::{
|
pub use unrecognized_platform::{
|
||||||
unrecognized_platform, UnrecognizedPlatformCheck, UnrecognizedPlatformName,
|
unrecognized_platform, UnrecognizedPlatformCheck, UnrecognizedPlatformName,
|
||||||
};
|
};
|
||||||
|
@ -17,4 +18,5 @@ mod non_empty_stub_body;
|
||||||
mod pass_statement_stub_body;
|
mod pass_statement_stub_body;
|
||||||
mod prefix_type_params;
|
mod prefix_type_params;
|
||||||
mod simple_defaults;
|
mod simple_defaults;
|
||||||
|
mod type_comment_in_stub;
|
||||||
mod unrecognized_platform;
|
mod unrecognized_platform;
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use regex::Regex;
|
||||||
|
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
|
||||||
|
/// Checks for the use of type comments (e.g., `x = 1 # type: int`) in stub
|
||||||
|
/// files.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Stub (`.pyi`) files should use type annotations directly, rather
|
||||||
|
/// than type comments, even if they're intended to support Python 2, since
|
||||||
|
/// stub files are not executed at runtime. The one exception is `# type: ignore`.
|
||||||
|
///
|
||||||
|
/// ## 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static TYPE_COMMENT_REGEX: Lazy<Regex> =
|
||||||
|
Lazy::new(|| Regex::new(r"^#\s*type:\s*([^#]+)(\s*#.*?)?$").unwrap());
|
||||||
|
static TYPE_IGNORE_REGEX: Lazy<Regex> =
|
||||||
|
Lazy::new(|| Regex::new(r"^#\s*type:\s*ignore([^#]+)?(\s*#.*?)?$").unwrap());
|
||||||
|
|
||||||
|
/// PYI033
|
||||||
|
pub fn type_comment_in_stub(tokens: &[LexResult]) -> Vec<Diagnostic> {
|
||||||
|
let mut diagnostics = vec![];
|
||||||
|
|
||||||
|
for token in tokens.iter().flatten() {
|
||||||
|
if let (location, Tok::Comment(comment), end_location) = token {
|
||||||
|
if TYPE_COMMENT_REGEX.is_match(comment) && !TYPE_IGNORE_REGEX.is_match(comment) {
|
||||||
|
diagnostics.push(Diagnostic::new(
|
||||||
|
TypeCommentInStub,
|
||||||
|
Range {
|
||||||
|
location: *location,
|
||||||
|
end_location: *end_location,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnostics
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
[]
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
- kind:
|
||||||
|
TypeCommentInStub: ~
|
||||||
|
location:
|
||||||
|
row: 6
|
||||||
|
column: 21
|
||||||
|
end_location:
|
||||||
|
row: 6
|
||||||
|
column: 127
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
TypeCommentInStub: ~
|
||||||
|
location:
|
||||||
|
row: 7
|
||||||
|
column: 21
|
||||||
|
end_location:
|
||||||
|
row: 7
|
||||||
|
column: 183
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
TypeCommentInStub: ~
|
||||||
|
location:
|
||||||
|
row: 8
|
||||||
|
column: 21
|
||||||
|
end_location:
|
||||||
|
row: 8
|
||||||
|
column: 126
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
TypeCommentInStub: ~
|
||||||
|
location:
|
||||||
|
row: 9
|
||||||
|
column: 21
|
||||||
|
end_location:
|
||||||
|
row: 9
|
||||||
|
column: 132
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
TypeCommentInStub: ~
|
||||||
|
location:
|
||||||
|
row: 10
|
||||||
|
column: 19
|
||||||
|
end_location:
|
||||||
|
row: 10
|
||||||
|
column: 128
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
TypeCommentInStub: ~
|
||||||
|
location:
|
||||||
|
row: 11
|
||||||
|
column: 19
|
||||||
|
end_location:
|
||||||
|
row: 11
|
||||||
|
column: 123
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
TypeCommentInStub: ~
|
||||||
|
location:
|
||||||
|
row: 14
|
||||||
|
column: 11
|
||||||
|
end_location:
|
||||||
|
row: 14
|
||||||
|
column: 128
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
TypeCommentInStub: ~
|
||||||
|
location:
|
||||||
|
row: 15
|
||||||
|
column: 10
|
||||||
|
end_location:
|
||||||
|
row: 15
|
||||||
|
column: 172
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
TypeCommentInStub: ~
|
||||||
|
location:
|
||||||
|
row: 19
|
||||||
|
column: 28
|
||||||
|
end_location:
|
||||||
|
row: 19
|
||||||
|
column: 139
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
TypeCommentInStub: ~
|
||||||
|
location:
|
||||||
|
row: 29
|
||||||
|
column: 21
|
||||||
|
end_location:
|
||||||
|
row: 29
|
||||||
|
column: 44
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
TypeCommentInStub: ~
|
||||||
|
location:
|
||||||
|
row: 32
|
||||||
|
column: 25
|
||||||
|
end_location:
|
||||||
|
row: 32
|
||||||
|
column: 55
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
|
2
ruff.schema.json
generated
2
ruff.schema.json
generated
|
@ -1948,6 +1948,8 @@
|
||||||
"PYI014",
|
"PYI014",
|
||||||
"PYI02",
|
"PYI02",
|
||||||
"PYI021",
|
"PYI021",
|
||||||
|
"PYI03",
|
||||||
|
"PYI033",
|
||||||
"Q",
|
"Q",
|
||||||
"Q0",
|
"Q0",
|
||||||
"Q00",
|
"Q00",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue