Implement E241 and E242 (tab/multiple ws after commas) (#6094)

## Summary

This PR implements pycodestyle's E241 (tab after comma) and E242
(multiple whitespace after comma) lints.

These are marked as nursery rules like many other pycodestyle rules.

Refs #2402

## Test Plan

E24.py copied from pycodestyle.
This commit is contained in:
Aarni Koskela 2023-07-27 21:58:41 +03:00 committed by GitHub
parent 1418ee62f8
commit 3d54d31cd9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 151 additions and 3 deletions

View file

@ -0,0 +1,13 @@
#: E241
a = (1, 2)
#: Okay
b = (1, 20)
#: E242
a = (1, 2) # tab before 2
#: Okay
b = (1, 20) # space before 20
#: E241 E241 E241
# issue 135
more_spaces = [a, b,
ef, +h,
c, -d]

View file

@ -9,9 +9,9 @@ use ruff_source_file::Locator;
use crate::registry::{AsRule, Rule};
use crate::rules::pycodestyle::rules::logical_lines::{
extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword,
missing_whitespace_around_operator, space_around_operator, whitespace_around_keywords,
whitespace_around_named_parameter_equals, whitespace_before_comment,
whitespace_before_parameters, LogicalLines, TokenFlags,
missing_whitespace_around_operator, 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::Settings;
@ -61,6 +61,9 @@ pub(crate) fn check_logical_lines(
missing_whitespace_around_operator(&line, &mut context);
missing_whitespace(&line, should_fix_missing_whitespace, &mut context);
}
if line.flags().contains(TokenFlags::PUNCTUATION) {
space_after_comma(&line, &mut context);
}
if line
.flags()

View file

@ -84,6 +84,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pycodestyle, "E227") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundBitwiseOrShiftOperator),
(Pycodestyle, "E228") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundModuloOperator),
(Pycodestyle, "E231") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespace),
(Pycodestyle, "E241") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterComma),
(Pycodestyle, "E242") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterComma),
(Pycodestyle, "E251") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedSpacesAroundKeywordParameterEquals),
(Pycodestyle, "E252") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundParameterEquals),
(Pycodestyle, "E261") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TooFewSpacesBeforeInlineComment),

View file

@ -303,6 +303,7 @@ impl Rule {
| Rule::MissingWhitespaceAroundOperator
| Rule::MissingWhitespaceAroundParameterEquals
| Rule::MultipleLeadingHashesForBlockComment
| Rule::MultipleSpacesAfterComma
| Rule::MultipleSpacesAfterKeyword
| Rule::MultipleSpacesAfterOperator
| Rule::MultipleSpacesBeforeKeyword
@ -312,6 +313,7 @@ impl Rule {
| Rule::NoSpaceAfterBlockComment
| Rule::NoSpaceAfterInlineComment
| Rule::OverIndented
| Rule::TabAfterComma
| Rule::TabAfterKeyword
| Rule::TabAfterOperator
| Rule::TabBeforeKeyword

View file

@ -70,6 +70,7 @@ mod tests {
#[test_case(Rule::IndentationWithInvalidMultiple, Path::new("E11.py"))]
#[test_case(Rule::IndentationWithInvalidMultipleComment, Path::new("E11.py"))]
#[test_case(Rule::MultipleLeadingHashesForBlockComment, Path::new("E26.py"))]
#[test_case(Rule::MultipleSpacesAfterComma, Path::new("E24.py"))]
#[test_case(Rule::MultipleSpacesAfterKeyword, Path::new("E27.py"))]
#[test_case(Rule::MultipleSpacesAfterOperator, Path::new("E22.py"))]
#[test_case(Rule::MultipleSpacesBeforeKeyword, Path::new("E27.py"))]
@ -80,6 +81,7 @@ mod tests {
#[test_case(Rule::NoSpaceAfterBlockComment, Path::new("E26.py"))]
#[test_case(Rule::NoSpaceAfterInlineComment, Path::new("E26.py"))]
#[test_case(Rule::OverIndented, Path::new("E11.py"))]
#[test_case(Rule::TabAfterComma, Path::new("E24.py"))]
#[test_case(Rule::TabAfterKeyword, Path::new("E27.py"))]
#[test_case(Rule::TabAfterOperator, Path::new("E22.py"))]
#[test_case(Rule::TabBeforeKeyword, Path::new("E27.py"))]

View file

@ -120,6 +120,57 @@ impl Violation for MultipleSpacesAfterOperator {
}
}
/// ## What it does
/// Checks for extraneous tabs after a comma.
///
/// ## Why is this bad?
/// Commas should be followed by one space, never tabs.
///
/// ## Example
/// ```python
/// a = 4,\t5
/// ```
///
/// Use instead:
/// ```python
/// a = 4, 3
/// ```
///
#[violation]
pub struct TabAfterComma;
impl Violation for TabAfterComma {
#[derive_message_formats]
fn message(&self) -> String {
format!("Tab after comma")
}
}
/// ## What it does
/// Checks for extraneous whitespace after a comma.
///
/// ## Why is this bad?
/// According to the `black` code style, commas should be followed by a single space.
///
/// ## Example
/// ```python
/// a = 4, 5
/// ```
///
/// Use instead:
/// ```python
/// a = 4, 5
/// ```
#[violation]
pub struct MultipleSpacesAfterComma;
impl Violation for MultipleSpacesAfterComma {
#[derive_message_formats]
fn message(&self) -> String {
format!("Multiple spaces after comma")
}
}
/// E221, E222, E223, E224
pub(crate) fn space_around_operator(line: &LogicalLine, context: &mut LogicalLinesContext) {
let mut after_operator = false;
@ -161,6 +212,23 @@ pub(crate) fn space_around_operator(line: &LogicalLine, context: &mut LogicalLin
}
}
/// E241, E242
pub(crate) fn space_after_comma(line: &LogicalLine, context: &mut LogicalLinesContext) {
for token in line.tokens() {
if matches!(token.kind(), TokenKind::Comma) {
match line.trailing_whitespace(token) {
(Whitespace::Tab, len) => {
context.push(TabAfterComma, TextRange::at(token.end(), len));
}
(Whitespace::Many, len) => {
context.push(MultipleSpacesAfterComma, TextRange::at(token.end(), len));
}
_ => {}
}
}
}
}
const fn is_operator_token(token: TokenKind) -> bool {
matches!(
token,

View file

@ -0,0 +1,40 @@
---
source: crates/ruff/src/rules/pycodestyle/mod.rs
---
E24.py:2:8: E241 Multiple spaces after comma
|
1 | #: E241
2 | a = (1, 2)
| ^^ E241
3 | #: Okay
4 | b = (1, 20)
|
E24.py:11:18: E241 Multiple spaces after comma
|
9 | #: E241 E241 E241
10 | # issue 135
11 | more_spaces = [a, b,
| ^^^^ E241
12 | ef, +h,
13 | c, -d]
|
E24.py:12:19: E241 Multiple spaces after comma
|
10 | # issue 135
11 | more_spaces = [a, b,
12 | ef, +h,
| ^^ E241
13 | c, -d]
|
E24.py:13:18: E241 Multiple spaces after comma
|
11 | more_spaces = [a, b,
12 | ef, +h,
13 | c, -d]
| ^^^ E241
|

View file

@ -0,0 +1,14 @@
---
source: crates/ruff/src/rules/pycodestyle/mod.rs
---
E24.py:6:8: E242 Tab after comma
|
4 | b = (1, 20)
5 | #: E242
6 | a = (1, 2) # tab before 2
| ^ E242
7 | #: Okay
8 | b = (1, 20) # space before 20
|

2
ruff.schema.json generated
View file

@ -1869,6 +1869,8 @@
"E227",
"E228",
"E231",
"E241",
"E242",
"E251",
"E252",
"E261",

View file

@ -43,6 +43,7 @@ KNOWN_FORMATTING_VIOLATIONS = [
"missing-whitespace-around-operator",
"multi-line-implicit-string-concatenation",
"multiple-leading-hashes-for-block-comment",
"multiple-spaces-after-comma",
"multiple-spaces-after-keyword",
"multiple-spaces-after-operator",
"multiple-spaces-before-keyword",
@ -81,6 +82,7 @@ KNOWN_PARSE_ERRORS = [
"missing-newline-at-end-of-file",
"mixed-spaces-and-tabs",
"no-indented-block",
"tab-after-comma",
"tab-after-keyword",
"tab-after-operator",
"tab-before-keyword",