From 78ca1d23c0c415fd84cbd2c8f0b2077a6c71e0a5 Mon Sep 17 00:00:00 2001 From: Hoel Bagard Date: Wed, 13 Mar 2024 22:08:51 +0900 Subject: [PATCH 1/3] Do not skip line check for lines before first logical line. Ignoring all lines until the first logical line does not match the behavior from pycodestyle. Fixes #10374 --- .../ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs index 4879f1e93d069..ad21ee7241cf7 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs @@ -696,9 +696,7 @@ impl<'a> BlankLinesChecker<'a> { state.class_status.update(&logical_line); state.fn_status.update(&logical_line); - if state.is_not_first_logical_line { - self.check_line(&logical_line, &state, prev_indent_length, diagnostics); - } + self.check_line(&logical_line, &state, prev_indent_length, diagnostics); match logical_line.kind { LogicalLineKind::Class => { @@ -818,6 +816,8 @@ impl<'a> BlankLinesChecker<'a> { && line.kind.is_class_function_or_decorator() // Blank lines in stub files are used to group definitions. Don't enforce blank lines. && !self.source_type.is_stub() + // Do not expect blank lines before the first logical line. + && state.is_not_first_logical_line { // E302 let mut diagnostic = Diagnostic::new( From 7d4c6b54b13764b654fee619db5c1567dc13c504 Mon Sep 17 00:00:00 2001 From: Hoel Bagard Date: Thu, 14 Mar 2024 13:32:49 +0900 Subject: [PATCH 2/3] add fixtures about the first line/beginning of the file for the E303 rule. --- .../pycodestyle/E303_first_line_comment.py | 6 ++++++ .../pycodestyle/E303_first_line_docstring.py | 6 ++++++ .../pycodestyle/E303_first_line_expression.py | 6 ++++++ .../pycodestyle/E303_first_line_statement.py | 6 ++++++ .../ruff_linter/src/rules/pycodestyle/mod.rs | 15 +++++++++++++++ ...tests__E303_E303_first_line_comment.py.snap | 18 ++++++++++++++++++ ...sts__E303_E303_first_line_docstring.py.snap | 18 ++++++++++++++++++ ...ts__E303_E303_first_line_expression.py.snap | 18 ++++++++++++++++++ ...sts__E303_E303_first_line_statement.py.snap | 18 ++++++++++++++++++ 9 files changed, 111 insertions(+) create mode 100644 crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_comment.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_docstring.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_expression.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_statement.py create mode 100644 crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_comment.py.snap create mode 100644 crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_docstring.py.snap create mode 100644 crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_expression.py.snap create mode 100644 crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_statement.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_comment.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_comment.py new file mode 100644 index 0000000000000..c26e6f3abf7ba --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_comment.py @@ -0,0 +1,6 @@ +# Test where the first line is a comment, and the rule violation follows it. + + + +def fn(): + pass diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_docstring.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_docstring.py new file mode 100644 index 0000000000000..54e94f60fe68a --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_docstring.py @@ -0,0 +1,6 @@ +"""Test where the error is after the module's docstring.""" + + + +def fn(): + pass diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_expression.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_expression.py new file mode 100644 index 0000000000000..0ee0604ca8648 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_expression.py @@ -0,0 +1,6 @@ +"Test where the first line is a comment, " + "and the rule violation follows it." + + + +def fn(): + pass diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_statement.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_statement.py new file mode 100644 index 0000000000000..25327ca1d40a4 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_statement.py @@ -0,0 +1,6 @@ +print("Test where the first line is a statement, and the rule violation follows it.") + + + +def fn(): + pass diff --git a/crates/ruff_linter/src/rules/pycodestyle/mod.rs b/crates/ruff_linter/src/rules/pycodestyle/mod.rs index 035e16e2b4fb5..8f944ac95c25f 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/mod.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/mod.rs @@ -171,6 +171,21 @@ mod tests { Ok(()) } + #[test_case(Path::new("E303_first_line_comment.py"))] + #[test_case(Path::new("E303_first_line_docstring.py"))] + #[test_case(Path::new("E303_first_line_expression.py"))] + #[test_case(Path::new("E303_first_line_statement.py"))] + fn blank_lines_first_line(path: &Path) -> Result<()> { + let rule_code = Rule::TooManyBlankLines; + let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); + let diagnostics = test_path( + Path::new("pycodestyle").join(path).as_path(), + &settings::LinterSettings::for_rule(rule_code), + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } + #[test_case(Rule::BlankLineBetweenMethods, Path::new("E30.py"))] #[test_case(Rule::BlankLinesTopLevel, Path::new("E30.py"))] #[test_case(Rule::TooManyBlankLines, Path::new("E30.py"))] diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_comment.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_comment.py.snap new file mode 100644 index 0000000000000..0494356fb0a58 --- /dev/null +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_comment.py.snap @@ -0,0 +1,18 @@ +--- +source: crates/ruff_linter/src/rules/pycodestyle/mod.rs +--- +E303_first_line_comment.py:5:1: E303 [*] Too many blank lines (3) + | +5 | def fn(): + | ^^^ E303 +6 | pass + | + = help: Remove extraneous blank line(s) + +ℹ Safe fix +1 1 | # Test where the first line is a comment, and the rule violation follows it. +2 2 | +3 3 | +4 |- +5 4 | def fn(): +6 5 | pass diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_docstring.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_docstring.py.snap new file mode 100644 index 0000000000000..d709f904ddbd6 --- /dev/null +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_docstring.py.snap @@ -0,0 +1,18 @@ +--- +source: crates/ruff_linter/src/rules/pycodestyle/mod.rs +--- +E303_first_line_docstring.py:5:1: E303 [*] Too many blank lines (3) + | +5 | def fn(): + | ^^^ E303 +6 | pass + | + = help: Remove extraneous blank line(s) + +ℹ Safe fix +1 1 | """Test where the error is after the module's docstring.""" +2 2 | +3 3 | +4 |- +5 4 | def fn(): +6 5 | pass diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_expression.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_expression.py.snap new file mode 100644 index 0000000000000..d81f9098c5312 --- /dev/null +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_expression.py.snap @@ -0,0 +1,18 @@ +--- +source: crates/ruff_linter/src/rules/pycodestyle/mod.rs +--- +E303_first_line_expression.py:5:1: E303 [*] Too many blank lines (3) + | +5 | def fn(): + | ^^^ E303 +6 | pass + | + = help: Remove extraneous blank line(s) + +ℹ Safe fix +1 1 | "Test where the first line is a comment, " + "and the rule violation follows it." +2 2 | +3 3 | +4 |- +5 4 | def fn(): +6 5 | pass diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_statement.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_statement.py.snap new file mode 100644 index 0000000000000..7197d3e13338d --- /dev/null +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E303_E303_first_line_statement.py.snap @@ -0,0 +1,18 @@ +--- +source: crates/ruff_linter/src/rules/pycodestyle/mod.rs +--- +E303_first_line_statement.py:5:1: E303 [*] Too many blank lines (3) + | +5 | def fn(): + | ^^^ E303 +6 | pass + | + = help: Remove extraneous blank line(s) + +ℹ Safe fix +1 1 | print("Test where the first line is a statement, and the rule violation follows it.") +2 2 | +3 3 | +4 |- +5 4 | def fn(): +6 5 | pass From 831d895b03a417554bbe0c153e83a2f843445379 Mon Sep 17 00:00:00 2001 From: Hoel Bagard Date: Thu, 14 Mar 2024 13:49:47 +0900 Subject: [PATCH 3/3] add fixtures about the first line/beginning of the file for the E302 rule. --- .../pycodestyle/E302_first_line_docstring.py | 4 ++++ .../pycodestyle/E302_first_line_expression.py | 4 ++++ .../pycodestyle/E302_first_line_function.py | 5 +++++ .../pycodestyle/E302_first_line_statement.py | 4 ++++ .../ruff_linter/src/rules/pycodestyle/mod.rs | 15 ++++++++------ ...ts__E302_E302_first_line_docstring.py.snap | 19 ++++++++++++++++++ ...s__E302_E302_first_line_expression.py.snap | 19 ++++++++++++++++++ ...sts__E302_E302_first_line_function.py.snap | 20 +++++++++++++++++++ ...ts__E302_E302_first_line_statement.py.snap | 19 ++++++++++++++++++ 9 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_docstring.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_expression.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_function.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_statement.py create mode 100644 crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_docstring.py.snap create mode 100644 crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_expression.py.snap create mode 100644 crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_function.py.snap create mode 100644 crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_statement.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_docstring.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_docstring.py new file mode 100644 index 0000000000000..d0d3e6f260ec8 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_docstring.py @@ -0,0 +1,4 @@ +"""Test where the error is after the module's docstring.""" + +def fn(): + pass diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_expression.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_expression.py new file mode 100644 index 0000000000000..81c5095168e01 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_expression.py @@ -0,0 +1,4 @@ +"Test where the first line is a comment, " + "and the rule violation follows it." + +def fn(): + pass diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_function.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_function.py new file mode 100644 index 0000000000000..f31e2ca703b12 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_function.py @@ -0,0 +1,5 @@ +def fn1(): + pass + +def fn2(): + pass diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_statement.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_statement.py new file mode 100644 index 0000000000000..28ddd76ec9c84 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_statement.py @@ -0,0 +1,4 @@ +print("Test where the first line is a statement, and the rule violation follows it.") + +def fn(): + pass diff --git a/crates/ruff_linter/src/rules/pycodestyle/mod.rs b/crates/ruff_linter/src/rules/pycodestyle/mod.rs index 8f944ac95c25f..deeeac30ac5ee 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/mod.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/mod.rs @@ -171,12 +171,15 @@ mod tests { Ok(()) } - #[test_case(Path::new("E303_first_line_comment.py"))] - #[test_case(Path::new("E303_first_line_docstring.py"))] - #[test_case(Path::new("E303_first_line_expression.py"))] - #[test_case(Path::new("E303_first_line_statement.py"))] - fn blank_lines_first_line(path: &Path) -> Result<()> { - let rule_code = Rule::TooManyBlankLines; + #[test_case(Rule::BlankLinesTopLevel, Path::new("E302_first_line_docstring.py"))] + #[test_case(Rule::BlankLinesTopLevel, Path::new("E302_first_line_expression.py"))] + #[test_case(Rule::BlankLinesTopLevel, Path::new("E302_first_line_function.py"))] + #[test_case(Rule::BlankLinesTopLevel, Path::new("E302_first_line_statement.py"))] + #[test_case(Rule::TooManyBlankLines, Path::new("E303_first_line_comment.py"))] + #[test_case(Rule::TooManyBlankLines, Path::new("E303_first_line_docstring.py"))] + #[test_case(Rule::TooManyBlankLines, Path::new("E303_first_line_expression.py"))] + #[test_case(Rule::TooManyBlankLines, Path::new("E303_first_line_statement.py"))] + fn blank_lines_first_line(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( Path::new("pycodestyle").join(path).as_path(), diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_docstring.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_docstring.py.snap new file mode 100644 index 0000000000000..4fa4accc0cf4c --- /dev/null +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_docstring.py.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff_linter/src/rules/pycodestyle/mod.rs +--- +E302_first_line_docstring.py:3:1: E302 [*] Expected 2 blank lines, found 1 + | +1 | """Test where the error is after the module's docstring.""" +2 | +3 | def fn(): + | ^^^ E302 +4 | pass + | + = help: Add missing blank line(s) + +ℹ Safe fix +1 1 | """Test where the error is after the module's docstring.""" +2 2 | + 3 |+ +3 4 | def fn(): +4 5 | pass diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_expression.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_expression.py.snap new file mode 100644 index 0000000000000..9cd8779d1b6f7 --- /dev/null +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_expression.py.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff_linter/src/rules/pycodestyle/mod.rs +--- +E302_first_line_expression.py:3:1: E302 [*] Expected 2 blank lines, found 1 + | +1 | "Test where the first line is a comment, " + "and the rule violation follows it." +2 | +3 | def fn(): + | ^^^ E302 +4 | pass + | + = help: Add missing blank line(s) + +ℹ Safe fix +1 1 | "Test where the first line is a comment, " + "and the rule violation follows it." +2 2 | + 3 |+ +3 4 | def fn(): +4 5 | pass diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_function.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_function.py.snap new file mode 100644 index 0000000000000..a847c86079c7a --- /dev/null +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_function.py.snap @@ -0,0 +1,20 @@ +--- +source: crates/ruff_linter/src/rules/pycodestyle/mod.rs +--- +E302_first_line_function.py:4:1: E302 [*] Expected 2 blank lines, found 1 + | +2 | pass +3 | +4 | def fn2(): + | ^^^ E302 +5 | pass + | + = help: Add missing blank line(s) + +ℹ Safe fix +1 1 | def fn1(): +2 2 | pass +3 3 | + 4 |+ +4 5 | def fn2(): +5 6 | pass diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_statement.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_statement.py.snap new file mode 100644 index 0000000000000..a4736c7dd4e63 --- /dev/null +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E302_first_line_statement.py.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff_linter/src/rules/pycodestyle/mod.rs +--- +E302_first_line_statement.py:3:1: E302 [*] Expected 2 blank lines, found 1 + | +1 | print("Test where the first line is a statement, and the rule violation follows it.") +2 | +3 | def fn(): + | ^^^ E302 +4 | pass + | + = help: Add missing blank line(s) + +ℹ Safe fix +1 1 | print("Test where the first line is a statement, and the rule violation follows it.") +2 2 | + 3 |+ +3 4 | def fn(): +4 5 | pass