mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 04:19:18 +00:00
[pycodestyle
] Implement redundant-backslash
(E502
) (#10292)
## Summary Implements the [redundant-backslash](https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes) rule (E502) from pycodestyle. ## Test Plan New fixture has been added Part of #2402
This commit is contained in:
parent
fc7139d9a5
commit
c746912b9e
12 changed files with 475 additions and 4 deletions
88
crates/ruff_linter/resources/test/fixtures/pycodestyle/E502.py
vendored
Normal file
88
crates/ruff_linter/resources/test/fixtures/pycodestyle/E502.py
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
a = 2 + 2
|
||||
|
||||
a = (2 + 2)
|
||||
|
||||
a = 2 + \
|
||||
3 \
|
||||
+ 4
|
||||
|
||||
a = (3 -\
|
||||
2 + \
|
||||
7)
|
||||
|
||||
z = 5 + \
|
||||
(3 -\
|
||||
2 + \
|
||||
7) + \
|
||||
4
|
||||
|
||||
b = [2 +
|
||||
2]
|
||||
|
||||
b = [
|
||||
2 + 4 + 5 + \
|
||||
44 \
|
||||
- 5
|
||||
]
|
||||
|
||||
c = (True and
|
||||
False \
|
||||
or False \
|
||||
and True \
|
||||
)
|
||||
|
||||
c = (True and
|
||||
False)
|
||||
|
||||
d = True and \
|
||||
False or \
|
||||
False \
|
||||
and not True
|
||||
|
||||
|
||||
s = {
|
||||
'x': 2 + \
|
||||
2
|
||||
}
|
||||
|
||||
|
||||
s = {
|
||||
'x': 2 +
|
||||
2
|
||||
}
|
||||
|
||||
|
||||
x = {2 + 4 \
|
||||
+ 3}
|
||||
|
||||
y = (
|
||||
2 + 2 # \
|
||||
+ 3 # \
|
||||
+ 4 \
|
||||
+ 3
|
||||
)
|
||||
|
||||
|
||||
x = """
|
||||
(\\
|
||||
)
|
||||
"""
|
||||
|
||||
|
||||
("""hello \
|
||||
""")
|
||||
|
||||
("hello \
|
||||
")
|
||||
|
||||
|
||||
x = "abc" \
|
||||
"xyz"
|
||||
|
||||
x = ("abc" \
|
||||
"xyz")
|
||||
|
||||
|
||||
def foo():
|
||||
x = (a + \
|
||||
2)
|
|
@ -1,6 +1,7 @@
|
|||
use crate::line_width::IndentWidth;
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::TokenKind;
|
||||
use ruff_source_file::Locator;
|
||||
|
@ -9,8 +10,8 @@ use ruff_text_size::{Ranged, TextRange};
|
|||
use crate::registry::AsRule;
|
||||
use crate::rules::pycodestyle::rules::logical_lines::{
|
||||
extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword,
|
||||
missing_whitespace_around_operator, space_after_comma, space_around_operator,
|
||||
whitespace_around_keywords, whitespace_around_named_parameter_equals,
|
||||
missing_whitespace_around_operator, redundant_backslash, space_after_comma,
|
||||
space_around_operator, whitespace_around_keywords, whitespace_around_named_parameter_equals,
|
||||
whitespace_before_comment, whitespace_before_parameters, LogicalLines, TokenFlags,
|
||||
};
|
||||
use crate::settings::LinterSettings;
|
||||
|
@ -35,6 +36,7 @@ pub(crate) fn expand_indent(line: &str, indent_width: IndentWidth) -> usize {
|
|||
pub(crate) fn check_logical_lines(
|
||||
tokens: &[LexResult],
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
stylist: &Stylist,
|
||||
settings: &LinterSettings,
|
||||
) -> Vec<Diagnostic> {
|
||||
|
@ -73,6 +75,7 @@ pub(crate) fn check_logical_lines(
|
|||
|
||||
if line.flags().contains(TokenFlags::BRACKET) {
|
||||
whitespace_before_parameters(&line, &mut context);
|
||||
redundant_backslash(&line, locator, indexer, &mut context);
|
||||
}
|
||||
|
||||
// Extract the indentation level.
|
||||
|
|
|
@ -146,6 +146,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||
(Pycodestyle, "E401") => (RuleGroup::Stable, rules::pycodestyle::rules::MultipleImportsOnOneLine),
|
||||
(Pycodestyle, "E402") => (RuleGroup::Stable, rules::pycodestyle::rules::ModuleImportNotAtTopOfFile),
|
||||
(Pycodestyle, "E501") => (RuleGroup::Stable, rules::pycodestyle::rules::LineTooLong),
|
||||
(Pycodestyle, "E502") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::RedundantBackslash),
|
||||
(Pycodestyle, "E701") => (RuleGroup::Stable, rules::pycodestyle::rules::MultipleStatementsOnOneLineColon),
|
||||
(Pycodestyle, "E702") => (RuleGroup::Stable, rules::pycodestyle::rules::MultipleStatementsOnOneLineSemicolon),
|
||||
(Pycodestyle, "E703") => (RuleGroup::Stable, rules::pycodestyle::rules::UselessSemicolon),
|
||||
|
|
|
@ -132,7 +132,7 @@ pub fn check_path(
|
|||
.any(|rule_code| rule_code.lint_source().is_logical_lines())
|
||||
{
|
||||
diagnostics.extend(crate::checkers::logical_lines::check_logical_lines(
|
||||
&tokens, locator, stylist, settings,
|
||||
&tokens, locator, indexer, stylist, settings,
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -327,6 +327,7 @@ impl Rule {
|
|||
| Rule::NoSpaceAfterBlockComment
|
||||
| Rule::NoSpaceAfterInlineComment
|
||||
| Rule::OverIndented
|
||||
| Rule::RedundantBackslash
|
||||
| Rule::TabAfterComma
|
||||
| Rule::TabAfterKeyword
|
||||
| Rule::TabAfterOperator
|
||||
|
|
|
@ -71,6 +71,7 @@ mod tests {
|
|||
#[test_case(Rule::IsLiteral, Path::new("constant_literals.py"))]
|
||||
#[test_case(Rule::TypeComparison, Path::new("E721.py"))]
|
||||
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_2.py"))]
|
||||
#[test_case(Rule::RedundantBackslash, Path::new("E502.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
|
|
|
@ -3,6 +3,7 @@ pub(crate) use indentation::*;
|
|||
pub(crate) use missing_whitespace::*;
|
||||
pub(crate) use missing_whitespace_after_keyword::*;
|
||||
pub(crate) use missing_whitespace_around_operator::*;
|
||||
pub(crate) use redundant_backslash::*;
|
||||
pub(crate) use space_around_operator::*;
|
||||
pub(crate) use whitespace_around_keywords::*;
|
||||
pub(crate) use whitespace_around_named_parameter_equals::*;
|
||||
|
@ -25,6 +26,7 @@ mod indentation;
|
|||
mod missing_whitespace;
|
||||
mod missing_whitespace_after_keyword;
|
||||
mod missing_whitespace_around_operator;
|
||||
mod redundant_backslash;
|
||||
mod space_around_operator;
|
||||
mod whitespace_around_keywords;
|
||||
mod whitespace_around_named_parameter_equals;
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::TokenKind;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::checkers::logical_lines::LogicalLinesContext;
|
||||
|
||||
use super::LogicalLine;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for redundant backslashes between brackets.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Explicit line joins using a backslash are redundant between brackets.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// x = (2 + \
|
||||
/// 2)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// x = (2 +
|
||||
/// 2)
|
||||
/// ```
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#maximum-line-length
|
||||
#[violation]
|
||||
pub struct RedundantBackslash;
|
||||
|
||||
impl AlwaysFixableViolation for RedundantBackslash {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Redundant backslash")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Remove redundant backslash".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// E502
|
||||
pub(crate) fn redundant_backslash(
|
||||
line: &LogicalLine,
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
context: &mut LogicalLinesContext,
|
||||
) {
|
||||
let mut parens = 0;
|
||||
let continuation_lines = indexer.continuation_line_starts();
|
||||
let mut start_index = 0;
|
||||
|
||||
for token in line.tokens() {
|
||||
match token.kind() {
|
||||
TokenKind::Lpar | TokenKind::Lsqb | TokenKind::Lbrace => {
|
||||
if parens == 0 {
|
||||
let start = locator.line_start(token.start());
|
||||
start_index = continuation_lines
|
||||
.binary_search(&start)
|
||||
.map_or_else(|err_index| err_index, |ok_index| ok_index);
|
||||
}
|
||||
parens += 1;
|
||||
}
|
||||
TokenKind::Rpar | TokenKind::Rsqb | TokenKind::Rbrace => {
|
||||
parens -= 1;
|
||||
if parens == 0 {
|
||||
let end = locator.line_start(token.start());
|
||||
let end_index = continuation_lines
|
||||
.binary_search(&end)
|
||||
.map_or_else(|err_index| err_index, |ok_index| ok_index);
|
||||
for continuation_line in &continuation_lines[start_index..end_index] {
|
||||
let backslash_end = locator.line_end(*continuation_line);
|
||||
let backslash_start = backslash_end - TextSize::new(1);
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
RedundantBackslash,
|
||||
TextRange::new(backslash_start, backslash_end),
|
||||
);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::deletion(
|
||||
backslash_start,
|
||||
backslash_end,
|
||||
)));
|
||||
context.push_diagnostic(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,281 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E502.py:9:9: E502 [*] Redundant backslash
|
||||
|
|
||||
7 | + 4
|
||||
8 |
|
||||
9 | a = (3 -\
|
||||
| ^ E502
|
||||
10 | 2 + \
|
||||
11 | 7)
|
||||
|
|
||||
= help: Remove redundant backslash
|
||||
|
||||
ℹ Safe fix
|
||||
6 6 | 3 \
|
||||
7 7 | + 4
|
||||
8 8 |
|
||||
9 |-a = (3 -\
|
||||
9 |+a = (3 -
|
||||
10 10 | 2 + \
|
||||
11 11 | 7)
|
||||
12 12 |
|
||||
|
||||
E502.py:10:11: E502 [*] Redundant backslash
|
||||
|
|
||||
9 | a = (3 -\
|
||||
10 | 2 + \
|
||||
| ^ E502
|
||||
11 | 7)
|
||||
|
|
||||
= help: Remove redundant backslash
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | + 4
|
||||
8 8 |
|
||||
9 9 | a = (3 -\
|
||||
10 |- 2 + \
|
||||
10 |+ 2 +
|
||||
11 11 | 7)
|
||||
12 12 |
|
||||
13 13 | z = 5 + \
|
||||
|
||||
E502.py:14:9: E502 [*] Redundant backslash
|
||||
|
|
||||
13 | z = 5 + \
|
||||
14 | (3 -\
|
||||
| ^ E502
|
||||
15 | 2 + \
|
||||
16 | 7) + \
|
||||
|
|
||||
= help: Remove redundant backslash
|
||||
|
||||
ℹ Safe fix
|
||||
11 11 | 7)
|
||||
12 12 |
|
||||
13 13 | z = 5 + \
|
||||
14 |- (3 -\
|
||||
14 |+ (3 -
|
||||
15 15 | 2 + \
|
||||
16 16 | 7) + \
|
||||
17 17 | 4
|
||||
|
||||
E502.py:15:11: E502 [*] Redundant backslash
|
||||
|
|
||||
13 | z = 5 + \
|
||||
14 | (3 -\
|
||||
15 | 2 + \
|
||||
| ^ E502
|
||||
16 | 7) + \
|
||||
17 | 4
|
||||
|
|
||||
= help: Remove redundant backslash
|
||||
|
||||
ℹ Safe fix
|
||||
12 12 |
|
||||
13 13 | z = 5 + \
|
||||
14 14 | (3 -\
|
||||
15 |- 2 + \
|
||||
15 |+ 2 +
|
||||
16 16 | 7) + \
|
||||
17 17 | 4
|
||||
18 18 |
|
||||
|
||||
E502.py:23:17: E502 [*] Redundant backslash
|
||||
|
|
||||
22 | b = [
|
||||
23 | 2 + 4 + 5 + \
|
||||
| ^ E502
|
||||
24 | 44 \
|
||||
25 | - 5
|
||||
|
|
||||
= help: Remove redundant backslash
|
||||
|
||||
ℹ Safe fix
|
||||
20 20 | 2]
|
||||
21 21 |
|
||||
22 22 | b = [
|
||||
23 |- 2 + 4 + 5 + \
|
||||
23 |+ 2 + 4 + 5 +
|
||||
24 24 | 44 \
|
||||
25 25 | - 5
|
||||
26 26 | ]
|
||||
|
||||
E502.py:24:8: E502 [*] Redundant backslash
|
||||
|
|
||||
22 | b = [
|
||||
23 | 2 + 4 + 5 + \
|
||||
24 | 44 \
|
||||
| ^ E502
|
||||
25 | - 5
|
||||
26 | ]
|
||||
|
|
||||
= help: Remove redundant backslash
|
||||
|
||||
ℹ Safe fix
|
||||
21 21 |
|
||||
22 22 | b = [
|
||||
23 23 | 2 + 4 + 5 + \
|
||||
24 |- 44 \
|
||||
24 |+ 44
|
||||
25 25 | - 5
|
||||
26 26 | ]
|
||||
27 27 |
|
||||
|
||||
E502.py:29:11: E502 [*] Redundant backslash
|
||||
|
|
||||
28 | c = (True and
|
||||
29 | False \
|
||||
| ^ E502
|
||||
30 | or False \
|
||||
31 | and True \
|
||||
|
|
||||
= help: Remove redundant backslash
|
||||
|
||||
ℹ Safe fix
|
||||
26 26 | ]
|
||||
27 27 |
|
||||
28 28 | c = (True and
|
||||
29 |- False \
|
||||
29 |+ False
|
||||
30 30 | or False \
|
||||
31 31 | and True \
|
||||
32 32 | )
|
||||
|
||||
E502.py:30:14: E502 [*] Redundant backslash
|
||||
|
|
||||
28 | c = (True and
|
||||
29 | False \
|
||||
30 | or False \
|
||||
| ^ E502
|
||||
31 | and True \
|
||||
32 | )
|
||||
|
|
||||
= help: Remove redundant backslash
|
||||
|
||||
ℹ Safe fix
|
||||
27 27 |
|
||||
28 28 | c = (True and
|
||||
29 29 | False \
|
||||
30 |- or False \
|
||||
30 |+ or False
|
||||
31 31 | and True \
|
||||
32 32 | )
|
||||
33 33 |
|
||||
|
||||
E502.py:31:14: E502 [*] Redundant backslash
|
||||
|
|
||||
29 | False \
|
||||
30 | or False \
|
||||
31 | and True \
|
||||
| ^ E502
|
||||
32 | )
|
||||
|
|
||||
= help: Remove redundant backslash
|
||||
|
||||
ℹ Safe fix
|
||||
28 28 | c = (True and
|
||||
29 29 | False \
|
||||
30 30 | or False \
|
||||
31 |- and True \
|
||||
31 |+ and True
|
||||
32 32 | )
|
||||
33 33 |
|
||||
34 34 | c = (True and
|
||||
|
||||
E502.py:44:14: E502 [*] Redundant backslash
|
||||
|
|
||||
43 | s = {
|
||||
44 | 'x': 2 + \
|
||||
| ^ E502
|
||||
45 | 2
|
||||
46 | }
|
||||
|
|
||||
= help: Remove redundant backslash
|
||||
|
||||
ℹ Safe fix
|
||||
41 41 |
|
||||
42 42 |
|
||||
43 43 | s = {
|
||||
44 |- 'x': 2 + \
|
||||
44 |+ 'x': 2 +
|
||||
45 45 | 2
|
||||
46 46 | }
|
||||
47 47 |
|
||||
|
||||
E502.py:55:12: E502 [*] Redundant backslash
|
||||
|
|
||||
55 | x = {2 + 4 \
|
||||
| ^ E502
|
||||
56 | + 3}
|
||||
|
|
||||
= help: Remove redundant backslash
|
||||
|
||||
ℹ Safe fix
|
||||
52 52 | }
|
||||
53 53 |
|
||||
54 54 |
|
||||
55 |-x = {2 + 4 \
|
||||
55 |+x = {2 + 4
|
||||
56 56 | + 3}
|
||||
57 57 |
|
||||
58 58 | y = (
|
||||
|
||||
E502.py:61:9: E502 [*] Redundant backslash
|
||||
|
|
||||
59 | 2 + 2 # \
|
||||
60 | + 3 # \
|
||||
61 | + 4 \
|
||||
| ^ E502
|
||||
62 | + 3
|
||||
63 | )
|
||||
|
|
||||
= help: Remove redundant backslash
|
||||
|
||||
ℹ Safe fix
|
||||
58 58 | y = (
|
||||
59 59 | 2 + 2 # \
|
||||
60 60 | + 3 # \
|
||||
61 |- + 4 \
|
||||
61 |+ + 4
|
||||
62 62 | + 3
|
||||
63 63 | )
|
||||
64 64 |
|
||||
|
||||
E502.py:82:12: E502 [*] Redundant backslash
|
||||
|
|
||||
80 | "xyz"
|
||||
81 |
|
||||
82 | x = ("abc" \
|
||||
| ^ E502
|
||||
83 | "xyz")
|
||||
|
|
||||
= help: Remove redundant backslash
|
||||
|
||||
ℹ Safe fix
|
||||
79 79 | x = "abc" \
|
||||
80 80 | "xyz"
|
||||
81 81 |
|
||||
82 |-x = ("abc" \
|
||||
82 |+x = ("abc"
|
||||
83 83 | "xyz")
|
||||
84 84 |
|
||||
85 85 |
|
||||
|
||||
E502.py:87:14: E502 [*] Redundant backslash
|
||||
|
|
||||
86 | def foo():
|
||||
87 | x = (a + \
|
||||
| ^ E502
|
||||
88 | 2)
|
||||
|
|
||||
= help: Remove redundant backslash
|
||||
|
||||
ℹ Safe fix
|
||||
84 84 |
|
||||
85 85 |
|
||||
86 86 | def foo():
|
||||
87 |- x = (a + \
|
||||
87 |+ x = (a +
|
||||
88 88 | 2)
|
|
@ -380,7 +380,7 @@ per-file-ignores = { "__init__.py" = ["F401"] }
|
|||
assert!(result.is_err());
|
||||
let result = PatternPrefixPair::from_str("**/bar:E501");
|
||||
assert!(result.is_ok());
|
||||
let result = PatternPrefixPair::from_str("bar:E502");
|
||||
let result = PatternPrefixPair::from_str("bar:E503");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
|
1
ruff.schema.json
generated
1
ruff.schema.json
generated
|
@ -2867,6 +2867,7 @@
|
|||
"E5",
|
||||
"E50",
|
||||
"E501",
|
||||
"E502",
|
||||
"E7",
|
||||
"E70",
|
||||
"E701",
|
||||
|
|
|
@ -69,6 +69,7 @@ KNOWN_FORMATTING_VIOLATIONS = [
|
|||
"over-indented",
|
||||
"pass-statement-stub-body",
|
||||
"prohibited-trailing-comma",
|
||||
"redundant-backslash",
|
||||
"shebang-leading-whitespace",
|
||||
"surrounding-whitespace",
|
||||
"tab-indentation",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue