mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-21 19:05:09 +00:00
feat(E211): add rule + autofix (#3313)
This commit is contained in:
parent
508bc605a5
commit
6f649d6579
17 changed files with 216 additions and 11 deletions
14
crates/ruff/resources/test/fixtures/pycodestyle/E21.py
vendored
Normal file
14
crates/ruff/resources/test/fixtures/pycodestyle/E21.py
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#: E211
|
||||||
|
spam (1)
|
||||||
|
#: E211 E211
|
||||||
|
dict ['key'] = list [index]
|
||||||
|
#: E211
|
||||||
|
dict['key'] ['subkey'] = list[index]
|
||||||
|
#: Okay
|
||||||
|
spam(1)
|
||||||
|
dict['key'] = list[index]
|
||||||
|
|
||||||
|
|
||||||
|
# This is not prohibited by PEP8, but avoid it.
|
||||||
|
class Foo (Bar, Baz):
|
||||||
|
pass
|
|
@ -1,17 +1,19 @@
|
||||||
|
#![allow(dead_code, unused_imports, unused_variables)]
|
||||||
|
|
||||||
use bisection::bisect_left;
|
use bisection::bisect_left;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rustpython_parser::ast::Location;
|
use rustpython_parser::ast::Location;
|
||||||
use rustpython_parser::lexer::LexResult;
|
use rustpython_parser::lexer::LexResult;
|
||||||
|
|
||||||
use crate::ast::types::Range;
|
use crate::ast::types::Range;
|
||||||
use crate::registry::Diagnostic;
|
use crate::registry::{Diagnostic, Rule};
|
||||||
use crate::rules::pycodestyle::logical_lines::{iter_logical_lines, TokenFlags};
|
use crate::rules::pycodestyle::logical_lines::{iter_logical_lines, TokenFlags};
|
||||||
use crate::rules::pycodestyle::rules::{
|
use crate::rules::pycodestyle::rules::{
|
||||||
extraneous_whitespace, indentation, missing_whitespace_after_keyword, space_around_operator,
|
extraneous_whitespace, indentation, missing_whitespace_after_keyword, space_around_operator,
|
||||||
whitespace_around_keywords, whitespace_around_named_parameter_equals,
|
whitespace_around_keywords, whitespace_around_named_parameter_equals,
|
||||||
whitespace_before_comment,
|
whitespace_before_comment, whitespace_before_parameters,
|
||||||
};
|
};
|
||||||
use crate::settings::Settings;
|
use crate::settings::{flags, Settings};
|
||||||
use crate::source_code::{Locator, Stylist};
|
use crate::source_code::{Locator, Stylist};
|
||||||
|
|
||||||
/// Return the amount of indentation, expanding tabs to the next multiple of 8.
|
/// Return the amount of indentation, expanding tabs to the next multiple of 8.
|
||||||
|
@ -40,6 +42,7 @@ pub fn check_logical_lines(
|
||||||
locator: &Locator,
|
locator: &Locator,
|
||||||
stylist: &Stylist,
|
stylist: &Stylist,
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
|
autofix: flags::Autofix,
|
||||||
) -> Vec<Diagnostic> {
|
) -> Vec<Diagnostic> {
|
||||||
let mut diagnostics = vec![];
|
let mut diagnostics = vec![];
|
||||||
|
|
||||||
|
@ -149,6 +152,21 @@ pub fn check_logical_lines(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if line.flags.contains(TokenFlags::BRACKET) {
|
||||||
|
#[cfg(feature = "logical_lines")]
|
||||||
|
let should_fix =
|
||||||
|
autofix.into() && settings.rules.should_fix(&Rule::WhitespaceBeforeParameters);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "logical_lines"))]
|
||||||
|
let should_fix = false;
|
||||||
|
|
||||||
|
for diagnostic in whitespace_before_parameters(&line.tokens, should_fix) {
|
||||||
|
if settings.rules.enabled(diagnostic.kind.rule()) {
|
||||||
|
diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (index, kind) in indentation(
|
for (index, kind) in indentation(
|
||||||
&line,
|
&line,
|
||||||
prev_line.as_ref(),
|
prev_line.as_ref(),
|
||||||
|
|
|
@ -29,6 +29,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||||
#[cfg(feature = "logical_lines")]
|
#[cfg(feature = "logical_lines")]
|
||||||
(Pycodestyle, "E203") => Rule::WhitespaceBeforePunctuation,
|
(Pycodestyle, "E203") => Rule::WhitespaceBeforePunctuation,
|
||||||
#[cfg(feature = "logical_lines")]
|
#[cfg(feature = "logical_lines")]
|
||||||
|
(Pycodestyle, "E211") => Rule::WhitespaceBeforeParameters,
|
||||||
|
#[cfg(feature = "logical_lines")]
|
||||||
(Pycodestyle, "E221") => Rule::MultipleSpacesBeforeOperator,
|
(Pycodestyle, "E221") => Rule::MultipleSpacesBeforeOperator,
|
||||||
#[cfg(feature = "logical_lines")]
|
#[cfg(feature = "logical_lines")]
|
||||||
(Pycodestyle, "E222") => Rule::MultipleSpacesAfterOperator,
|
(Pycodestyle, "E222") => Rule::MultipleSpacesAfterOperator,
|
||||||
|
|
|
@ -101,7 +101,13 @@ pub fn check_path(
|
||||||
.iter_enabled()
|
.iter_enabled()
|
||||||
.any(|rule_code| rule_code.lint_source().is_logical_lines())
|
.any(|rule_code| rule_code.lint_source().is_logical_lines())
|
||||||
{
|
{
|
||||||
diagnostics.extend(check_logical_lines(&tokens, locator, stylist, settings));
|
diagnostics.extend(check_logical_lines(
|
||||||
|
&tokens,
|
||||||
|
locator,
|
||||||
|
stylist,
|
||||||
|
settings,
|
||||||
|
flags::Autofix::Enabled,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the AST-based rules.
|
// Run the AST-based rules.
|
||||||
|
|
|
@ -63,6 +63,8 @@ ruff_macros::register_rules!(
|
||||||
#[cfg(feature = "logical_lines")]
|
#[cfg(feature = "logical_lines")]
|
||||||
rules::pycodestyle::rules::MissingWhitespaceAroundParameterEquals,
|
rules::pycodestyle::rules::MissingWhitespaceAroundParameterEquals,
|
||||||
#[cfg(feature = "logical_lines")]
|
#[cfg(feature = "logical_lines")]
|
||||||
|
rules::pycodestyle::rules::WhitespaceBeforeParameters,
|
||||||
|
#[cfg(feature = "logical_lines")]
|
||||||
rules::pycodestyle::rules::TabBeforeKeyword,
|
rules::pycodestyle::rules::TabBeforeKeyword,
|
||||||
rules::pycodestyle::rules::MultipleImportsOnOneLine,
|
rules::pycodestyle::rules::MultipleImportsOnOneLine,
|
||||||
rules::pycodestyle::rules::ModuleImportNotAtTopOfFile,
|
rules::pycodestyle::rules::ModuleImportNotAtTopOfFile,
|
||||||
|
@ -857,6 +859,7 @@ impl Rule {
|
||||||
| Rule::WhitespaceBeforeCloseBracket
|
| Rule::WhitespaceBeforeCloseBracket
|
||||||
| Rule::UnexpectedSpacesAroundKeywordParameterEquals
|
| Rule::UnexpectedSpacesAroundKeywordParameterEquals
|
||||||
| Rule::MissingWhitespaceAroundParameterEquals
|
| Rule::MissingWhitespaceAroundParameterEquals
|
||||||
|
| Rule::WhitespaceBeforeParameters
|
||||||
| Rule::WhitespaceBeforePunctuation => &LintSource::LogicalLines,
|
| Rule::WhitespaceBeforePunctuation => &LintSource::LogicalLines,
|
||||||
_ => &LintSource::Ast,
|
_ => &LintSource::Ast,
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,3 +156,7 @@ pub fn is_op_token(token: &Tok) -> bool {
|
||||||
| Tok::Colon
|
| Tok::Colon
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_soft_keyword_token(token: &Tok) -> bool {
|
||||||
|
matches!(token, Tok::Match | Tok::Case)
|
||||||
|
}
|
||||||
|
|
|
@ -101,6 +101,7 @@ mod tests {
|
||||||
#[test_case(Rule::WhitespaceAfterOpenBracket, Path::new("E20.py"))]
|
#[test_case(Rule::WhitespaceAfterOpenBracket, Path::new("E20.py"))]
|
||||||
#[test_case(Rule::WhitespaceBeforeCloseBracket, Path::new("E20.py"))]
|
#[test_case(Rule::WhitespaceBeforeCloseBracket, Path::new("E20.py"))]
|
||||||
#[test_case(Rule::WhitespaceBeforePunctuation, Path::new("E20.py"))]
|
#[test_case(Rule::WhitespaceBeforePunctuation, Path::new("E20.py"))]
|
||||||
|
#[test_case(Rule::WhitespaceBeforeParameters, Path::new("E21.py"))]
|
||||||
#[test_case(
|
#[test_case(
|
||||||
Rule::UnexpectedSpacesAroundKeywordParameterEquals,
|
Rule::UnexpectedSpacesAroundKeywordParameterEquals,
|
||||||
Path::new("E25.py")
|
Path::new("E25.py")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code, unused_imports, unused_variables)]
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code, unused_imports, unused_variables)]
|
||||||
|
|
||||||
use ruff_macros::{define_violation, derive_message_formats};
|
use ruff_macros::{define_violation, derive_message_formats};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#![allow(dead_code, unused_imports)]
|
#![allow(dead_code, unused_imports, unused_variables)]
|
||||||
|
|
||||||
use rustpython_parser::ast::Location;
|
use rustpython_parser::ast::Location;
|
||||||
use rustpython_parser::Tok;
|
use rustpython_parser::Tok;
|
||||||
|
|
|
@ -55,6 +55,8 @@ pub use whitespace_around_named_parameter_equals::{
|
||||||
UnexpectedSpacesAroundKeywordParameterEquals,
|
UnexpectedSpacesAroundKeywordParameterEquals,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use whitespace_before_parameters::{whitespace_before_parameters, WhitespaceBeforeParameters};
|
||||||
|
|
||||||
mod ambiguous_class_name;
|
mod ambiguous_class_name;
|
||||||
mod ambiguous_function_name;
|
mod ambiguous_function_name;
|
||||||
mod ambiguous_variable_name;
|
mod ambiguous_variable_name;
|
||||||
|
@ -80,3 +82,4 @@ mod type_comparison;
|
||||||
mod whitespace_around_keywords;
|
mod whitespace_around_keywords;
|
||||||
mod whitespace_around_named_parameter_equals;
|
mod whitespace_around_named_parameter_equals;
|
||||||
mod whitespace_before_comment;
|
mod whitespace_before_comment;
|
||||||
|
mod whitespace_before_parameters;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code, unused_imports, unused_variables)]
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code, unused_imports, unused_variables)]
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code, unused_imports, unused_variables)]
|
||||||
|
|
||||||
use rustpython_parser::ast::Location;
|
use rustpython_parser::ast::Location;
|
||||||
use rustpython_parser::Tok;
|
use rustpython_parser::Tok;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code, unused_imports, unused_variables)]
|
||||||
|
|
||||||
use ruff_macros::{define_violation, derive_message_formats};
|
use ruff_macros::{define_violation, derive_message_formats};
|
||||||
use rustpython_parser::ast::Location;
|
use rustpython_parser::ast::Location;
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
#![allow(dead_code, unused_imports, unused_variables)]
|
||||||
|
|
||||||
|
use rustpython_parser::ast::Location;
|
||||||
|
use rustpython_parser::Tok;
|
||||||
|
|
||||||
|
use ruff_macros::{define_violation, derive_message_formats};
|
||||||
|
|
||||||
|
use crate::ast::types::Range;
|
||||||
|
use crate::fix::Fix;
|
||||||
|
use crate::registry::Diagnostic;
|
||||||
|
use crate::rules::pycodestyle::helpers::{is_keyword_token, is_op_token, is_soft_keyword_token};
|
||||||
|
use crate::violation::AlwaysAutofixableViolation;
|
||||||
|
|
||||||
|
define_violation!(
|
||||||
|
pub struct WhitespaceBeforeParameters {
|
||||||
|
pub bracket: String,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
impl AlwaysAutofixableViolation for WhitespaceBeforeParameters {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let WhitespaceBeforeParameters { bracket } = self;
|
||||||
|
format!("Whitespace before {bracket}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autofix_title(&self) -> String {
|
||||||
|
let WhitespaceBeforeParameters { bracket } = self;
|
||||||
|
format!("Removed whitespace before {bracket}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// E211
|
||||||
|
#[cfg(feature = "logical_lines")]
|
||||||
|
pub fn whitespace_before_parameters(
|
||||||
|
tokens: &[(Location, &Tok, Location)],
|
||||||
|
autofix: bool,
|
||||||
|
) -> Vec<Diagnostic> {
|
||||||
|
let mut diagnostics = vec![];
|
||||||
|
let (_, mut prev_token, mut prev_end) = tokens.first().unwrap();
|
||||||
|
for (idx, (start, tok, end)) in tokens.iter().enumerate() {
|
||||||
|
if is_op_token(tok)
|
||||||
|
&& (**tok == Tok::Lpar || **tok == Tok::Lsqb)
|
||||||
|
&& *start != prev_end
|
||||||
|
&& (matches!(prev_token, Tok::Name { .. })
|
||||||
|
|| matches!(prev_token, Tok::Rpar | Tok::Rsqb | Tok::Rbrace))
|
||||||
|
&& (idx < 2 || *(tokens[idx - 2].1) != Tok::Class)
|
||||||
|
&& !is_keyword_token(tok)
|
||||||
|
&& !is_soft_keyword_token(tok)
|
||||||
|
{
|
||||||
|
let start = Location::new(prev_end.row(), prev_end.column());
|
||||||
|
let end = Location::new(end.row(), end.column() - 1);
|
||||||
|
|
||||||
|
let kind: WhitespaceBeforeParameters = WhitespaceBeforeParameters {
|
||||||
|
bracket: tok.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut diagnostic = Diagnostic::new(kind, Range::new(start, end));
|
||||||
|
|
||||||
|
if autofix {
|
||||||
|
diagnostic.amend(Fix::deletion(start, end));
|
||||||
|
}
|
||||||
|
diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
prev_token = *tok;
|
||||||
|
prev_end = *end;
|
||||||
|
}
|
||||||
|
diagnostics
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "logical_lines"))]
|
||||||
|
pub fn whitespace_before_parameters(
|
||||||
|
_tokens: &[(Location, &Tok, Location)],
|
||||||
|
_autofix: bool,
|
||||||
|
) -> Vec<Diagnostic> {
|
||||||
|
vec![]
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/pycodestyle/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
- kind:
|
||||||
|
WhitespaceBeforeParameters:
|
||||||
|
bracket: "'('"
|
||||||
|
location:
|
||||||
|
row: 2
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 2
|
||||||
|
column: 5
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 2
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 2
|
||||||
|
column: 5
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
WhitespaceBeforeParameters:
|
||||||
|
bracket: "'['"
|
||||||
|
location:
|
||||||
|
row: 4
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 4
|
||||||
|
column: 5
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 4
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 4
|
||||||
|
column: 5
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
WhitespaceBeforeParameters:
|
||||||
|
bracket: "'['"
|
||||||
|
location:
|
||||||
|
row: 4
|
||||||
|
column: 19
|
||||||
|
end_location:
|
||||||
|
row: 4
|
||||||
|
column: 20
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 4
|
||||||
|
column: 19
|
||||||
|
end_location:
|
||||||
|
row: 4
|
||||||
|
column: 20
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
WhitespaceBeforeParameters:
|
||||||
|
bracket: "'['"
|
||||||
|
location:
|
||||||
|
row: 6
|
||||||
|
column: 11
|
||||||
|
end_location:
|
||||||
|
row: 6
|
||||||
|
column: 12
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 6
|
||||||
|
column: 11
|
||||||
|
end_location:
|
||||||
|
row: 6
|
||||||
|
column: 12
|
||||||
|
parent: ~
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue