mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-24 05:25:17 +00:00
[pycodestyle] trailing-whitespace, blank-line-contains-whitespace (W291, W293) (#3122)
This commit is contained in:
parent
c8c575dd43
commit
198b301baf
11 changed files with 225 additions and 4 deletions
26
crates/ruff/resources/test/fixtures/pycodestyle/W29.py
vendored
Normal file
26
crates/ruff/resources/test/fixtures/pycodestyle/W29.py
vendored
Normal 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'
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"))]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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: ~
|
||||
|
|
@ -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: ~
|
||||
|
|
@ -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
2
ruff.schema.json
generated
|
@ -2100,7 +2100,9 @@
|
|||
"W",
|
||||
"W2",
|
||||
"W29",
|
||||
"W291",
|
||||
"W292",
|
||||
"W293",
|
||||
"W5",
|
||||
"W50",
|
||||
"W505",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue