diff --git a/crates/ruff/resources/test/fixtures/pycodestyle/E30.py b/crates/ruff/resources/test/fixtures/pycodestyle/E30.py new file mode 100644 index 0000000000000..084ecb091d7c2 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pycodestyle/E30.py @@ -0,0 +1,568 @@ +"""Fixtures for the errors E301, E302, E303, E304, E305 and E306. + +Since these errors are about new lines, each test starts with either "No error" or "# E30X". +Each test's end is signaled by a "# end" line. + +There should be no E30X error outside of a test's bound. +""" + + +# No error +class Class: + pass +# end + + +# No error +def func(): + pass +# end + + +# No error +# comment +class Class: + pass +# end + + +# No error +# comment +def func(): + pass +# end + + +# no error +def foo(): + pass + + +def bar(): + pass + + +class Foo(object): + pass + + +class Bar(object): + pass +# end + + +# No error +class Class(object): + + def func1(): + pass + + def func2(): + pass +# end + + +# No error +class Class: + + def func1(): + pass + + # comment + def func2(): + pass + + # This is a + # ... multi-line comment + + def func3(): + pass + + +# This is a +# ... multi-line comment + +@decorator +class Class: + + def func1(): + pass + + # comment + + def func2(): + pass + + @property + def func3(): + pass + +# end + + +# No error +try: + from nonexistent import Bar +except ImportError: + class Bar(object): + """This is a Bar replacement""" +# end + + +# No error +def with_feature(f): + """Some decorator""" + wrapper = f + if has_this_feature(f): + def wrapper(*args): + call_feature(args[0]) + return f(*args) + return wrapper +# end + + +# No error +try: + next +except NameError: + def next(iterator, default): + for item in iterator: + return item + return default +# end + + +# No error +def fn(): + pass + + +class Foo(): + """Class Foo""" + + def fn(): + + pass +# end + + +# No error +# comment +def c(): + pass + + +# comment + + +def d(): + pass + +# This is a +# ... multi-line comment + +# And this one is +# ... a second paragraph +# ... which spans on 3 lines + + +# Function `e` is below +# NOTE: Hey this is a testcase + +def e(): + pass + + +def fn(): + print() + + # comment + + print() + + print() + +# Comment 1 + +# Comment 2 + + +# Comment 3 + +def fn2(): + + pass +# end + + +# no error +if __name__ == '__main__': + foo() +# end + + +# no error +defaults = {} +defaults.update({}) +# end + + +# no error +def foo(x): + classification = x + definitely = not classification +# end + + +# no error +def bar(): pass +def baz(): pass +# end + + +# no error +def foo(): + def bar(): pass + def baz(): pass +# end + + +# no error +from typing import overload +from typing import Union +# end + + +# no error +@overload +def f(x: int) -> int: ... +@overload +def f(x: str) -> str: ... +# end + + +# no error +def f(x: Union[int, str]) -> Union[int, str]: + return x +# end + + +# no error +from typing import Protocol + + +class C(Protocol): + @property + def f(self) -> int: ... + @property + def g(self) -> str: ... +# end + + +# no error +def f( + a, +): + pass +# end + + +# E301 +class Class(object): + + def func1(): + pass + def func2(): + pass +# end + + +# E301 +class Class: + + def fn1(): + pass + # comment + def fn2(): + pass +# end + + +# E302 +"""Main module.""" +def fn(): + pass +# end + + +# E302 +import sys +def get_sys_path(): + return sys.path +# end + + +# E302 +def a(): + pass + +def b(): + pass +# end + + +# E302 +def a(): + pass + +# comment + +def b(): + pass +# end + + +# E302 +def a(): + pass + +async def b(): + pass +# end + + +# E302 +async def x(): + pass + +async def x(y: int = 1): + pass +# end + + +# E302 +def bar(): + pass +def baz(): pass +# end + + +# E302 +def bar(): pass +def baz(): + pass +# end + + +# E302 +def f(): + pass + +# comment +@decorator +def g(): + pass +# end + + +# E303 +def fn(): + _ = None + + + # arbitrary comment + + def inner(): # E306 not expected + pass +# end + + +# E303 +def fn(): + _ = None + + + # arbitrary comment + def inner(): # E306 not expected + pass +# end + + +# E303 +print() + + + +print() +# end + + +# E303:5:1 +print() + + + +# comment + +print() +# end + + +# E303:5:5 E303:8:5 +def a(): + print() + + + # comment + + + # another comment + + print() +# end + + +# E303 +#!python + + + +"""This class docstring comes on line 5. +It gives error E303: too many blank lines (3) +""" +# end + + +# E304 +@decorator + +def function(): + pass +# end + + +# E305:7:1 +def fn(): + print() + + # comment + + # another comment +fn() +# end + + +# E305 +class Class(): + pass + + # comment + + # another comment +a = 1 +# end + + +# E305:8:1 +def fn(): + print() + + # comment + + # another comment + +try: + fn() +except Exception: + pass +# end + + +# E305:5:1 +def a(): + print + +# Two spaces before comments, too. +if a(): + a() +# end + + +#: E305:8:1 +# Example from https://github.com/PyCQA/pycodestyle/issues/400 +import stuff + + +def main(): + blah, blah + +if __name__ == '__main__': + main() +# end + + +# E306:3:5 +def a(): + x = 1 + def b(): + pass +# end + + +#: E306:3:5 +async def a(): + x = 1 + def b(): + pass +# end + + +#: E306:3:5 E306:5:9 +def a(): + x = 2 + def b(): + x = 1 + def c(): + pass +# end + + +# E306:3:5 E306:6:5 +def a(): + x = 1 + class C: + pass + x = 2 + def b(): + pass +# end + + +# E306:4:5 +def foo(): + def bar(): + pass + def baz(): pass +# end + + +# E306:3:5 +def foo(): + def bar(): pass + def baz(): + pass +# end + + +# E306 +class C: + def f(): + pass +# end + + +# E306 +def f(): + def f(): + pass +# end diff --git a/crates/ruff/src/checkers/logical_lines.rs b/crates/ruff/src/checkers/logical_lines.rs index cbf9263c70a65..b6697b5c040dd 100644 --- a/crates/ruff/src/checkers/logical_lines.rs +++ b/crates/ruff/src/checkers/logical_lines.rs @@ -7,10 +7,11 @@ use ruff_python_ast::token_kind::TokenKind; use crate::registry::{AsRule, Rule}; use crate::rules::pycodestyle::rules::logical_lines::{ - extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword, - missing_whitespace_around_operator, space_around_operator, whitespace_around_keywords, - whitespace_around_named_parameter_equals, whitespace_before_comment, - whitespace_before_parameters, LogicalLines, TokenFlags, + blank_lines, extraneous_whitespace, indentation, missing_whitespace, + missing_whitespace_after_keyword, missing_whitespace_around_operator, space_around_operator, + whitespace_around_keywords, whitespace_around_named_parameter_equals, + whitespace_before_comment, whitespace_before_parameters, BlankLinesTrackingVars, LogicalLines, + TokenFlags, }; use crate::settings::Settings; @@ -49,6 +50,7 @@ pub(crate) fn check_logical_lines( let should_fix_whitespace_before_punctuation = settings.rules.should_fix(Rule::WhitespaceBeforePunctuation); + let mut blank_lines_tracking_vars = BlankLinesTrackingVars::default(); let mut prev_line = None; let mut prev_indent_level = None; let indent_char = stylist.indentation().as_char(); @@ -119,6 +121,16 @@ pub(crate) fn check_logical_lines( } } + blank_lines( + &line, + prev_line.as_ref(), + &mut blank_lines_tracking_vars, + indent_level, + locator, + stylist, + &mut context, + ); + if !line.is_comment_only() { prev_line = Some(line); prev_indent_level = Some(indent_level); diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 6085e64b5a140..5468d7a0afdba 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -83,6 +83,12 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pycodestyle, "E273") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterKeyword), (Pycodestyle, "E274") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabBeforeKeyword), (Pycodestyle, "E275") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAfterKeyword), + (Pycodestyle, "E301") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::BlankLineBetweenMethods), + (Pycodestyle, "E302") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::BlankLinesTopLevel), + (Pycodestyle, "E303") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TooManyBlankLines), + (Pycodestyle, "E304") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::BlankLineAfterDecorator), + (Pycodestyle, "E305") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::BlankLinesAfterFunctionOrClass), + (Pycodestyle, "E306") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::BlankLinesBeforeNestedDefinition), (Pycodestyle, "E401") => (RuleGroup::Unspecified, rules::pycodestyle::rules::MultipleImportsOnOneLine), (Pycodestyle, "E402") => (RuleGroup::Unspecified, rules::pycodestyle::rules::ModuleImportNotAtTopOfFile), (Pycodestyle, "E501") => (RuleGroup::Unspecified, rules::pycodestyle::rules::LineTooLong), diff --git a/crates/ruff/src/flake8_to_ruff/parser.rs b/crates/ruff/src/flake8_to_ruff/parser.rs index 5c305aafcde65..51a335ab2735a 100644 --- a/crates/ruff/src/flake8_to_ruff/parser.rs +++ b/crates/ruff/src/flake8_to_ruff/parser.rs @@ -290,6 +290,10 @@ mod tests { pattern: "examples/*".to_string(), prefix: codes::Pyflakes::_841.into(), }, + PatternPrefixPair { + pattern: "*.pyi".to_string(), + prefix: codes::Pycodestyle::E302.into(), + }, ]; assert_eq!(actual, expected); diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index c89df825dd899..e8590216790d2 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -310,7 +310,12 @@ impl Rule { Rule::IOError => LintSource::Io, Rule::UnsortedImports | Rule::MissingRequiredImport => LintSource::Imports, Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => LintSource::Filesystem, - Rule::IndentationWithInvalidMultiple + Rule::BlankLineAfterDecorator + | Rule::BlankLineBetweenMethods + | Rule::BlankLinesAfterFunctionOrClass + | Rule::BlankLinesBeforeNestedDefinition + | Rule::BlankLinesTopLevel + | Rule::IndentationWithInvalidMultiple | Rule::IndentationWithInvalidMultipleComment | Rule::MissingWhitespace | Rule::MissingWhitespaceAfterKeyword @@ -334,6 +339,7 @@ impl Rule { | Rule::TabBeforeKeyword | Rule::TabBeforeOperator | Rule::TooFewSpacesBeforeInlineComment + | Rule::TooManyBlankLines | Rule::UnexpectedIndentation | Rule::UnexpectedIndentationComment | Rule::UnexpectedSpacesAroundKeywordParameterEquals diff --git a/crates/ruff/src/rules/pycodestyle/mod.rs b/crates/ruff/src/rules/pycodestyle/mod.rs index cc2570824631e..f6c41052269d3 100644 --- a/crates/ruff/src/rules/pycodestyle/mod.rs +++ b/crates/ruff/src/rules/pycodestyle/mod.rs @@ -104,6 +104,12 @@ mod tests { Path::new("E25.py") )] #[test_case(Rule::MissingWhitespaceAroundParameterEquals, Path::new("E25.py"))] + #[test_case(Rule::BlankLineBetweenMethods, Path::new("E30.py"))] + #[test_case(Rule::BlankLinesTopLevel, Path::new("E30.py"))] + #[test_case(Rule::TooManyBlankLines, Path::new("E30.py"))] + #[test_case(Rule::BlankLineAfterDecorator, Path::new("E30.py"))] + #[test_case(Rule::BlankLinesAfterFunctionOrClass, Path::new("E30.py"))] + #[test_case(Rule::BlankLinesBeforeNestedDefinition, Path::new("E30.py"))] fn logical(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff/src/rules/pycodestyle/rules/logical_lines/blank_lines.rs b/crates/ruff/src/rules/pycodestyle/rules/logical_lines/blank_lines.rs new file mode 100644 index 0000000000000..a64f5e9dbc514 --- /dev/null +++ b/crates/ruff/src/rules/pycodestyle/rules/logical_lines/blank_lines.rs @@ -0,0 +1,479 @@ +use ruff_diagnostics::AlwaysAutofixableViolation; +use ruff_diagnostics::Diagnostic; +use ruff_diagnostics::Edit; +use ruff_diagnostics::Fix; +use ruff_python_ast::source_code::Locator; + +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::source_code::Stylist; +use ruff_python_ast::token_kind::TokenKind; +use ruff_text_size::TextSize; + +use crate::checkers::logical_lines::LogicalLinesContext; + +use super::LogicalLine; + +/// Contains variables used for the linting of blank lines. +#[derive(Default)] +#[allow(clippy::struct_excessive_bools)] +pub(crate) struct BlankLinesTrackingVars { + follows_decorator: bool, + follows_def: bool, + is_in_class: bool, + /// The indent level where the class started. + class_indent_level: usize, + is_in_fn: bool, + /// The indent level where the function started. + fn_indent_level: usize, +} + +/// Number of blank lines between various code parts. +struct BlankLinesConfig; + +impl BlankLinesConfig { + /// Number of blank lines around top level classes and functions. + const TOP_LEVEL: u32 = 2; + /// Number of blank lines around methods and nested classes and functions. + const METHOD: u32 = 1; +} + +/// ## What it does +/// Checks for missing blank lines between methods of a class. +/// +/// ## Why is this bad? +/// PEP 8 recommends the use of blank lines as follows: +/// - Two blank lines are expected between functions and classes +/// - One blank line is expected between methods of a class. +/// +/// ## Example +/// ```python +/// class MyClass(object): +/// def func1(): +/// pass +/// def func2(): +/// pass +/// ``` +/// +/// Use instead: +/// ```python +/// class MyClass(object): +/// def func1(): +/// pass +/// +/// def func2(): +/// pass +/// ``` +/// +/// ## References +/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines) +/// - [Flake 8 rule](https://www.flake8rules.com/rules/E301.html) +#[violation] +pub struct BlankLineBetweenMethods(pub u32); + +impl AlwaysAutofixableViolation for BlankLineBetweenMethods { + #[derive_message_formats] + fn message(&self) -> String { + let BlankLineBetweenMethods(nb_blank_lines) = self; + format!( + "Expected {:?} blank line, found {nb_blank_lines}", + BlankLinesConfig::METHOD + ) + } + + fn autofix_title(&self) -> String { + "Add missing blank line(s)".to_string() + } +} + +/// ## What it does +/// Checks for missing blank lines between top level functions and classes. +/// +/// ## Why is this bad? +/// PEP 8 recommends the use of blank lines as follows: +/// - Two blank lines are expected between functions and classes +/// - One blank line is expected between methods of a class. +/// +/// ## Example +/// ```python +/// def func1(): +/// pass +/// def func2(): +/// pass +/// ``` +/// +/// Use instead: +/// ```python +/// def func1(): +/// pass +/// +/// +/// def func2(): +/// pass +/// ``` +/// +/// ## References +/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines) +/// - [Flake 8 rule](https://www.flake8rules.com/rules/E302.html) +#[violation] +pub struct BlankLinesTopLevel(pub u32); + +impl AlwaysAutofixableViolation for BlankLinesTopLevel { + #[derive_message_formats] + fn message(&self) -> String { + let BlankLinesTopLevel(nb_blank_lines) = self; + format!( + "Expected {:?} blank lines, found {nb_blank_lines}", + BlankLinesConfig::TOP_LEVEL + ) + } + + fn autofix_title(&self) -> String { + "Add missing blank line(s)".to_string() + } +} + +/// ## What it does +/// Checks for extraneous blank lines. +/// +/// ## Why is this bad? +/// PEP 8 recommends the using blank lines as following: +/// - Two blank lines are expected between functions and classes +/// - One blank line is expected between methods of a class. +/// +/// ## Example +/// ```python +/// def func1(): +/// pass +/// +/// +/// +/// def func2(): +/// pass +/// ``` +/// +/// Use instead: +/// ```python +/// def func1(): +/// pass +/// +/// +/// def func2(): +/// pass +/// ``` +/// +/// ## References +/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines) +/// - [Flake 8 rule](https://www.flake8rules.com/rules/E303.html) +#[violation] +pub struct TooManyBlankLines(pub u32); + +impl AlwaysAutofixableViolation for TooManyBlankLines { + #[derive_message_formats] + fn message(&self) -> String { + let TooManyBlankLines(nb_blank_lines) = self; + format!("Too many blank lines ({nb_blank_lines})") + } + + fn autofix_title(&self) -> String { + "Remove extraneous blank line(s)".to_string() + } +} + +/// ## What it does +/// Checks for missing blank line after function decorator. +/// +/// ## Why is this bad? +/// PEP 8 recommends the use of blank lines as follows: +/// - Two blank lines are expected between functions and classes +/// - One blank line is expected between methods of a class. +/// +/// ## Example +/// ```python +/// class User(object): +/// +/// @property +/// +/// def name(self): +/// pass +/// ``` +/// +/// Use instead: +/// ```python +/// class User(object): +/// +/// @property +/// def name(self): +/// pass +/// ``` +/// +/// ## References +/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines) +/// - [Flake 8 rule](https://www.flake8rules.com/rules/E304.html) +#[violation] +pub struct BlankLineAfterDecorator; + +impl AlwaysAutofixableViolation for BlankLineAfterDecorator { + #[derive_message_formats] + fn message(&self) -> String { + format!("blank lines found after function decorator") + } + + fn autofix_title(&self) -> String { + "Remove extraneous blank line(s)".to_string() + } +} + +/// ## What it does +/// Checks for missing blank lines after end of function or class. +/// +/// ## Why is this bad? +/// PEP 8 recommends the using blank lines as following: +/// - Two blank lines are expected between functions and classes +/// - One blank line is expected between methods of a class. +/// +/// ## Example +/// ```python +/// class User(object): +/// pass +/// user = User() +/// ``` +/// +/// Use instead: +/// ```python +/// class User(object): +/// pass +/// +/// +/// user = User() +/// ``` +/// +/// ## References +/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines) +/// - [Flake 8 rule](https://www.flake8rules.com/rules/E305.html) +#[violation] +pub struct BlankLinesAfterFunctionOrClass(pub u32); + +impl AlwaysAutofixableViolation for BlankLinesAfterFunctionOrClass { + #[derive_message_formats] + fn message(&self) -> String { + let BlankLinesAfterFunctionOrClass(blank_lines) = self; + format!("expected 2 blank lines after class or function definition, found ({blank_lines})") + } + + fn autofix_title(&self) -> String { + "Add missing blank line(s)".to_string() + } +} + +/// ## What it does +/// Checks for for 1 blank line between nested functions/classes definitions. +/// +/// ## Why is this bad? +/// PEP 8 recommends the using blank lines as following: +/// - Two blank lines are expected between functions and classes +/// - One blank line is expected between methods of a class. +/// +/// ## Example +/// ```python +/// def outer(): +/// def inner(): +/// pass +/// def inner2(): +/// pass +/// ``` +/// +/// Use instead: +/// ```python +/// def outer(): +/// +/// def inner(): +/// pass +/// +/// def inner2(): +/// pass +/// ``` +/// +/// ## References +/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines) +/// - [Flake 8 rule](https://www.flake8rules.com/rules/E306.html) +#[violation] +pub struct BlankLinesBeforeNestedDefinition(pub u32); + +impl AlwaysAutofixableViolation for BlankLinesBeforeNestedDefinition { + #[derive_message_formats] + fn message(&self) -> String { + let BlankLinesBeforeNestedDefinition(blank_lines) = self; + format!("Expected 1 blank line before a nested definition, found {blank_lines}") + } + + fn autofix_title(&self) -> String { + "Add missing blank line".to_string() + } +} + +/// E301, E302, E303, E304, E305, E306 +pub(crate) fn blank_lines( + line: &LogicalLine, + prev_line: Option<&LogicalLine>, + tracked_vars: &mut BlankLinesTrackingVars, + indent_level: usize, + locator: &Locator, + stylist: &Stylist, + context: &mut LogicalLinesContext, +) { + if indent_level <= tracked_vars.class_indent_level { + tracked_vars.is_in_class = false; + } + + if indent_level <= tracked_vars.fn_indent_level { + tracked_vars.is_in_fn = false; + } + + for (token_idx, token) in line.tokens().iter().enumerate() { + if token.kind() == TokenKind::Def + && tracked_vars.is_in_class + && line.line.preceding_blank_lines == 0 + && !tracked_vars.follows_decorator + && prev_line + .and_then(|prev_line| prev_line.tokens_trimmed().first()) + .map_or(false, |token| { + !matches!(token.kind(), TokenKind::Def | TokenKind::Class) + }) + { + // E301 + let mut diagnostic = Diagnostic::new( + BlankLineBetweenMethods(line.line.preceding_blank_lines), + token.range(), + ); + diagnostic.set_fix(Fix::automatic(Edit::insertion( + stylist.line_ending().as_str().to_string(), + locator.line_start(token.range().start()), + ))); + context.push_diagnostic(diagnostic); + } else if matches!(token.kind(), TokenKind::Def | TokenKind::Class) + && !(tracked_vars.follows_decorator + || tracked_vars.is_in_class + || tracked_vars.is_in_fn + || tracked_vars.follows_def + && line + .tokens_trimmed() + .last() + .map_or(false, |token| !matches!(token.kind(), TokenKind::Colon))) + && prev_line + .and_then(|prev_line| prev_line.tokens_trimmed().first()) + .map_or(false, |token| !matches!(token.kind(), TokenKind::Except)) + && line.line.preceding_blank_lines < 2 + && prev_line.is_some() + { + // E302 + let mut diagnostic = Diagnostic::new( + BlankLinesTopLevel(line.line.preceding_blank_lines), + token.range(), + ); + diagnostic.set_fix(Fix::automatic(Edit::insertion( + stylist + .line_ending() + .as_str() + .to_string() + .repeat(2 - line.line.preceding_blank_lines as usize), + locator.line_start(token.range().start()), + ))); + context.push_diagnostic(diagnostic); + } else if token_idx == 0 + && (line.line.preceding_blank_lines > BlankLinesConfig::TOP_LEVEL + || ((tracked_vars.is_in_class || tracked_vars.is_in_fn) + && line.line.preceding_blank_lines > BlankLinesConfig::METHOD)) + { + // E303 + let mut diagnostic = Diagnostic::new( + TooManyBlankLines(line.line.preceding_blank_lines), + token.range(), + ); + + let chars_to_remove = if indent_level > 0 { + line.line.preceding_blank_characters - BlankLinesConfig::METHOD + } else { + line.line.preceding_blank_characters - BlankLinesConfig::TOP_LEVEL + }; + let end = locator.line_start(token.range().start()); + let start = end - TextSize::new(chars_to_remove); + diagnostic.set_fix(Fix::automatic(Edit::deletion(start, end))); + + context.push_diagnostic(diagnostic); + } else if tracked_vars.follows_decorator && line.line.preceding_blank_lines > 0 { + // E304 + let mut diagnostic = Diagnostic::new(BlankLineAfterDecorator, token.range()); + + let range = token.range(); + diagnostic.set_fix(Fix::automatic(Edit::deletion( + locator.line_start(range.start()) + - TextSize::new(line.line.preceding_blank_characters), + locator.line_start(range.start()), + ))); + context.push_diagnostic(diagnostic); + } else if line.line.preceding_blank_lines < 2 + && (tracked_vars.is_in_fn || tracked_vars.is_in_class) + && indent_level == 0 + { + // E305 + let mut diagnostic = Diagnostic::new( + BlankLinesAfterFunctionOrClass(line.line.preceding_blank_lines), + token.range(), + ); + diagnostic.set_fix(Fix::automatic(Edit::insertion( + stylist + .line_ending() + .as_str() + .to_string() + .repeat(2 - line.line.preceding_blank_lines as usize), + locator.line_start(token.range().start()), + ))); + context.push_diagnostic(diagnostic); + } else if matches!(token.kind(), TokenKind::Def | TokenKind::Class) + && (tracked_vars.is_in_class || tracked_vars.is_in_fn) + && line.line.preceding_blank_lines == 0 + { + // E306 + let mut diagnostic = Diagnostic::new( + BlankLinesBeforeNestedDefinition(line.line.preceding_blank_lines), + token.range(), + ); + diagnostic.set_fix(Fix::automatic(Edit::insertion( + stylist.line_ending().as_str().to_string(), + locator.line_start(token.range().start()), + ))); + + context.push_diagnostic(diagnostic); + } + + match token.kind() { + TokenKind::Class => { + if !tracked_vars.is_in_class { + tracked_vars.class_indent_level = indent_level; + } + tracked_vars.is_in_class = true; + tracked_vars.follows_decorator = false; + tracked_vars.follows_def = false; + break; + } + TokenKind::At => { + tracked_vars.follows_decorator = true; + tracked_vars.follows_def = false; + break; + } + TokenKind::Def => { + if !tracked_vars.is_in_fn { + tracked_vars.fn_indent_level = indent_level; + } + tracked_vars.is_in_fn = true; + tracked_vars.follows_def = true; + tracked_vars.follows_decorator = false; + break; + } + _ => { + tracked_vars.follows_decorator = false; + tracked_vars.follows_def = false; + } + } + } +} diff --git a/crates/ruff/src/rules/pycodestyle/rules/logical_lines/mod.rs b/crates/ruff/src/rules/pycodestyle/rules/logical_lines/mod.rs index a00c71e75038a..32df51d79ecc0 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/logical_lines/mod.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/logical_lines/mod.rs @@ -1,3 +1,4 @@ +pub(crate) use blank_lines::*; pub(crate) use extraneous_whitespace::*; pub(crate) use indentation::*; pub(crate) use missing_whitespace::*; @@ -20,6 +21,7 @@ use ruff_python_ast::source_code::Locator; use ruff_python_ast::token_kind::TokenKind; use ruff_python_whitespace::is_python_whitespace; +mod blank_lines; mod extraneous_whitespace; mod indentation; mod missing_whitespace; @@ -119,7 +121,7 @@ impl<'a> IntoIterator for &'a LogicalLines<'a> { /// 2 /// ] /// ``` -#[derive(Debug)] +#[derive(Clone, Debug)] pub(crate) struct LogicalLine<'a> { lines: &'a LogicalLines<'a>, line: &'a Line, @@ -423,6 +425,10 @@ struct LogicalLinesBuilder { tokens: Vec, lines: Vec, current_line: CurrentLine, + /// Number of consecutive blank lines. + current_blank_lines: u32, + /// Number of blank characters in the blank lines (\n vs \r\n for example). + current_blank_characters: u32, } impl LogicalLinesBuilder { @@ -486,12 +492,19 @@ impl LogicalLinesBuilder { let is_empty = self.tokens[self.current_line.tokens_start as usize..end as usize] .iter() .all(|token| token.kind.is_newline()); - if !is_empty { + if is_empty { + self.current_blank_lines += 1; + self.current_blank_characters += end - self.current_line.tokens_start; + } else { self.lines.push(Line { flags: self.current_line.flags, + preceding_blank_lines: self.current_blank_lines, + preceding_blank_characters: self.current_blank_characters, tokens_start: self.current_line.tokens_start, tokens_end: end, }); + self.current_blank_lines = 0; + self.current_blank_characters = 0; } self.current_line = CurrentLine { @@ -515,6 +528,8 @@ impl LogicalLinesBuilder { #[derive(Debug, Clone)] struct Line { flags: TokenFlags, + preceding_blank_lines: u32, + preceding_blank_characters: u32, tokens_start: u32, tokens_end: u32, } diff --git a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E301_E30.py.snap b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E301_E30.py.snap new file mode 100644 index 0000000000000..91a46a731de68 --- /dev/null +++ b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E301_E30.py.snap @@ -0,0 +1,44 @@ +--- +source: crates/ruff/src/rules/pycodestyle/mod.rs +--- +E30.py:6:5: E301 [*] Expected 1 blank line, found 0 + | +4 | def a(): +5 | pass +6 | def b(): + | ^^^ E301 +7 | pass +8 | #: E301:6:5 + | + = help: Add missing blank line(s) + +ℹ Fix +3 3 | +4 4 | def a(): +5 5 | pass + 6 |+ +6 7 | def b(): +7 8 | pass +8 9 | #: E301:6:5 + +E30.py:14:5: E301 [*] Expected 1 blank line, found 0 + | +12 | pass +13 | # comment +14 | def b(): + | ^^^ E301 +15 | pass +16 | #: + | + = help: Add missing blank line(s) + +ℹ Fix +11 11 | def a(): +12 12 | pass +13 13 | # comment + 14 |+ +14 15 | def b(): +15 16 | pass +16 17 | #: + + diff --git a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E302_E30.py.snap b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E302_E30.py.snap new file mode 100644 index 0000000000000..a3de2d6144567 --- /dev/null +++ b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E302_E30.py.snap @@ -0,0 +1,514 @@ +--- +source: crates/ruff/src/rules/pycodestyle/mod.rs +--- +E30.py:21:1: E302 [*] Expected 2 blank lines, found 0 + | +19 | #: E302:2:1 +20 | """Main module.""" +21 | def _main(): + | ^^^ E302 +22 | pass +23 | #: E302:2:1 + | + = help: Add missing blank line(s) + +ℹ Fix +18 18 | +19 19 | #: E302:2:1 +20 20 | """Main module.""" + 21 |+ + 22 |+ +21 23 | def _main(): +22 24 | pass +23 25 | #: E302:2:1 + +E30.py:25:1: E302 [*] Expected 2 blank lines, found 0 + | +23 | #: E302:2:1 +24 | import sys +25 | def get_sys_path(): + | ^^^ E302 +26 | return sys.path +27 | #: E302:4:1 + | + = help: Add missing blank line(s) + +ℹ Fix +22 22 | pass +23 23 | #: E302:2:1 +24 24 | import sys + 25 |+ + 26 |+ +25 27 | def get_sys_path(): +26 28 | return sys.path +27 29 | #: E302:4:1 + +E30.py:28:1: E302 [*] Expected 2 blank lines, found 0 + | +26 | return sys.path +27 | #: E302:4:1 +28 | def a(): + | ^^^ E302 +29 | pass + | + = help: Add missing blank line(s) + +ℹ Fix +25 25 | def get_sys_path(): +26 26 | return sys.path +27 27 | #: E302:4:1 + 28 |+ + 29 |+ +28 30 | def a(): +29 31 | pass +30 32 | + +E30.py:31:1: E302 [*] Expected 2 blank lines, found 1 + | +29 | pass +30 | +31 | def b(): + | ^^^ E302 +32 | pass +33 | #: E302:6:1 + | + = help: Add missing blank line(s) + +ℹ Fix +28 28 | def a(): +29 29 | pass +30 30 | + 31 |+ +31 32 | def b(): +32 33 | pass +33 34 | #: E302:6:1 + +E30.py:34:1: E302 [*] Expected 2 blank lines, found 0 + | +32 | pass +33 | #: E302:6:1 +34 | def a(): + | ^^^ E302 +35 | pass + | + = help: Add missing blank line(s) + +ℹ Fix +31 31 | def b(): +32 32 | pass +33 33 | #: E302:6:1 + 34 |+ + 35 |+ +34 36 | def a(): +35 37 | pass +36 38 | + +E30.py:39:1: E302 [*] Expected 2 blank lines, found 1 + | +37 | # comment +38 | +39 | def b(): + | ^^^ E302 +40 | pass +41 | #: + | + = help: Add missing blank line(s) + +ℹ Fix +36 36 | +37 37 | # comment +38 38 | + 39 |+ +39 40 | def b(): +40 41 | pass +41 42 | #: + +E30.py:43:1: E302 [*] Expected 2 blank lines, found 0 + | +41 | #: +42 | #: E302:4:1 +43 | def a(): + | ^^^ E302 +44 | pass + | + = help: Add missing blank line(s) + +ℹ Fix +40 40 | pass +41 41 | #: +42 42 | #: E302:4:1 + 43 |+ + 44 |+ +43 45 | def a(): +44 46 | pass +45 47 | + +E30.py:46:7: E302 [*] Expected 2 blank lines, found 1 + | +44 | pass +45 | +46 | async def b(): + | ^^^ E302 +47 | pass +48 | #: + | + = help: Add missing blank line(s) + +ℹ Fix +43 43 | def a(): +44 44 | pass +45 45 | + 46 |+ +46 47 | async def b(): +47 48 | pass +48 49 | #: + +E30.py:65:1: E302 [*] Expected 2 blank lines, found 0 + | +63 | print +64 | #: E303:5:5 E303:8:5 +65 | def a(): + | ^^^ E302 +66 | print + | + = help: Add missing blank line(s) + +ℹ Fix +62 62 | +63 63 | print +64 64 | #: E303:5:5 E303:8:5 + 65 |+ + 66 |+ +65 67 | def a(): +66 68 | print +67 69 | + +E30.py:94:1: E302 [*] Expected 2 blank lines, found 0 + | +93 | #: E305:7:1 +94 | def a(): + | ^^^ E302 +95 | print + | + = help: Add missing blank line(s) + +ℹ Fix +91 91 | #: +92 92 | +93 93 | #: E305:7:1 + 94 |+ + 95 |+ +94 96 | def a(): +95 97 | print +96 98 | + +E30.py:102:1: E302 [*] Expected 2 blank lines, found 0 + | +100 | a() +101 | #: E305:8:1 +102 | def a(): + | ^^^ E302 +103 | print + | + = help: Add missing blank line(s) + +ℹ Fix +99 99 | # another comment +100 100 | a() +101 101 | #: E305:8:1 + 102 |+ + 103 |+ +102 104 | def a(): +103 105 | print +104 106 | + +E30.py:114:1: E302 [*] Expected 2 blank lines, found 0 + | +112 | pass +113 | #: E305:5:1 +114 | def a(): + | ^^^ E302 +115 | print + | + = help: Add missing blank line(s) + +ℹ Fix +111 111 | except Exception: +112 112 | pass +113 113 | #: E305:5:1 + 114 |+ + 115 |+ +114 116 | def a(): +115 117 | print +116 118 | + +E30.py:123:1: E302 [*] Expected 2 blank lines, found 0 + | +122 | #: E306:3:5 +123 | def a(): + | ^^^ E302 +124 | x = 1 +125 | def b(): + | + = help: Add missing blank line(s) + +ℹ Fix +120 120 | #: +121 121 | +122 122 | #: E306:3:5 + 123 |+ + 124 |+ +123 125 | def a(): +124 126 | x = 1 +125 127 | def b(): + +E30.py:128:7: E302 [*] Expected 2 blank lines, found 0 + | +126 | pass +127 | #: E306:3:5 +128 | async def a(): + | ^^^ E302 +129 | x = 1 +130 | def b(): + | + = help: Add missing blank line(s) + +ℹ Fix +125 125 | def b(): +126 126 | pass +127 127 | #: E306:3:5 + 128 |+ + 129 |+ +128 130 | async def a(): +129 131 | x = 1 +130 132 | def b(): + +E30.py:133:1: E302 [*] Expected 2 blank lines, found 0 + | +131 | pass +132 | #: E306:3:5 E306:5:9 +133 | def a(): + | ^^^ E302 +134 | x = 2 +135 | def b(): + | + = help: Add missing blank line(s) + +ℹ Fix +130 130 | def b(): +131 131 | pass +132 132 | #: E306:3:5 E306:5:9 + 133 |+ + 134 |+ +133 135 | def a(): +134 136 | x = 2 +135 137 | def b(): + +E30.py:140:1: E302 [*] Expected 2 blank lines, found 0 + | +138 | pass +139 | #: E306:3:5 E306:6:5 +140 | def a(): + | ^^^ E302 +141 | x = 1 +142 | class C: + | + = help: Add missing blank line(s) + +ℹ Fix +137 137 | def c(): +138 138 | pass +139 139 | #: E306:3:5 E306:6:5 + 140 |+ + 141 |+ +140 142 | def a(): +141 143 | x = 1 +142 144 | class C: + +E30.py:161:8: E302 [*] Expected 2 blank lines, found 0 + | +159 | # Previously just E272:1:6 E272:4:6 +160 | #: E302:4:1 E271:1:6 E271:4:6 +161 | async def x(): + | ^^^ E302 +162 | pass + | + = help: Add missing blank line(s) + +ℹ Fix +158 158 | main() +159 159 | # Previously just E272:1:6 E272:4:6 +160 160 | #: E302:4:1 E271:1:6 E271:4:6 + 161 |+ + 162 |+ +161 163 | async def x(): +162 164 | pass +163 165 | + +E30.py:164:8: E302 [*] Expected 2 blank lines, found 1 + | +162 | pass +163 | +164 | async def x(y: int = 1): + | ^^^ E302 +165 | pass +166 | #: E704:3:1 E302:3:1 + | + = help: Add missing blank line(s) + +ℹ Fix +161 161 | async def x(): +162 162 | pass +163 163 | + 164 |+ +164 165 | async def x(y: int = 1): +165 166 | pass +166 167 | #: E704:3:1 E302:3:1 + +E30.py:167:1: E302 [*] Expected 2 blank lines, found 0 + | +165 | pass +166 | #: E704:3:1 E302:3:1 +167 | def bar(): + | ^^^ E302 +168 | pass +169 | def baz(): pass + | + = help: Add missing blank line(s) + +ℹ Fix +164 164 | async def x(y: int = 1): +165 165 | pass +166 166 | #: E704:3:1 E302:3:1 + 167 |+ + 168 |+ +167 169 | def bar(): +168 170 | pass +169 171 | def baz(): pass + +E30.py:169:1: E302 [*] Expected 2 blank lines, found 0 + | +167 | def bar(): +168 | pass +169 | def baz(): pass + | ^^^ E302 +170 | #: E704:1:1 E302:2:1 +171 | def bar(): pass + | + = help: Add missing blank line(s) + +ℹ Fix +166 166 | #: E704:3:1 E302:3:1 +167 167 | def bar(): +168 168 | pass + 169 |+ + 170 |+ +169 171 | def baz(): pass +170 172 | #: E704:1:1 E302:2:1 +171 173 | def bar(): pass + +E30.py:171:1: E302 [*] Expected 2 blank lines, found 0 + | +169 | def baz(): pass +170 | #: E704:1:1 E302:2:1 +171 | def bar(): pass + | ^^^ E302 +172 | def baz(): +173 | pass + | + = help: Add missing blank line(s) + +ℹ Fix +168 168 | pass +169 169 | def baz(): pass +170 170 | #: E704:1:1 E302:2:1 + 171 |+ + 172 |+ +171 173 | def bar(): pass +172 174 | def baz(): +173 175 | pass + +E30.py:172:1: E302 [*] Expected 2 blank lines, found 0 + | +170 | #: E704:1:1 E302:2:1 +171 | def bar(): pass +172 | def baz(): + | ^^^ E302 +173 | pass +174 | #: E704:4:5 E306:4:5 + | + = help: Add missing blank line(s) + +ℹ Fix +169 169 | def baz(): pass +170 170 | #: E704:1:1 E302:2:1 +171 171 | def bar(): pass + 172 |+ + 173 |+ +172 174 | def baz(): +173 175 | pass +174 176 | #: E704:4:5 E306:4:5 + +E30.py:175:1: E302 [*] Expected 2 blank lines, found 0 + | +173 | pass +174 | #: E704:4:5 E306:4:5 +175 | def foo(): + | ^^^ E302 +176 | def bar(): +177 | pass + | + = help: Add missing blank line(s) + +ℹ Fix +172 172 | def baz(): +173 173 | pass +174 174 | #: E704:4:5 E306:4:5 + 175 |+ + 176 |+ +175 177 | def foo(): +176 178 | def bar(): +177 179 | pass + +E30.py:180:1: E302 [*] Expected 2 blank lines, found 0 + | +178 | def baz(): pass +179 | #: E704:2:5 E306:3:5 +180 | def foo(): + | ^^^ E302 +181 | def bar(): pass +182 | def baz(): + | + = help: Add missing blank line(s) + +ℹ Fix +177 177 | pass +178 178 | def baz(): pass +179 179 | #: E704:2:5 E306:3:5 + 180 |+ + 181 |+ +180 182 | def foo(): +181 183 | def bar(): pass +182 184 | def baz(): + +E30.py:185:1: E302 [*] Expected 2 blank lines, found 0 + | +183 | pass +184 | #: E302:5:1 +185 | def f(): + | ^^^ E302 +186 | pass + | + = help: Add missing blank line(s) + +ℹ Fix +182 182 | def baz(): +183 183 | pass +184 184 | #: E302:5:1 + 185 |+ + 186 |+ +185 187 | def f(): +186 188 | pass +187 189 | + + diff --git a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E303_E30.py.snap b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E303_E30.py.snap new file mode 100644 index 0000000000000..66ea47695798a --- /dev/null +++ b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E303_E30.py.snap @@ -0,0 +1,92 @@ +--- +source: crates/ruff/src/rules/pycodestyle/mod.rs +--- +E30.py:55:1: E303 [*] Too many blank lines (3) + | +55 | print + | ^^^^^ E303 +56 | #: E303:5:1 +57 | print + | + = help: Remove extraneous blank line(s) + +ℹ Fix +51 51 | print +52 52 | +53 53 | +54 |- +55 54 | print +56 55 | #: E303:5:1 +57 56 | print + +E30.py:61:1: E303 [*] Too many blank lines (3) + | +61 | # comment + | ^^^^^^^^^ E303 +62 | +63 | print + | + = help: Remove extraneous blank line(s) + +ℹ Fix +57 57 | print +58 58 | +59 59 | +60 |- +61 60 | # comment +62 61 | +63 62 | print + +E30.py:69:5: E303 [*] Too many blank lines (2) + | +69 | # comment + | ^^^^^^^^^ E303 + | + = help: Remove extraneous blank line(s) + +ℹ Fix +65 65 | def a(): +66 66 | print +67 67 | +68 |- +69 68 | # comment +70 69 | +71 70 | + +E30.py:72:5: E303 [*] Too many blank lines (2) + | +72 | # another comment + | ^^^^^^^^^^^^^^^^^ E303 +73 | +74 | print + | + = help: Remove extraneous blank line(s) + +ℹ Fix +68 68 | +69 69 | # comment +70 70 | +71 |- +72 71 | # another comment +73 72 | +74 73 | print + +E30.py:88:1: E303 [*] Too many blank lines (3) + | +88 | """This class docstring comes on line 5. + | E303 +89 | It gives error E303: too many blank lines (3) +90 | """ + | + = help: Remove extraneous blank line(s) + +ℹ Fix +84 84 | #!python +85 85 | +86 86 | +87 |- +88 87 | """This class docstring comes on line 5. +89 88 | It gives error E303: too many blank lines (3) +90 89 | """ + + diff --git a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E304_E30.py.snap b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E304_E30.py.snap new file mode 100644 index 0000000000000..d1e7385344733 --- /dev/null +++ b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E304_E30.py.snap @@ -0,0 +1,24 @@ +--- +source: crates/ruff/src/rules/pycodestyle/mod.rs +--- +E30.py:81:1: E304 [*] blank lines found after function decorator + | +79 | @decorator +80 | +81 | def function(): + | ^^^ E304 +82 | pass +83 | #: E303:5:1 + | + = help: Remove extraneous blank line(s) + +ℹ Fix +77 77 | +78 78 | #: E304:3:1 +79 79 | @decorator +80 |- +81 80 | def function(): +82 81 | pass +83 82 | #: E303:5:1 + + diff --git a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E305_E30.py.snap b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E305_E30.py.snap new file mode 100644 index 0000000000000..47288f5b439cd --- /dev/null +++ b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E305_E30.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff/src/rules/pycodestyle/mod.rs +--- + diff --git a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E306_E30.py.snap b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E306_E30.py.snap new file mode 100644 index 0000000000000..7f9f29fc1a626 --- /dev/null +++ b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E306_E30.py.snap @@ -0,0 +1,204 @@ +--- +source: crates/ruff/src/rules/pycodestyle/mod.rs +--- +E30.py:125:5: E306 [*] Expected 1 blank line before a nested definition, found 0 + | +123 | def a(): +124 | x = 1 +125 | def b(): + | ^^^ E306 +126 | pass +127 | #: E306:3:5 + | + = help: Add missing blank line + +ℹ Fix +122 122 | #: E306:3:5 +123 123 | def a(): +124 124 | x = 1 + 125 |+ +125 126 | def b(): +126 127 | pass +127 128 | #: E306:3:5 + +E30.py:130:5: E306 [*] Expected 1 blank line before a nested definition, found 0 + | +128 | async def a(): +129 | x = 1 +130 | def b(): + | ^^^ E306 +131 | pass +132 | #: E306:3:5 E306:5:9 + | + = help: Add missing blank line + +ℹ Fix +127 127 | #: E306:3:5 +128 128 | async def a(): +129 129 | x = 1 + 130 |+ +130 131 | def b(): +131 132 | pass +132 133 | #: E306:3:5 E306:5:9 + +E30.py:135:5: E306 [*] Expected 1 blank line before a nested definition, found 0 + | +133 | def a(): +134 | x = 2 +135 | def b(): + | ^^^ E306 +136 | x = 1 +137 | def c(): + | + = help: Add missing blank line + +ℹ Fix +132 132 | #: E306:3:5 E306:5:9 +133 133 | def a(): +134 134 | x = 2 + 135 |+ +135 136 | def b(): +136 137 | x = 1 +137 138 | def c(): + +E30.py:137:9: E306 [*] Expected 1 blank line before a nested definition, found 0 + | +135 | def b(): +136 | x = 1 +137 | def c(): + | ^^^ E306 +138 | pass +139 | #: E306:3:5 E306:6:5 + | + = help: Add missing blank line + +ℹ Fix +134 134 | x = 2 +135 135 | def b(): +136 136 | x = 1 + 137 |+ +137 138 | def c(): +138 139 | pass +139 140 | #: E306:3:5 E306:6:5 + +E30.py:142:5: E306 [*] Expected 1 blank line before a nested definition, found 0 + | +140 | def a(): +141 | x = 1 +142 | class C: + | ^^^^^ E306 +143 | pass +144 | x = 2 + | + = help: Add missing blank line + +ℹ Fix +139 139 | #: E306:3:5 E306:6:5 +140 140 | def a(): +141 141 | x = 1 + 142 |+ +142 143 | class C: +143 144 | pass +144 145 | x = 2 + +E30.py:145:5: E306 [*] Expected 1 blank line before a nested definition, found 0 + | +143 | pass +144 | x = 2 +145 | def b(): + | ^^^ E306 +146 | pass +147 | #: + | + = help: Add missing blank line + +ℹ Fix +142 142 | class C: +143 143 | pass +144 144 | x = 2 + 145 |+ +145 146 | def b(): +146 147 | pass +147 148 | #: + +E30.py:176:5: E306 [*] Expected 1 blank line before a nested definition, found 0 + | +174 | #: E704:4:5 E306:4:5 +175 | def foo(): +176 | def bar(): + | ^^^ E306 +177 | pass +178 | def baz(): pass + | + = help: Add missing blank line + +ℹ Fix +173 173 | pass +174 174 | #: E704:4:5 E306:4:5 +175 175 | def foo(): + 176 |+ +176 177 | def bar(): +177 178 | pass +178 179 | def baz(): pass + +E30.py:178:5: E306 [*] Expected 1 blank line before a nested definition, found 0 + | +176 | def bar(): +177 | pass +178 | def baz(): pass + | ^^^ E306 +179 | #: E704:2:5 E306:3:5 +180 | def foo(): + | + = help: Add missing blank line + +ℹ Fix +175 175 | def foo(): +176 176 | def bar(): +177 177 | pass + 178 |+ +178 179 | def baz(): pass +179 180 | #: E704:2:5 E306:3:5 +180 181 | def foo(): + +E30.py:181:5: E306 [*] Expected 1 blank line before a nested definition, found 0 + | +179 | #: E704:2:5 E306:3:5 +180 | def foo(): +181 | def bar(): pass + | ^^^ E306 +182 | def baz(): +183 | pass + | + = help: Add missing blank line + +ℹ Fix +178 178 | def baz(): pass +179 179 | #: E704:2:5 E306:3:5 +180 180 | def foo(): + 181 |+ +181 182 | def bar(): pass +182 183 | def baz(): +183 184 | pass + +E30.py:182:5: E306 [*] Expected 1 blank line before a nested definition, found 0 + | +180 | def foo(): +181 | def bar(): pass +182 | def baz(): + | ^^^ E306 +183 | pass +184 | #: E302:5:1 + | + = help: Add missing blank line + +ℹ Fix +179 179 | #: E704:2:5 E306:3:5 +180 180 | def foo(): +181 181 | def bar(): pass + 182 |+ +182 183 | def baz(): +183 184 | pass +184 185 | #: E302:5:1 + + diff --git a/ruff.schema.json b/ruff.schema.json index 54023b1ece385..98ddc2fabe706 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1839,6 +1839,12 @@ "E273", "E274", "E275", + "E301", + "E302", + "E303", + "E304", + "E305", + "E306", "E4", "E40", "E401", diff --git a/scripts/check_docs_formatted.py b/scripts/check_docs_formatted.py index 81752ac742fd6..e8be367e5bd05 100755 --- a/scripts/check_docs_formatted.py +++ b/scripts/check_docs_formatted.py @@ -27,6 +27,11 @@ "bad-quotes-docstring", "bad-quotes-inline-string", "bad-quotes-multiline-string", + "blank-line-after-decorator", + "blank-line-between-methods", + "blank-lines-after-function-or-class", + "blank-lines-before-nested-definition", + "blank-lines-top-level", "explicit-string-concatenation", "indentation-with-invalid-multiple", "line-too-long", @@ -45,6 +50,7 @@ "over-indented", "prohibited-trailing-comma", "too-few-spaces-before-inline-comment", + "too-many-blank-lines", "trailing-comma-on-bare-tuple", "unexpected-indentation-comment", "unnecessary-class-parentheses",