[pycodestyle] trailing-whitespace, blank-line-contains-whitespace (W291, W293) (#3122)

This commit is contained in:
Matt Nawara 2023-02-23 19:04:45 -05:00 committed by GitHub
parent c8c575dd43
commit 198b301baf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 225 additions and 4 deletions

View file

@ -0,0 +1,26 @@
#: Okay
# 情
#: W291:1:6
print
#: W293:2:1
class Foo(object):
bang = 12
#: W291:2:35
'''multiline
string with trailing whitespace'''
#: W291 W292 noeol
x = 1
#: W191 W292 noeol
if False:
pass # indented with tabs
#: W292:1:36 noeol
# This line doesn't have a linefeed
#: W292:1:5 E225:1:2 noeol
1+ 1
#: W292:1:27 E261:1:12 noeol
import this # no line feed
#: W292:3:22 noeol
class Test(object):
def __repr__(self):
return 'test'

View file

@ -9,6 +9,7 @@ use crate::rules::flake8_executable::rules::{
};
use crate::rules::pycodestyle::rules::{
doc_line_too_long, line_too_long, mixed_spaces_and_tabs, no_newline_at_end_of_file,
trailing_whitespace,
};
use crate::rules::pygrep_hooks::rules::{blanket_noqa, blanket_type_ignore};
use crate::rules::pylint;
@ -41,6 +42,9 @@ pub fn check_physical_lines(
let enforce_unnecessary_coding_comment = settings.rules.enabled(&Rule::UTF8EncodingDeclaration);
let enforce_mixed_spaces_and_tabs = settings.rules.enabled(&Rule::MixedSpacesAndTabs);
let enforce_bidirectional_unicode = settings.rules.enabled(&Rule::BidirectionalUnicode);
let enforce_trailing_whitespace = settings.rules.enabled(&Rule::TrailingWhitespace);
let enforce_blank_line_contains_whitespace =
settings.rules.enabled(&Rule::BlankLineContainsWhitespace);
let fix_unnecessary_coding_comment =
autofix.into() && settings.rules.should_fix(&Rule::UTF8EncodingDeclaration);
@ -139,6 +143,12 @@ pub fn check_physical_lines(
if enforce_bidirectional_unicode {
diagnostics.extend(pylint::rules::bidirectional_unicode(index, line));
}
if enforce_trailing_whitespace || enforce_blank_line_contains_whitespace {
if let Some(diagnostic) = trailing_whitespace(index, line, settings, autofix) {
diagnostics.push(diagnostic);
}
}
}
if enforce_no_newline_at_end_of_file {

View file

@ -72,7 +72,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pycodestyle, "E999") => Rule::SyntaxError,
// pycodestyle warnings
(Pycodestyle, "W291") => Rule::TrailingWhitespace,
(Pycodestyle, "W292") => Rule::NoNewLineAtEndOfFile,
(Pycodestyle, "W293") => Rule::BlankLineContainsWhitespace,
(Pycodestyle, "W505") => Rule::DocLineTooLong,
(Pycodestyle, "W605") => Rule::InvalidEscapeSequence,

View file

@ -77,7 +77,9 @@ ruff_macros::register_rules!(
rules::pycodestyle::rules::IOError,
rules::pycodestyle::rules::SyntaxError,
// pycodestyle warnings
rules::pycodestyle::rules::TrailingWhitespace,
rules::pycodestyle::rules::NoNewLineAtEndOfFile,
rules::pycodestyle::rules::BlankLineContainsWhitespace,
rules::pycodestyle::rules::DocLineTooLong,
rules::pycodestyle::rules::InvalidEscapeSequence,
// pyflakes
@ -786,7 +788,9 @@ impl Rule {
| Rule::ShebangNewline
| Rule::BidirectionalUnicode
| Rule::ShebangPython
| Rule::ShebangWhitespace => &LintSource::PhysicalLines,
| Rule::ShebangWhitespace
| Rule::TrailingWhitespace
| Rule::BlankLineContainsWhitespace => &LintSource::PhysicalLines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AmbiguousUnicodeCharacterDocstring
| Rule::AmbiguousUnicodeCharacterString

View file

@ -24,6 +24,7 @@ mod tests {
#[test_case(Rule::AmbiguousVariableName, Path::new("E741.py"))]
#[test_case(Rule::LambdaAssignment, Path::new("E731.py"))]
#[test_case(Rule::BareExcept, Path::new("E722.py"))]
#[test_case(Rule::BlankLineContainsWhitespace, Path::new("W29.py"))]
#[test_case(Rule::InvalidEscapeSequence, Path::new("W605_0.py"))]
#[test_case(Rule::InvalidEscapeSequence, Path::new("W605_1.py"))]
#[test_case(Rule::LineTooLong, Path::new("E501.py"))]
@ -41,6 +42,7 @@ mod tests {
#[test_case(Rule::NotInTest, Path::new("E713.py"))]
#[test_case(Rule::NotIsTest, Path::new("E714.py"))]
#[test_case(Rule::SyntaxError, Path::new("E999.py"))]
#[test_case(Rule::TrailingWhitespace, Path::new("W29.py"))]
#[test_case(Rule::TrueFalseComparison, Path::new("E712.py"))]
#[test_case(Rule::TypeComparison, Path::new("E721.py"))]
#[test_case(Rule::UselessSemicolon, Path::new("E70.py"))]

View file

@ -32,6 +32,9 @@ pub use space_around_operator::{
space_around_operator, MultipleSpacesAfterOperator, MultipleSpacesBeforeOperator,
TabAfterOperator, TabBeforeOperator,
};
pub use trailing_whitespace::{
trailing_whitespace, BlankLineContainsWhitespace, TrailingWhitespace,
};
pub use type_comparison::{type_comparison, TypeComparison};
pub use whitespace_around_keywords::{
whitespace_around_keywords, MultipleSpacesAfterKeyword, MultipleSpacesBeforeKeyword,
@ -60,6 +63,7 @@ mod mixed_spaces_and_tabs;
mod no_newline_at_end_of_file;
mod not_tests;
mod space_around_operator;
mod trailing_whitespace;
mod type_comparison;
mod whitespace_around_keywords;
mod whitespace_before_comment;

View file

@ -0,0 +1,75 @@
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::fix::Fix;
use crate::registry::{Diagnostic, Rule};
use crate::settings::{flags, Settings};
use crate::violation::AlwaysAutofixableViolation;
define_violation!(
pub struct TrailingWhitespace;
);
impl AlwaysAutofixableViolation for TrailingWhitespace {
#[derive_message_formats]
fn message(&self) -> String {
format!("Trailing whitespace")
}
fn autofix_title(&self) -> String {
"Remove trailing whitespace".to_string()
}
}
define_violation!(
pub struct BlankLineContainsWhitespace;
);
impl AlwaysAutofixableViolation for BlankLineContainsWhitespace {
#[derive_message_formats]
fn message(&self) -> String {
format!("Blank line contains whitespace")
}
fn autofix_title(&self) -> String {
"Remove whitespace from blank line".to_string()
}
}
/// W291, W293
pub fn trailing_whitespace(
lineno: usize,
line: &str,
settings: &Settings,
autofix: flags::Autofix,
) -> Option<Diagnostic> {
let whitespace_count = line.chars().rev().take_while(|c| c.is_whitespace()).count();
if whitespace_count > 0 {
let line_char_count = line.chars().count();
let start = Location::new(lineno + 1, line_char_count - whitespace_count);
let end = Location::new(lineno + 1, line_char_count);
if whitespace_count == line_char_count {
if settings.rules.enabled(&Rule::BlankLineContainsWhitespace) {
let mut diagnostic =
Diagnostic::new(BlankLineContainsWhitespace, Range::new(start, end));
if matches!(autofix, flags::Autofix::Enabled)
&& settings
.rules
.should_fix(&Rule::BlankLineContainsWhitespace)
{
diagnostic.amend(Fix::deletion(start, end));
}
return Some(diagnostic);
}
} else if settings.rules.enabled(&Rule::TrailingWhitespace) {
let mut diagnostic = Diagnostic::new(TrailingWhitespace, Range::new(start, end));
if matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::TrailingWhitespace)
{
diagnostic.amend(Fix::deletion(start, end));
}
return Some(diagnostic);
}
}
None
}

View file

@ -0,0 +1,56 @@
---
source: crates/ruff/src/rules/pycodestyle/mod.rs
expression: diagnostics
---
- kind:
TrailingWhitespace: ~
location:
row: 4
column: 5
end_location:
row: 4
column: 6
fix:
content: ""
location:
row: 4
column: 5
end_location:
row: 4
column: 6
parent: ~
- kind:
TrailingWhitespace: ~
location:
row: 11
column: 34
end_location:
row: 11
column: 37
fix:
content: ""
location:
row: 11
column: 34
end_location:
row: 11
column: 37
parent: ~
- kind:
TrailingWhitespace: ~
location:
row: 13
column: 5
end_location:
row: 13
column: 8
fix:
content: ""
location:
row: 13
column: 5
end_location:
row: 13
column: 8
parent: ~

View file

@ -0,0 +1,22 @@
---
source: crates/ruff/src/rules/pycodestyle/mod.rs
expression: diagnostics
---
- kind:
BlankLineContainsWhitespace: ~
location:
row: 7
column: 0
end_location:
row: 7
column: 4
fix:
content: ""
location:
row: 7
column: 0
end_location:
row: 7
column: 4
parent: ~

View file

@ -465,7 +465,9 @@ mod tests {
}]);
let expected = FxHashSet::from_iter([
Rule::TrailingWhitespace,
Rule::NoNewLineAtEndOfFile,
Rule::BlankLineContainsWhitespace,
Rule::DocLineTooLong,
Rule::InvalidEscapeSequence,
]);
@ -483,7 +485,12 @@ mod tests {
ignore: vec![codes::Pycodestyle::W292.into()],
..RuleSelection::default()
}]);
let expected = FxHashSet::from_iter([Rule::DocLineTooLong, Rule::InvalidEscapeSequence]);
let expected = FxHashSet::from_iter([
Rule::TrailingWhitespace,
Rule::BlankLineContainsWhitespace,
Rule::DocLineTooLong,
Rule::InvalidEscapeSequence,
]);
assert_eq!(actual, expected);
let actual = resolve_rules([RuleSelection {
@ -514,7 +521,9 @@ mod tests {
},
]);
let expected = FxHashSet::from_iter([
Rule::TrailingWhitespace,
Rule::NoNewLineAtEndOfFile,
Rule::BlankLineContainsWhitespace,
Rule::DocLineTooLong,
Rule::InvalidEscapeSequence,
]);
@ -549,7 +558,12 @@ mod tests {
..RuleSelection::default()
},
]);
let expected = FxHashSet::from_iter([Rule::DocLineTooLong, Rule::InvalidEscapeSequence]);
let expected = FxHashSet::from_iter([
Rule::TrailingWhitespace,
Rule::BlankLineContainsWhitespace,
Rule::DocLineTooLong,
Rule::InvalidEscapeSequence,
]);
assert_eq!(actual, expected);
let actual = resolve_rules([
@ -564,7 +578,11 @@ mod tests {
..RuleSelection::default()
},
]);
let expected = FxHashSet::from_iter([Rule::InvalidEscapeSequence]);
let expected = FxHashSet::from_iter([
Rule::TrailingWhitespace,
Rule::BlankLineContainsWhitespace,
Rule::InvalidEscapeSequence,
]);
assert_eq!(actual, expected);
}
}

2
ruff.schema.json generated
View file

@ -2100,7 +2100,9 @@
"W",
"W2",
"W29",
"W291",
"W292",
"W293",
"W5",
"W50",
"W505",