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/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..deeeac30ac5ee 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/mod.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/mod.rs @@ -171,6 +171,24 @@ mod tests { Ok(()) } + #[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(), + &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/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( 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 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