diff --git a/README.md b/README.md index 6bbdde75ad132..e3eb544ccdf79 100644 --- a/README.md +++ b/README.md @@ -540,7 +540,7 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI | E743 | AmbiguousFunctionName | Ambiguous function name: `...` | | | E902 | IOError | IOError: `...` | | | E999 | SyntaxError | SyntaxError: `...` | | -| W292 | NoNewLineAtEndOfFile | No newline at end of file | | +| W292 | NoNewLineAtEndOfFile | No newline at end of file | 🛠 | | W605 | InvalidEscapeSequence | Invalid escape sequence: '\c' | | ### mccabe (C90) diff --git a/resources/test/fixtures/pycodestyle/W292_2.py b/resources/test/fixtures/pycodestyle/W292_2.py index d87dddbf7f94b..9f6018aa557b0 100644 --- a/resources/test/fixtures/pycodestyle/W292_2.py +++ b/resources/test/fixtures/pycodestyle/W292_2.py @@ -1,2 +1,2 @@ def fn() -> None: - pass + print("Newline present (no W292)") diff --git a/resources/test/fixtures/pycodestyle/W292_3.py b/resources/test/fixtures/pycodestyle/W292_3.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/resources/test/fixtures/pycodestyle/W292_4.py b/resources/test/fixtures/pycodestyle/W292_4.py new file mode 100644 index 0000000000000..0519ecba6ea91 --- /dev/null +++ b/resources/test/fixtures/pycodestyle/W292_4.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/checkers/lines.rs b/src/checkers/lines.rs index 18c929e189309..b68778a82ea34 100644 --- a/src/checkers/lines.rs +++ b/src/checkers/lines.rs @@ -55,7 +55,11 @@ pub fn check_lines( } if enforce_no_newline_at_end_of_file { - if let Some(check) = no_newline_at_end_of_file(contents) { + if let Some(check) = no_newline_at_end_of_file( + contents, + matches!(autofix, flags::Autofix::Enabled) + && settings.fixable.contains(&CheckCode::W292), + ) { checks.push(check); } } diff --git a/src/checks.rs b/src/checks.rs index 0c931d73094e6..cb4e4289d68ea 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -2983,6 +2983,7 @@ impl CheckKind { | CheckKind::NoBlankLineBeforeClass(..) | CheckKind::NoBlankLineBeforeFunction(..) | CheckKind::NoBlankLinesBetweenHeaderAndContent(..) + | CheckKind::NoNewLineAtEndOfFile | CheckKind::NoOverIndentation | CheckKind::NoSurroundingWhitespace | CheckKind::NoUnderIndentation diff --git a/src/pycodestyle/checks.rs b/src/pycodestyle/checks.rs index acccc5813ae86..58e76c611871d 100644 --- a/src/pycodestyle/checks.rs +++ b/src/pycodestyle/checks.rs @@ -5,6 +5,7 @@ use rustpython_ast::{Located, Location, Stmt, StmtKind}; use rustpython_parser::ast::{Cmpop, Expr, ExprKind}; use crate::ast::types::Range; +use crate::autofix::Fix; use crate::checks::{Check, CheckKind}; use crate::source_code_locator::SourceCodeLocator; @@ -134,18 +135,24 @@ pub fn ambiguous_function_name(name: &str, location: Range) -> Option { } /// W292 -pub fn no_newline_at_end_of_file(contents: &str) -> Option { +pub fn no_newline_at_end_of_file(contents: &str, autofix: bool) -> Option { if !contents.ends_with('\n') { // Note: if `lines.last()` is `None`, then `contents` is empty (and so we don't // want to raise W292 anyway). if let Some(line) = contents.lines().last() { - return Some(Check::new( + // Both locations are at the end of the file (and thus the same). + let location = Location::new(contents.lines().count(), line.len()); + let mut check = Check::new( CheckKind::NoNewLineAtEndOfFile, Range { - location: Location::new(contents.lines().count(), line.len() + 1), - end_location: Location::new(contents.lines().count(), line.len() + 1), + location, + end_location: location, }, - )); + ); + if autofix { + check.amend(Fix::insertion("\n".to_string(), location)); + } + return Some(check); } } None diff --git a/src/pycodestyle/mod.rs b/src/pycodestyle/mod.rs index 3c9e9bebc56cf..3b3d580973da3 100644 --- a/src/pycodestyle/mod.rs +++ b/src/pycodestyle/mod.rs @@ -31,6 +31,8 @@ mod tests { #[test_case(CheckCode::W292, Path::new("W292_0.py"))] #[test_case(CheckCode::W292, Path::new("W292_1.py"))] #[test_case(CheckCode::W292, Path::new("W292_2.py"))] + #[test_case(CheckCode::W292, Path::new("W292_3.py"))] + #[test_case(CheckCode::W292, Path::new("W292_4.py"))] #[test_case(CheckCode::W605, Path::new("W605_0.py"))] #[test_case(CheckCode::W605, Path::new("W605_1.py"))] fn checks(check_code: CheckCode, path: &Path) -> Result<()> { diff --git a/src/pycodestyle/snapshots/ruff__pycodestyle__tests__W292_W292_0.py.snap b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__W292_W292_0.py.snap index 2bd3ea6397e5a..473c23813ecda 100644 --- a/src/pycodestyle/snapshots/ruff__pycodestyle__tests__W292_W292_0.py.snap +++ b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__W292_W292_0.py.snap @@ -5,9 +5,16 @@ expression: checks - kind: NoNewLineAtEndOfFile location: row: 2 - column: 9 + column: 8 end_location: row: 2 - column: 9 - fix: ~ + column: 8 + fix: + content: "\n" + location: + row: 2 + column: 8 + end_location: + row: 2 + column: 8 diff --git a/src/pycodestyle/snapshots/ruff__pycodestyle__tests__W292_W292_3.py.snap b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__W292_W292_3.py.snap new file mode 100644 index 0000000000000..803588cfbe0fc --- /dev/null +++ b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__W292_W292_3.py.snap @@ -0,0 +1,6 @@ +--- +source: src/pycodestyle/mod.rs +expression: checks +--- +[] + diff --git a/src/pycodestyle/snapshots/ruff__pycodestyle__tests__W292_W292_4.py.snap b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__W292_W292_4.py.snap new file mode 100644 index 0000000000000..860926bfb3c51 --- /dev/null +++ b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__W292_W292_4.py.snap @@ -0,0 +1,20 @@ +--- +source: src/pycodestyle/mod.rs +expression: checks +--- +- kind: NoNewLineAtEndOfFile + location: + row: 1 + column: 1 + end_location: + row: 1 + column: 1 + fix: + content: "\n" + location: + row: 1 + column: 1 + end_location: + row: 1 + column: 1 +