mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
[pycodestyle
] Whitespace after decorator (E204
) (#12140)
## Summary <!-- What's the purpose of the change? What does it do, and why? --> This is the implementation for the new rule of `pycodestyle (E204)`. It follows the guidlines described in the contributing site, and as such it has a new file named `whitespace_after_decorator.rs`, a new test file called `E204.py`, and as such invokes the `function` in the `AST statement checker` for functions and functions in classes. Linking #2402 because it has all the pycodestyle rules. ## Test Plan <!-- How was it tested? --> The file E204.py, has a `decorator` defined called wrapper, and this decorator is used for 2 cases. The first one is when a `function` which has a `decorator` is called in the file, and the second one is when there is a `class` and 2 `methods` are defined for the `class` with a `decorator` attached it. Test file: ``` python def foo(fun): def wrapper(): print('before') fun() print('after') return wrapper # No error @foo def bar(): print('bar') # E204 @ foo def baz(): print('baz') class Test: # No error @foo def bar(self): print('bar') # E204 @ foo def baz(self): print('baz') ``` I am still new to rust and any suggestion is appreciated. Specially with the way im using native ruff utilities. --------- Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
parent
5e7ba05612
commit
1e07bfa373
9 changed files with 181 additions and 0 deletions
34
crates/ruff_linter/resources/test/fixtures/pycodestyle/E204.py
vendored
Normal file
34
crates/ruff_linter/resources/test/fixtures/pycodestyle/E204.py
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
def foo(fun):
|
||||||
|
def wrapper():
|
||||||
|
print('before')
|
||||||
|
fun()
|
||||||
|
print('after')
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
# No error
|
||||||
|
@foo
|
||||||
|
def bar():
|
||||||
|
print('bar')
|
||||||
|
|
||||||
|
# E204
|
||||||
|
@ foo
|
||||||
|
def baz():
|
||||||
|
print('baz')
|
||||||
|
|
||||||
|
class Test:
|
||||||
|
# No error
|
||||||
|
@foo
|
||||||
|
def bar(self):
|
||||||
|
print('bar')
|
||||||
|
|
||||||
|
# E204
|
||||||
|
@ foo
|
||||||
|
def baz(self):
|
||||||
|
print('baz')
|
||||||
|
|
||||||
|
|
||||||
|
# E204
|
||||||
|
@ \
|
||||||
|
foo
|
||||||
|
def baz():
|
||||||
|
print('baz')
|
|
@ -368,6 +368,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::UnusedAsync) {
|
if checker.enabled(Rule::UnusedAsync) {
|
||||||
ruff::rules::unused_async(checker, function_def);
|
ruff::rules::unused_async(checker, function_def);
|
||||||
}
|
}
|
||||||
|
if checker.enabled(Rule::WhitespaceAfterDecorator) {
|
||||||
|
pycodestyle::rules::whitespace_after_decorator(checker, decorator_list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Stmt::Return(_) => {
|
Stmt::Return(_) => {
|
||||||
if checker.enabled(Rule::ReturnOutsideFunction) {
|
if checker.enabled(Rule::ReturnOutsideFunction) {
|
||||||
|
@ -531,6 +534,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::MetaClassABCMeta) {
|
if checker.enabled(Rule::MetaClassABCMeta) {
|
||||||
refurb::rules::metaclass_abcmeta(checker, class_def);
|
refurb::rules::metaclass_abcmeta(checker, class_def);
|
||||||
}
|
}
|
||||||
|
if checker.enabled(Rule::WhitespaceAfterDecorator) {
|
||||||
|
pycodestyle::rules::whitespace_after_decorator(checker, decorator_list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Stmt::Import(ast::StmtImport { names, range: _ }) => {
|
Stmt::Import(ast::StmtImport { names, range: _ }) => {
|
||||||
if checker.enabled(Rule::MultipleImportsOnOneLine) {
|
if checker.enabled(Rule::MultipleImportsOnOneLine) {
|
||||||
|
|
|
@ -78,6 +78,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Pycodestyle, "E201") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceAfterOpenBracket),
|
(Pycodestyle, "E201") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceAfterOpenBracket),
|
||||||
(Pycodestyle, "E202") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeCloseBracket),
|
(Pycodestyle, "E202") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeCloseBracket),
|
||||||
(Pycodestyle, "E203") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceBeforePunctuation),
|
(Pycodestyle, "E203") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceBeforePunctuation),
|
||||||
|
(Pycodestyle, "E204") => (RuleGroup::Preview, rules::pycodestyle::rules::WhitespaceAfterDecorator),
|
||||||
(Pycodestyle, "E211") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeParameters),
|
(Pycodestyle, "E211") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeParameters),
|
||||||
(Pycodestyle, "E221") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeOperator),
|
(Pycodestyle, "E221") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeOperator),
|
||||||
(Pycodestyle, "E222") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterOperator),
|
(Pycodestyle, "E222") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterOperator),
|
||||||
|
|
|
@ -58,6 +58,7 @@ mod tests {
|
||||||
#[test_case(Rule::TypeComparison, Path::new("E721.py"))]
|
#[test_case(Rule::TypeComparison, Path::new("E721.py"))]
|
||||||
#[test_case(Rule::UselessSemicolon, Path::new("E70.py"))]
|
#[test_case(Rule::UselessSemicolon, Path::new("E70.py"))]
|
||||||
#[test_case(Rule::UselessSemicolon, Path::new("E703.ipynb"))]
|
#[test_case(Rule::UselessSemicolon, Path::new("E703.ipynb"))]
|
||||||
|
#[test_case(Rule::WhitespaceAfterDecorator, Path::new("E204.py"))]
|
||||||
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(
|
||||||
|
|
|
@ -20,6 +20,7 @@ pub(crate) use tab_indentation::*;
|
||||||
pub(crate) use too_many_newlines_at_end_of_file::*;
|
pub(crate) use too_many_newlines_at_end_of_file::*;
|
||||||
pub(crate) use trailing_whitespace::*;
|
pub(crate) use trailing_whitespace::*;
|
||||||
pub(crate) use type_comparison::*;
|
pub(crate) use type_comparison::*;
|
||||||
|
pub(crate) use whitespace_after_decorator::*;
|
||||||
|
|
||||||
mod ambiguous_class_name;
|
mod ambiguous_class_name;
|
||||||
mod ambiguous_function_name;
|
mod ambiguous_function_name;
|
||||||
|
@ -43,3 +44,4 @@ mod tab_indentation;
|
||||||
mod too_many_newlines_at_end_of_file;
|
mod too_many_newlines_at_end_of_file;
|
||||||
mod trailing_whitespace;
|
mod trailing_whitespace;
|
||||||
mod type_comparison;
|
mod type_comparison;
|
||||||
|
mod whitespace_after_decorator;
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::Decorator;
|
||||||
|
use ruff_python_trivia::is_python_whitespace;
|
||||||
|
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for trailing whitespace after a decorator's opening `@`.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Including whitespace after the `@` symbol is not compliant with
|
||||||
|
/// [PEP 8].
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```python
|
||||||
|
/// @ decorator
|
||||||
|
/// def func():
|
||||||
|
/// pass
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// @decorator
|
||||||
|
/// def func():
|
||||||
|
/// pass
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [PEP 8]: https://peps.python.org/pep-0008/#maximum-line-length
|
||||||
|
|
||||||
|
#[violation]
|
||||||
|
pub struct WhitespaceAfterDecorator;
|
||||||
|
|
||||||
|
impl AlwaysFixableViolation for WhitespaceAfterDecorator {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
format!("Whitespace after decorator")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fix_title(&self) -> String {
|
||||||
|
"Remove whitespace".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// E204
|
||||||
|
pub(crate) fn whitespace_after_decorator(checker: &mut Checker, decorator_list: &[Decorator]) {
|
||||||
|
for decorator in decorator_list {
|
||||||
|
let decorator_text = checker.locator().slice(decorator);
|
||||||
|
|
||||||
|
// Determine whether the `@` is followed by whitespace.
|
||||||
|
if let Some(trailing) = decorator_text.strip_prefix('@') {
|
||||||
|
// Collect the whitespace characters after the `@`.
|
||||||
|
if trailing.chars().next().is_some_and(is_python_whitespace) {
|
||||||
|
let end = trailing
|
||||||
|
.chars()
|
||||||
|
.position(|c| !(is_python_whitespace(c) || matches!(c, '\n' | '\r' | '\\')))
|
||||||
|
.unwrap_or(trailing.len());
|
||||||
|
|
||||||
|
let start = decorator.start() + TextSize::from(1);
|
||||||
|
let end = start + TextSize::try_from(end).unwrap();
|
||||||
|
let range = TextRange::new(start, end);
|
||||||
|
|
||||||
|
let mut diagnostic = Diagnostic::new(WhitespaceAfterDecorator, range);
|
||||||
|
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range)));
|
||||||
|
checker.diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||||
|
---
|
||||||
|
E204.py:14:2: E204 [*] Whitespace after decorator
|
||||||
|
|
|
||||||
|
13 | # E204
|
||||||
|
14 | @ foo
|
||||||
|
| ^ E204
|
||||||
|
15 | def baz():
|
||||||
|
16 | print('baz')
|
||||||
|
|
|
||||||
|
= help: Remove whitespace
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
11 11 | print('bar')
|
||||||
|
12 12 |
|
||||||
|
13 13 | # E204
|
||||||
|
14 |-@ foo
|
||||||
|
14 |+@foo
|
||||||
|
15 15 | def baz():
|
||||||
|
16 16 | print('baz')
|
||||||
|
17 17 |
|
||||||
|
|
||||||
|
E204.py:25:6: E204 [*] Whitespace after decorator
|
||||||
|
|
|
||||||
|
24 | # E204
|
||||||
|
25 | @ foo
|
||||||
|
| ^ E204
|
||||||
|
26 | def baz(self):
|
||||||
|
27 | print('baz')
|
||||||
|
|
|
||||||
|
= help: Remove whitespace
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
22 22 | print('bar')
|
||||||
|
23 23 |
|
||||||
|
24 24 | # E204
|
||||||
|
25 |- @ foo
|
||||||
|
25 |+ @foo
|
||||||
|
26 26 | def baz(self):
|
||||||
|
27 27 | print('baz')
|
||||||
|
28 28 |
|
||||||
|
|
||||||
|
E204.py:31:2: E204 [*] Whitespace after decorator
|
||||||
|
|
|
||||||
|
30 | # E204
|
||||||
|
31 | @ \
|
||||||
|
| __^
|
||||||
|
32 | | foo
|
||||||
|
| |_^ E204
|
||||||
|
33 | def baz():
|
||||||
|
34 | print('baz')
|
||||||
|
|
|
||||||
|
= help: Remove whitespace
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
28 28 |
|
||||||
|
29 29 |
|
||||||
|
30 30 | # E204
|
||||||
|
31 |-@ \
|
||||||
|
32 |-foo
|
||||||
|
31 |+@foo
|
||||||
|
33 32 | def baz():
|
||||||
|
34 33 | print('baz')
|
1
ruff.schema.json
generated
1
ruff.schema.json
generated
|
@ -2904,6 +2904,7 @@
|
||||||
"E201",
|
"E201",
|
||||||
"E202",
|
"E202",
|
||||||
"E203",
|
"E203",
|
||||||
|
"E204",
|
||||||
"E21",
|
"E21",
|
||||||
"E211",
|
"E211",
|
||||||
"E22",
|
"E22",
|
||||||
|
|
|
@ -86,6 +86,7 @@ KNOWN_FORMATTING_VIOLATIONS = [
|
||||||
"unnecessary-class-parentheses",
|
"unnecessary-class-parentheses",
|
||||||
"unnecessary-escaped-quote",
|
"unnecessary-escaped-quote",
|
||||||
"useless-semicolon",
|
"useless-semicolon",
|
||||||
|
"whitespace-after-decorator",
|
||||||
"whitespace-after-open-bracket",
|
"whitespace-after-open-bracket",
|
||||||
"whitespace-before-close-bracket",
|
"whitespace-before-close-bracket",
|
||||||
"whitespace-before-parameters",
|
"whitespace-before-parameters",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue