Skip to content

Commit

Permalink
Add check for W292 (#339)
Browse files Browse the repository at this point in the history
  • Loading branch information
cnpryer authored Oct 8, 2022
1 parent 95dfc61 commit 473675f
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 9 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| E743 | AmbiguousFunctionName | Ambiguous function name: `...` | ✅ | |
| E902 | IOError | IOError: `...` | ✅ | |
| E999 | SyntaxError | SyntaxError: `...` | ✅ | |
| W292 | NoNewLineAtEndOfFile | No newline at end of file | ✅ | |
| F401 | UnusedImport | `...` imported but unused | ✅ | 🛠 |
| F402 | ImportShadowedByLoopVar | Import `...` from line 1 shadowed by loop variable | ✅ | |
| F403 | ImportStarUsed | `from ... import *` used; unable to detect undefined names | ✅ | |
Expand Down Expand Up @@ -291,6 +292,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| U005 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
| M001 | UnusedNOQA | Unused `noqa` directive | | 🛠 |


## Integrations

### PyCharm
Expand Down
2 changes: 2 additions & 0 deletions resources/test/fixtures/W292_0.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def fn() -> None:
pass
2 changes: 2 additions & 0 deletions resources/test/fixtures/W292_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def fn() -> None:
pass # noqa: W292
40 changes: 40 additions & 0 deletions src/check_lines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,46 @@ pub fn check_lines(
}
}

// Enforce newlines at end of files.
if settings.enabled.contains(&CheckCode::W292) {
// If the file terminates with a newline, the last line should be an empty string slice.
if let Some(line) = lines.last() {
if !line.is_empty() {
let lineno = lines.len() - 1;
let noqa_lineno = noqa_line_for
.get(lineno)
.map(|lineno| lineno - 1)
.unwrap_or(lineno);

let noqa = noqa_directives
.entry(noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));

let check = Check::new(
CheckKind::NoNewLineAtEndOfFile,
Range {
location: Location::new(lines.len(), line.len() + 1),
end_location: Location::new(lines.len(), line.len() + 1),
},
);

match noqa {
(Directive::All(_, _), matches) => {
matches.push(check.kind.code().as_str());
}
(Directive::Codes(_, _, codes), matches) => {
if codes.contains(&check.kind.code().as_str()) {
matches.push(check.kind.code().as_str());
} else {
line_checks.push(check);
}
}
(Directive::None, _) => line_checks.push(check),
}
}
}
}

// Enforce that the noqa directive was actually used.
if enforce_noqa {
for (row, (directive, matches)) in noqa_directives {
Expand Down
37 changes: 28 additions & 9 deletions src/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize};

use crate::ast::types::Range;

pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
// pycodestyle
pub const DEFAULT_CHECK_CODES: [CheckCode; 43] = [
// pycodestyle errors
CheckCode::E402,
CheckCode::E501,
CheckCode::E711,
Expand All @@ -24,6 +24,8 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
CheckCode::E743,
CheckCode::E902,
CheckCode::E999,
// pycodestyle warnings
CheckCode::W292,
// pyflakes
CheckCode::F401,
CheckCode::F402,
Expand Down Expand Up @@ -55,8 +57,8 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
CheckCode::F901,
];

pub const ALL_CHECK_CODES: [CheckCode; 58] = [
// pycodestyle
pub const ALL_CHECK_CODES: [CheckCode; 59] = [
// pycodestyle errors
CheckCode::E402,
CheckCode::E501,
CheckCode::E711,
Expand All @@ -71,6 +73,8 @@ pub const ALL_CHECK_CODES: [CheckCode; 58] = [
CheckCode::E743,
CheckCode::E902,
CheckCode::E999,
// pycodestyle warnings
CheckCode::W292,
// pyflakes
CheckCode::F401,
CheckCode::F402,
Expand Down Expand Up @@ -126,7 +130,7 @@ pub const ALL_CHECK_CODES: [CheckCode; 58] = [

#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Hash, PartialOrd, Ord)]
pub enum CheckCode {
// pycodestyle
// pycodestyle errors
E402,
E501,
E711,
Expand All @@ -141,6 +145,8 @@ pub enum CheckCode {
E743,
E902,
E999,
// pycodestyle warnings
W292,
// pyflakes
F401,
F402,
Expand Down Expand Up @@ -199,7 +205,7 @@ impl FromStr for CheckCode {

fn from_str(s: &str) -> Result<Self> {
match s {
// pycodestyle
// pycodestyle errors
"E402" => Ok(CheckCode::E402),
"E501" => Ok(CheckCode::E501),
"E711" => Ok(CheckCode::E711),
Expand All @@ -214,6 +220,8 @@ impl FromStr for CheckCode {
"E743" => Ok(CheckCode::E743),
"E902" => Ok(CheckCode::E902),
"E999" => Ok(CheckCode::E999),
// pycodestyle warnings
"W292" => Ok(CheckCode::W292),
// pyflakes
"F401" => Ok(CheckCode::F401),
"F402" => Ok(CheckCode::F402),
Expand Down Expand Up @@ -271,7 +279,7 @@ impl FromStr for CheckCode {
impl CheckCode {
pub fn as_str(&self) -> &str {
match self {
// pycodestyle
// pycodestyle errors
CheckCode::E402 => "E402",
CheckCode::E501 => "E501",
CheckCode::E711 => "E711",
Expand All @@ -286,6 +294,8 @@ impl CheckCode {
CheckCode::E743 => "E743",
CheckCode::E902 => "E902",
CheckCode::E999 => "E999",
// pycodestyle warnings
CheckCode::W292 => "W292",
// pyflakes
CheckCode::F401 => "F401",
CheckCode::F402 => "F402",
Expand Down Expand Up @@ -352,7 +362,7 @@ impl CheckCode {
/// A placeholder representation of the CheckKind for the check.
pub fn kind(&self) -> CheckKind {
match self {
// pycodestyle
// pycodestyle errors
CheckCode::E402 => CheckKind::ModuleImportNotAtTopOfFile,
CheckCode::E501 => CheckKind::LineTooLong(89, 88),
CheckCode::E711 => CheckKind::NoneComparison(RejectedCmpop::Eq),
Expand All @@ -367,6 +377,8 @@ impl CheckCode {
CheckCode::E743 => CheckKind::AmbiguousFunctionName("...".to_string()),
CheckCode::E902 => CheckKind::IOError("IOError: `...`".to_string()),
CheckCode::E999 => CheckKind::SyntaxError("`...`".to_string()),
// pycodestyle warnings
CheckCode::W292 => CheckKind::NoNewLineAtEndOfFile,
// pyflakes
CheckCode::F401 => CheckKind::UnusedImport(vec!["...".to_string()]),
CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
Expand Down Expand Up @@ -481,6 +493,8 @@ pub enum CheckKind {
UnusedImport(Vec<String>),
UnusedVariable(String),
YieldOutsideFunction,
// More style
NoNewLineAtEndOfFile,
// flake8-builtin
BuiltinVariableShadowing(String),
BuiltinArgumentShadowing(String),
Expand Down Expand Up @@ -551,6 +565,8 @@ impl CheckKind {
CheckKind::UnusedImport(_) => "UnusedImport",
CheckKind::UnusedVariable(_) => "UnusedVariable",
CheckKind::YieldOutsideFunction => "YieldOutsideFunction",
// More style
CheckKind::NoNewLineAtEndOfFile => "NoNewLineAtEndOfFile",
// flake8-builtins
CheckKind::BuiltinVariableShadowing(_) => "BuiltinVariableShadowing",
CheckKind::BuiltinArgumentShadowing(_) => "BuiltinArgumentShadowing",
Expand Down Expand Up @@ -621,6 +637,8 @@ impl CheckKind {
CheckKind::UnusedImport(_) => &CheckCode::F401,
CheckKind::UnusedVariable(_) => &CheckCode::F841,
CheckKind::YieldOutsideFunction => &CheckCode::F704,
// More style
CheckKind::NoNewLineAtEndOfFile => &CheckCode::W292,
// flake8-builtins
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
Expand Down Expand Up @@ -776,7 +794,8 @@ impl CheckKind {
CheckKind::YieldOutsideFunction => {
"`yield` or `yield from` statement outside of a function/method".to_string()
}

// More style
CheckKind::NoNewLineAtEndOfFile => "No newline at end of file".to_string(),
// flake8-builtins
CheckKind::BuiltinVariableShadowing(name) => {
format!("Variable `{name}` is shadowing a python builtin")
Expand Down
24 changes: 24 additions & 0 deletions src/linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,30 @@ mod tests {
Ok(())
}

#[test]
fn w292_0() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/W292_0.py"),
&settings::Settings::for_rule(CheckCode::W292),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}

#[test]
fn w292_1() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/W292_1.py"),
&settings::Settings::for_rule(CheckCode::W292),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}

#[test]
fn f401() -> Result<()> {
let mut checks = check_path(
Expand Down
13 changes: 13 additions & 0 deletions src/snapshots/ruff__linter__tests__w292.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
source: src/linter.rs
expression: checks
---
- kind: NoNewLineAtEndOfFile
location:
row: 2
column: 9
end_location:
row: 2
column: 9
fix: ~

13 changes: 13 additions & 0 deletions src/snapshots/ruff__linter__tests__w292_0.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
source: src/linter.rs
expression: checks
---
- kind: NoNewLineAtEndOfFile
location:
row: 2
column: 9
end_location:
row: 2
column: 9
fix: ~

6 changes: 6 additions & 0 deletions src/snapshots/ruff__linter__tests__w292_1.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
source: src/linter.rs
expression: checks
---
[]

0 comments on commit 473675f

Please sign in to comment.