[pycodestyle] Add blank line(s) rules (E301, E302, E303, E304, E305, E306) (#9266)

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Hoël Bagard 2024-02-09 03:35:08 +09:00 committed by GitHub
parent 688177ff6a
commit 9027169125
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 2616 additions and 3 deletions

View file

@ -0,0 +1,816 @@
"""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
class Class:
"""Docstring"""
def __init__(self) -> None:
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(object):
def func1():
pass
# comment
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
# no error
if True:
class Class:
"""Docstring"""
def function(self):
...
# end
# no error
if True:
def function(self):
...
# end
# no error
@decorator
# comment
@decorator
def function():
pass
# end
# no error
class Class:
def method(self):
if True:
def function():
pass
# end
# no error
@decorator
async def function(data: None) -> None:
...
# end
# no error
class Class:
def method():
"""docstring"""
# comment
def function():
pass
# end
# no error
try:
if True:
# comment
class Class:
pass
except:
pass
# end
# no error
def f():
def f():
pass
# end
# no error
class MyClass:
# comment
def method(self) -> None:
pass
# end
# no error
def function1():
# Comment
def function2():
pass
# end
# no error
async def function1():
await function2()
async with function3():
pass
# end
# no error
if (
cond1
and cond2
):
pass
#end
# no error
async def function1():
await function2()
async with function3():
pass
# end
# no error
async def function1():
await function2()
async with function3():
pass
# end
# no error
async def function1():
await function2()
async with function3():
pass
# end
# no error
class Test:
async
def a(self): pass
# end
# no error
class Test:
def a():
pass
# wrongly indented comment
def b():
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 (pycodestyle detects E306)
pass
# end
# E303
def fn():
_ = None
# arbitrary comment
def inner(): # E306 not expected (pycodestyle detects E306)
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
# E303
class Class:
def a(self):
pass
def b(self):
pass
# end
# E303
if True:
a = 1
a = 2
# end
# E303
class Test:
# comment
# another comment
def test(self): pass
# end
# E303
class Test:
def a(self):
pass
# wrongly indented comment
def b(self):
pass
# end
# E304
@decorator
def function():
pass
# end
# E304
@decorator
# comment E304 not expected
def function():
pass
# end
# E304
@decorator
# comment E304 not expected
# second comment E304 not expected
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
def foo():
def bar():
pass
def baz(): pass
# end
# E306:3:5
def foo():
def bar(): pass
def baz():
pass
# end
# E306
def a():
x = 2
@decorator
def b():
pass
# end
# E306
def a():
x = 2
@decorator
async def b():
pass
# end
# E306
def a():
x = 2
async def b():
pass
# end

View file

@ -1,3 +1,4 @@
use crate::line_width::IndentWidth;
use ruff_diagnostics::Diagnostic;
use ruff_python_codegen::Stylist;
use ruff_python_parser::lexer::LexResult;
@ -15,11 +16,11 @@ use crate::rules::pycodestyle::rules::logical_lines::{
use crate::settings::LinterSettings;
/// Return the amount of indentation, expanding tabs to the next multiple of the settings' tab size.
fn expand_indent(line: &str, settings: &LinterSettings) -> usize {
pub(crate) fn expand_indent(line: &str, indent_width: IndentWidth) -> usize {
let line = line.trim_end_matches(['\n', '\r']);
let mut indent = 0;
let tab_size = settings.tab_size.as_usize();
let tab_size = indent_width.as_usize();
for c in line.bytes() {
match c {
b'\t' => indent = (indent / tab_size) * tab_size + tab_size,
@ -85,7 +86,7 @@ pub(crate) fn check_logical_lines(
TextRange::new(locator.line_start(first_token.start()), first_token.start())
};
let indent_level = expand_indent(locator.slice(range), settings);
let indent_level = expand_indent(locator.slice(range), settings.tab_size);
let indent_size = 4;

View file

@ -4,6 +4,7 @@ use std::path::Path;
use ruff_notebook::CellOffsets;
use ruff_python_ast::PySourceType;
use ruff_python_codegen::Stylist;
use ruff_python_parser::lexer::LexResult;
use ruff_python_parser::Tok;
@ -14,6 +15,7 @@ use ruff_source_file::Locator;
use crate::directives::TodoComment;
use crate::lex::docstring_detection::StateMachine;
use crate::registry::{AsRule, Rule};
use crate::rules::pycodestyle::rules::BlankLinesChecker;
use crate::rules::ruff::rules::Context;
use crate::rules::{
eradicate, flake8_commas, flake8_executable, flake8_fixme, flake8_implicit_str_concat,
@ -21,17 +23,37 @@ use crate::rules::{
};
use crate::settings::LinterSettings;
#[allow(clippy::too_many_arguments)]
pub(crate) fn check_tokens(
tokens: &[LexResult],
path: &Path,
locator: &Locator,
indexer: &Indexer,
stylist: &Stylist,
settings: &LinterSettings,
source_type: PySourceType,
cell_offsets: Option<&CellOffsets>,
) -> Vec<Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = vec![];
if settings.rules.any_enabled(&[
Rule::BlankLineBetweenMethods,
Rule::BlankLinesTopLevel,
Rule::TooManyBlankLines,
Rule::BlankLineAfterDecorator,
Rule::BlankLinesAfterFunctionOrClass,
Rule::BlankLinesBeforeNestedDefinition,
]) {
let mut blank_lines_checker = BlankLinesChecker::default();
blank_lines_checker.check_lines(
tokens,
locator,
stylist,
settings.tab_size,
&mut diagnostics,
);
}
if settings.rules.enabled(Rule::BlanketNOQA) {
pygrep_hooks::rules::blanket_noqa(&mut diagnostics, indexer, locator);
}

View file

@ -137,6 +137,12 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pycodestyle, "E274") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabBeforeKeyword),
#[allow(deprecated)]
(Pycodestyle, "E275") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAfterKeyword),
(Pycodestyle, "E301") => (RuleGroup::Preview, rules::pycodestyle::rules::BlankLineBetweenMethods),
(Pycodestyle, "E302") => (RuleGroup::Preview, rules::pycodestyle::rules::BlankLinesTopLevel),
(Pycodestyle, "E303") => (RuleGroup::Preview, rules::pycodestyle::rules::TooManyBlankLines),
(Pycodestyle, "E304") => (RuleGroup::Preview, rules::pycodestyle::rules::BlankLineAfterDecorator),
(Pycodestyle, "E305") => (RuleGroup::Preview, rules::pycodestyle::rules::BlankLinesAfterFunctionOrClass),
(Pycodestyle, "E306") => (RuleGroup::Preview, rules::pycodestyle::rules::BlankLinesBeforeNestedDefinition),
(Pycodestyle, "E401") => (RuleGroup::Stable, rules::pycodestyle::rules::MultipleImportsOnOneLine),
(Pycodestyle, "E402") => (RuleGroup::Stable, rules::pycodestyle::rules::ModuleImportNotAtTopOfFile),
(Pycodestyle, "E501") => (RuleGroup::Stable, rules::pycodestyle::rules::LineTooLong),

View file

@ -109,6 +109,7 @@ pub fn check_path(
path,
locator,
indexer,
stylist,
settings,
source_type,
source_kind.as_ipy_notebook().map(Notebook::cell_offsets),

View file

@ -264,6 +264,11 @@ impl Rule {
| Rule::BadQuotesMultilineString
| Rule::BlanketNOQA
| Rule::BlanketTypeIgnore
| Rule::BlankLineAfterDecorator
| Rule::BlankLineBetweenMethods
| Rule::BlankLinesAfterFunctionOrClass
| Rule::BlankLinesBeforeNestedDefinition
| Rule::BlankLinesTopLevel
| Rule::CommentedOutCode
| Rule::EmptyComment
| Rule::ExtraneousParentheses
@ -296,6 +301,7 @@ impl Rule {
| Rule::ShebangNotFirstLine
| Rule::SingleLineImplicitStringConcatenation
| Rule::TabIndentation
| Rule::TooManyBlankLines
| Rule::TrailingCommaOnBareTuple
| Rule::TypeCommentInStub
| Rule::UselessSemicolon

View file

@ -136,6 +136,13 @@ 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(

View file

@ -0,0 +1,896 @@
use itertools::Itertools;
use std::cmp::Ordering;
use std::num::NonZeroU32;
use std::slice::Iter;
use ruff_diagnostics::AlwaysFixableViolation;
use ruff_diagnostics::Diagnostic;
use ruff_diagnostics::Edit;
use ruff_diagnostics::Fix;
use ruff_macros::{derive_message_formats, violation};
use ruff_python_codegen::Stylist;
use ruff_python_parser::lexer::LexResult;
use ruff_python_parser::lexer::LexicalError;
use ruff_python_parser::Tok;
use ruff_python_parser::TokenKind;
use ruff_source_file::{Locator, UniversalNewlines};
use ruff_text_size::TextRange;
use ruff_text_size::TextSize;
use crate::checkers::logical_lines::expand_indent;
use crate::line_width::IndentWidth;
use ruff_python_trivia::PythonWhitespace;
/// Number of blank lines around top level classes and functions.
const BLANK_LINES_TOP_LEVEL: u32 = 2;
/// Number of blank lines around methods and nested classes and functions.
const BLANK_LINES_METHOD_LEVEL: u32 = 1;
/// ## What it does
/// Checks for missing blank lines between methods of a class.
///
/// ## Why is this bad?
/// PEP 8 recommends exactly one blank line 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;
impl AlwaysFixableViolation for BlankLineBetweenMethods {
#[derive_message_formats]
fn message(&self) -> String {
format!("Expected {BLANK_LINES_METHOD_LEVEL:?} blank line, found 0")
}
fn fix_title(&self) -> String {
"Add missing blank line".to_string()
}
}
/// ## What it does
/// Checks for missing blank lines between top level functions and classes.
///
/// ## Why is this bad?
/// PEP 8 recommends exactly two blank lines between top level functions and classes.
///
/// ## 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 {
actual_blank_lines: u32,
}
impl AlwaysFixableViolation for BlankLinesTopLevel {
#[derive_message_formats]
fn message(&self) -> String {
let BlankLinesTopLevel {
actual_blank_lines: nb_blank_lines,
} = self;
format!("Expected {BLANK_LINES_TOP_LEVEL:?} blank lines, found {nb_blank_lines}")
}
fn fix_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 using blank lines as follows:
/// - No more than two blank lines between top-level statements.
/// - No more than one blank line between non-top-level statements.
///
/// ## 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 {
actual_blank_lines: u32,
}
impl AlwaysFixableViolation for TooManyBlankLines {
#[derive_message_formats]
fn message(&self) -> String {
let TooManyBlankLines {
actual_blank_lines: nb_blank_lines,
} = self;
format!("Too many blank lines ({nb_blank_lines})")
}
fn fix_title(&self) -> String {
"Remove extraneous blank line(s)".to_string()
}
}
/// ## What it does
/// Checks for extraneous blank line(s) after function decorators.
///
/// ## Why is this bad?
/// There should be no blank lines between a decorator and the object it is decorating.
///
/// ## 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 {
actual_blank_lines: u32,
}
impl AlwaysFixableViolation for BlankLineAfterDecorator {
#[derive_message_formats]
fn message(&self) -> String {
format!(
"Blank lines found after function decorator ({lines})",
lines = self.actual_blank_lines
)
}
fn fix_title(&self) -> String {
"Remove extraneous blank line(s)".to_string()
}
}
/// ## What it does
/// Checks for missing blank lines after the end of function or class.
///
/// ## Why is this bad?
/// PEP 8 recommends 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 {
actual_blank_lines: u32,
}
impl AlwaysFixableViolation for BlankLinesAfterFunctionOrClass {
#[derive_message_formats]
fn message(&self) -> String {
let BlankLinesAfterFunctionOrClass {
actual_blank_lines: blank_lines,
} = self;
format!("Expected 2 blank lines after class or function definition, found ({blank_lines})")
}
fn fix_title(&self) -> String {
"Add missing blank line(s)".to_string()
}
}
/// ## What it does
/// Checks for 1 blank line between nested function or class definitions.
///
/// ## Why is this bad?
/// PEP 8 recommends 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;
impl AlwaysFixableViolation for BlankLinesBeforeNestedDefinition {
#[derive_message_formats]
fn message(&self) -> String {
format!("Expected 1 blank line before a nested definition, found 0")
}
fn fix_title(&self) -> String {
"Add missing blank line".to_string()
}
}
#[derive(Debug)]
struct LogicalLineInfo {
kind: LogicalLineKind,
first_token_range: TextRange,
// The token's kind right before the newline ending the logical line.
last_token: TokenKind,
// The end of the logical line including the newline.
logical_line_end: TextSize,
// `true` if this is not a blank but only consists of a comment.
is_comment_only: bool,
/// `true` if the line is a string only (including trivia tokens) line, which is a docstring if coming right after a class/function definition.
is_docstring: bool,
/// The indentation length in columns. See [`expand_indent`] for the computation of the indent.
indent_length: usize,
/// The number of blank lines preceding the current line.
blank_lines: BlankLines,
/// The maximum number of consecutive blank lines between the current line
/// and the previous non-comment logical line.
/// One of its main uses is to allow a comments to directly precede or follow a class/function definition.
/// As such, `preceding_blank_lines` is used for rules that cannot trigger on comments (all rules except E303),
/// and `blank_lines` is used for the rule that can trigger on comments (E303).
preceding_blank_lines: BlankLines,
}
/// Iterator that processes tokens until a full logical line (or comment line) is "built".
/// It then returns characteristics of that logical line (see `LogicalLineInfo`).
struct LinePreprocessor<'a> {
tokens: Iter<'a, Result<(Tok, TextRange), LexicalError>>,
locator: &'a Locator<'a>,
indent_width: IndentWidth,
/// The start position of the next logical line.
line_start: TextSize,
/// Maximum number of consecutive blank lines between the current line and the previous non-comment logical line.
/// One of its main uses is to allow a comment to directly precede a class/function definition.
max_preceding_blank_lines: BlankLines,
}
impl<'a> LinePreprocessor<'a> {
fn new(
tokens: &'a [LexResult],
locator: &'a Locator,
indent_width: IndentWidth,
) -> LinePreprocessor<'a> {
LinePreprocessor {
tokens: tokens.iter(),
locator,
line_start: TextSize::new(0),
max_preceding_blank_lines: BlankLines::Zero,
indent_width,
}
}
}
impl<'a> Iterator for LinePreprocessor<'a> {
type Item = LogicalLineInfo;
fn next(&mut self) -> Option<LogicalLineInfo> {
let mut line_is_comment_only = true;
let mut is_docstring = false;
// Number of consecutive blank lines directly preceding this logical line.
let mut blank_lines = BlankLines::Zero;
let mut first_logical_line_token: Option<(LogicalLineKind, TextRange)> = None;
let mut last_token: TokenKind = TokenKind::EndOfFile;
let mut parens = 0u32;
while let Some(result) = self.tokens.next() {
let Ok((token, range)) = result else {
continue;
};
if matches!(token, Tok::Indent | Tok::Dedent) {
continue;
}
let token_kind = TokenKind::from_token(token);
let (logical_line_kind, first_token_range) = if let Some(first_token_range) =
first_logical_line_token
{
first_token_range
}
// At the start of the line...
else {
// An empty line
if token_kind == TokenKind::NonLogicalNewline {
blank_lines.add(*range);
self.line_start = range.end();
continue;
}
is_docstring = token_kind == TokenKind::String;
let logical_line_kind = match token_kind {
TokenKind::Class => LogicalLineKind::Class,
TokenKind::Comment => LogicalLineKind::Comment,
TokenKind::At => LogicalLineKind::Decorator,
TokenKind::Def => LogicalLineKind::Function,
// Lookahead to distinguish `async def` from `async with`.
TokenKind::Async
if matches!(self.tokens.as_slice().first(), Some(Ok((Tok::Def, _)))) =>
{
LogicalLineKind::Function
}
_ => LogicalLineKind::Other,
};
first_logical_line_token = Some((logical_line_kind, *range));
(logical_line_kind, *range)
};
if !token_kind.is_trivia() {
line_is_comment_only = false;
}
// A docstring line is composed only of the docstring (TokenKind::String) and trivia tokens.
// (If a comment follows a docstring, we still count the line as a docstring)
if token_kind != TokenKind::String && !token_kind.is_trivia() {
is_docstring = false;
}
match token_kind {
TokenKind::Lbrace | TokenKind::Lpar | TokenKind::Lsqb => {
parens = parens.saturating_add(1);
}
TokenKind::Rbrace | TokenKind::Rpar | TokenKind::Rsqb => {
parens = parens.saturating_sub(1);
}
TokenKind::Newline | TokenKind::NonLogicalNewline if parens == 0 => {
let indent_range = TextRange::new(self.line_start, first_token_range.start());
let indent_length =
expand_indent(self.locator.slice(indent_range), self.indent_width);
self.max_preceding_blank_lines =
self.max_preceding_blank_lines.max(blank_lines);
let logical_line = LogicalLineInfo {
kind: logical_line_kind,
first_token_range,
last_token,
logical_line_end: range.end(),
is_comment_only: line_is_comment_only,
is_docstring,
indent_length,
blank_lines,
preceding_blank_lines: self.max_preceding_blank_lines,
};
// Reset the blank lines after a non-comment only line.
if !line_is_comment_only {
self.max_preceding_blank_lines = BlankLines::Zero;
}
// Set the start for the next logical line.
self.line_start = range.end();
return Some(logical_line);
}
_ => {}
}
last_token = token_kind;
}
None
}
}
#[derive(Clone, Copy, Debug, Default)]
enum BlankLines {
/// No blank lines
#[default]
Zero,
/// One or more blank lines
Many { count: NonZeroU32, range: TextRange },
}
impl BlankLines {
fn add(&mut self, line_range: TextRange) {
match self {
BlankLines::Zero => {
*self = BlankLines::Many {
count: NonZeroU32::MIN,
range: line_range,
}
}
BlankLines::Many { count, range } => {
assert_eq!(range.end(), line_range.start());
*count = count.saturating_add(1);
*range = TextRange::new(range.start(), line_range.end());
}
}
}
fn count(&self) -> u32 {
match self {
BlankLines::Zero => 0,
BlankLines::Many { count, .. } => count.get(),
}
}
fn range(&self) -> Option<TextRange> {
match self {
BlankLines::Zero => None,
BlankLines::Many { range, .. } => Some(*range),
}
}
}
impl PartialEq<u32> for BlankLines {
fn eq(&self, other: &u32) -> bool {
self.partial_cmp(other) == Some(Ordering::Equal)
}
}
impl PartialOrd<u32> for BlankLines {
fn partial_cmp(&self, other: &u32) -> Option<Ordering> {
self.count().partial_cmp(other)
}
}
impl PartialOrd for BlankLines {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for BlankLines {
fn cmp(&self, other: &Self) -> Ordering {
self.count().cmp(&other.count())
}
}
impl PartialEq for BlankLines {
fn eq(&self, other: &Self) -> bool {
self.count() == other.count()
}
}
impl Eq for BlankLines {}
#[derive(Copy, Clone, Debug, Default)]
enum Follows {
#[default]
Other,
Decorator,
Def,
Docstring,
}
#[derive(Copy, Clone, Debug, Default)]
enum Status {
/// Stores the indent level where the nesting started.
Inside(usize),
/// This is used to rectify a Inside switched to a Outside because of a dedented comment.
CommentAfter(usize),
#[default]
Outside,
}
impl Status {
fn update(&mut self, line: &LogicalLineInfo) {
match *self {
Status::Inside(nesting_indent) => {
if line.indent_length <= nesting_indent {
if line.is_comment_only {
*self = Status::CommentAfter(nesting_indent);
} else {
*self = Status::Outside;
}
}
}
Status::CommentAfter(indent) => {
if !line.is_comment_only {
if line.indent_length > indent {
*self = Status::Inside(indent);
} else {
*self = Status::Outside;
}
}
}
Status::Outside => {
// Nothing to do
}
}
}
}
/// Contains variables used for the linting of blank lines.
#[derive(Debug, Default)]
pub(crate) struct BlankLinesChecker {
follows: Follows,
fn_status: Status,
class_status: Status,
/// First line that is not a comment.
is_not_first_logical_line: bool,
/// Used for the fix in case a comment separates two non-comment logical lines to make the comment "stick"
/// to the second line instead of the first.
last_non_comment_line_end: TextSize,
previous_unindented_line_kind: Option<LogicalLineKind>,
}
impl BlankLinesChecker {
/// E301, E302, E303, E304, E305, E306
pub(crate) fn check_lines(
&mut self,
tokens: &[LexResult],
locator: &Locator,
stylist: &Stylist,
indent_width: IndentWidth,
diagnostics: &mut Vec<Diagnostic>,
) {
let mut prev_indent_length: Option<usize> = None;
let line_preprocessor = LinePreprocessor::new(tokens, locator, indent_width);
for logical_line in line_preprocessor {
self.check_line(
&logical_line,
prev_indent_length,
locator,
stylist,
diagnostics,
);
if !logical_line.is_comment_only {
prev_indent_length = Some(logical_line.indent_length);
}
}
}
#[allow(clippy::nonminimal_bool)]
fn check_line(
&mut self,
line: &LogicalLineInfo,
prev_indent_length: Option<usize>,
locator: &Locator,
stylist: &Stylist,
diagnostics: &mut Vec<Diagnostic>,
) {
self.class_status.update(line);
self.fn_status.update(line);
// Don't expect blank lines before the first non comment line.
if self.is_not_first_logical_line {
if line.preceding_blank_lines == 0
// Only applies to methods.
&& matches!(line.kind, LogicalLineKind::Function)
&& matches!(self.class_status, Status::Inside(_))
// The class/parent method's docstring can directly precede the def.
// Allow following a decorator (if there is an error it will be triggered on the first decorator).
&& !matches!(self.follows, Follows::Docstring | Follows::Decorator)
// Do not trigger when the def follows an if/while/etc...
&& prev_indent_length.is_some_and(|prev_indent_length| prev_indent_length >= line.indent_length)
{
// E301
let mut diagnostic =
Diagnostic::new(BlankLineBetweenMethods, line.first_token_range);
diagnostic.set_fix(Fix::safe_edit(Edit::insertion(
stylist.line_ending().to_string(),
locator.line_start(self.last_non_comment_line_end),
)));
diagnostics.push(diagnostic);
}
if line.preceding_blank_lines < BLANK_LINES_TOP_LEVEL
// Allow following a decorator (if there is an error it will be triggered on the first decorator).
&& !matches!(self.follows, Follows::Decorator)
// Allow groups of one-liners.
&& !(matches!(self.follows, Follows::Def) && !matches!(line.last_token, TokenKind::Colon))
// Only trigger on non-indented classes and functions (for example functions within an if are ignored)
&& line.indent_length == 0
// Only apply to functions or classes.
&& line.kind.is_top_level()
{
// E302
let mut diagnostic = Diagnostic::new(
BlankLinesTopLevel {
actual_blank_lines: line.preceding_blank_lines.count(),
},
line.first_token_range,
);
if let Some(blank_lines_range) = line.blank_lines.range() {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
stylist.line_ending().repeat(BLANK_LINES_TOP_LEVEL as usize),
blank_lines_range,
)));
} else {
diagnostic.set_fix(Fix::safe_edit(Edit::insertion(
stylist.line_ending().repeat(BLANK_LINES_TOP_LEVEL as usize),
locator.line_start(self.last_non_comment_line_end),
)));
}
diagnostics.push(diagnostic);
}
let expected_blank_lines = if line.indent_length > 0 {
BLANK_LINES_METHOD_LEVEL
} else {
BLANK_LINES_TOP_LEVEL
};
if line.blank_lines > expected_blank_lines {
// E303
let mut diagnostic = Diagnostic::new(
TooManyBlankLines {
actual_blank_lines: line.blank_lines.count(),
},
line.first_token_range,
);
if let Some(blank_lines_range) = line.blank_lines.range() {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
stylist.line_ending().repeat(expected_blank_lines as usize),
blank_lines_range,
)));
}
diagnostics.push(diagnostic);
}
if matches!(self.follows, Follows::Decorator)
&& !line.is_comment_only
&& line.preceding_blank_lines > 0
{
// E304
let mut diagnostic = Diagnostic::new(
BlankLineAfterDecorator {
actual_blank_lines: line.preceding_blank_lines.count(),
},
line.first_token_range,
);
// Get all the lines between the last decorator line (included) and the current line (included).
// Then remove all blank lines.
let trivia_range = TextRange::new(
self.last_non_comment_line_end,
locator.line_start(line.first_token_range.start()),
);
let trivia_text = locator.slice(trivia_range);
let mut trivia_without_blank_lines = trivia_text
.universal_newlines()
.filter_map(|line| {
(!line.trim_whitespace().is_empty()).then_some(line.as_str())
})
.join(&stylist.line_ending());
let fix = if trivia_without_blank_lines.is_empty() {
Fix::safe_edit(Edit::range_deletion(trivia_range))
} else {
trivia_without_blank_lines.push_str(&stylist.line_ending());
Fix::safe_edit(Edit::range_replacement(
trivia_without_blank_lines,
trivia_range,
))
};
diagnostic.set_fix(fix);
diagnostics.push(diagnostic);
}
if line.preceding_blank_lines < BLANK_LINES_TOP_LEVEL
&& self
.previous_unindented_line_kind
.is_some_and(LogicalLineKind::is_top_level)
&& line.indent_length == 0
&& !line.is_comment_only
&& !line.kind.is_top_level()
{
// E305
let mut diagnostic = Diagnostic::new(
BlankLinesAfterFunctionOrClass {
actual_blank_lines: line.preceding_blank_lines.count(),
},
line.first_token_range,
);
if let Some(blank_lines_range) = line.blank_lines.range() {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
stylist.line_ending().repeat(BLANK_LINES_TOP_LEVEL as usize),
blank_lines_range,
)));
} else {
diagnostic.set_fix(Fix::safe_edit(Edit::insertion(
stylist.line_ending().repeat(BLANK_LINES_TOP_LEVEL as usize),
locator.line_start(line.first_token_range.start()),
)));
}
diagnostics.push(diagnostic);
}
if line.preceding_blank_lines == 0
// Only apply to nested functions.
&& matches!(self.fn_status, Status::Inside(_))
&& line.kind.is_top_level()
// Allow following a decorator (if there is an error it will be triggered on the first decorator).
&& !matches!(self.follows, Follows::Decorator)
// The class's docstring can directly precede the first function.
&& !matches!(self.follows, Follows::Docstring)
// Do not trigger when the def/class follows an "indenting token" (if/while/etc...).
&& prev_indent_length.is_some_and(|prev_indent_length| prev_indent_length >= line.indent_length)
// Allow groups of one-liners.
&& !(matches!(self.follows, Follows::Def) && line.last_token != TokenKind::Colon)
{
// E306
let mut diagnostic =
Diagnostic::new(BlankLinesBeforeNestedDefinition, line.first_token_range);
diagnostic.set_fix(Fix::safe_edit(Edit::insertion(
stylist.line_ending().to_string(),
locator.line_start(line.first_token_range.start()),
)));
diagnostics.push(diagnostic);
}
}
match line.kind {
LogicalLineKind::Class => {
if matches!(self.class_status, Status::Outside) {
self.class_status = Status::Inside(line.indent_length);
}
self.follows = Follows::Other;
}
LogicalLineKind::Decorator => {
self.follows = Follows::Decorator;
}
LogicalLineKind::Function => {
if matches!(self.fn_status, Status::Outside) {
self.fn_status = Status::Inside(line.indent_length);
}
self.follows = Follows::Def;
}
LogicalLineKind::Comment => {}
LogicalLineKind::Other => {
self.follows = Follows::Other;
}
}
if line.is_docstring {
self.follows = Follows::Docstring;
}
if !line.is_comment_only {
self.is_not_first_logical_line = true;
self.last_non_comment_line_end = line.logical_line_end;
if line.indent_length == 0 {
self.previous_unindented_line_kind = Some(line.kind);
}
}
}
}
#[derive(Copy, Clone, Debug)]
enum LogicalLineKind {
/// The clause header of a class definition
Class,
/// A decorator
Decorator,
/// The clause header of a function
Function,
/// A comment only line
Comment,
/// Any other statement or clause header
Other,
}
impl LogicalLineKind {
fn is_top_level(self) -> bool {
matches!(
self,
LogicalLineKind::Class | LogicalLineKind::Function | LogicalLineKind::Decorator
)
}
}

View file

@ -2,6 +2,7 @@ pub(crate) use ambiguous_class_name::*;
pub(crate) use ambiguous_function_name::*;
pub(crate) use ambiguous_variable_name::*;
pub(crate) use bare_except::*;
pub(crate) use blank_lines::*;
pub(crate) use compound_statements::*;
pub(crate) use doc_line_too_long::*;
pub(crate) use errors::*;
@ -23,6 +24,7 @@ mod ambiguous_class_name;
mod ambiguous_function_name;
mod ambiguous_variable_name;
mod bare_except;
mod blank_lines;
mod compound_statements;
mod doc_line_too_long;
mod errors;

View file

@ -0,0 +1,44 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E30.py:444:5: E301 [*] Expected 1 blank line, found 0
|
442 | def func1():
443 | pass
444 | def func2():
| ^^^ E301
445 | pass
446 | # end
|
= help: Add missing blank line
Safe fix
441 441 |
442 442 | def func1():
443 443 | pass
444 |+
444 445 | def func2():
445 446 | pass
446 447 | # end
E30.py:455:5: E301 [*] Expected 1 blank line, found 0
|
453 | pass
454 | # comment
455 | def fn2():
| ^^^ E301
456 | pass
457 | # end
|
= help: Add missing blank line
Safe fix
451 451 |
452 452 | def fn1():
453 453 | pass
454 |+
454 455 | # comment
455 456 | def fn2():
456 457 | pass

View file

@ -0,0 +1,187 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E30.py:462:1: E302 [*] Expected 2 blank lines, found 0
|
460 | # E302
461 | """Main module."""
462 | def fn():
| ^^^ E302
463 | pass
464 | # end
|
= help: Add missing blank line(s)
Safe fix
459 459 |
460 460 | # E302
461 461 | """Main module."""
462 |+
463 |+
462 464 | def fn():
463 465 | pass
464 466 | # end
E30.py:469:1: E302 [*] Expected 2 blank lines, found 0
|
467 | # E302
468 | import sys
469 | def get_sys_path():
| ^^^ E302
470 | return sys.path
471 | # end
|
= help: Add missing blank line(s)
Safe fix
466 466 |
467 467 | # E302
468 468 | import sys
469 |+
470 |+
469 471 | def get_sys_path():
470 472 | return sys.path
471 473 | # end
E30.py:478:1: E302 [*] Expected 2 blank lines, found 1
|
476 | pass
477 |
478 | def b():
| ^^^ E302
479 | pass
480 | # end
|
= help: Add missing blank line(s)
Safe fix
475 475 | def a():
476 476 | pass
477 477 |
478 |+
478 479 | def b():
479 480 | pass
480 481 | # end
E30.py:489:1: E302 [*] Expected 2 blank lines, found 1
|
487 | # comment
488 |
489 | def b():
| ^^^ E302
490 | pass
491 | # end
|
= help: Add missing blank line(s)
Safe fix
486 486 |
487 487 | # comment
488 488 |
489 |+
489 490 | def b():
490 491 | pass
491 492 | # end
E30.py:498:1: E302 [*] Expected 2 blank lines, found 1
|
496 | pass
497 |
498 | async def b():
| ^^^^^ E302
499 | pass
500 | # end
|
= help: Add missing blank line(s)
Safe fix
495 495 | def a():
496 496 | pass
497 497 |
498 |+
498 499 | async def b():
499 500 | pass
500 501 | # end
E30.py:507:1: E302 [*] Expected 2 blank lines, found 1
|
505 | pass
506 |
507 | async def x(y: int = 1):
| ^^^^^ E302
508 | pass
509 | # end
|
= help: Add missing blank line(s)
Safe fix
504 504 | async def x():
505 505 | pass
506 506 |
507 |+
507 508 | async def x(y: int = 1):
508 509 | pass
509 510 | # end
E30.py:515:1: E302 [*] Expected 2 blank lines, found 0
|
513 | def bar():
514 | pass
515 | def baz(): pass
| ^^^ E302
516 | # end
|
= help: Add missing blank line(s)
Safe fix
512 512 | # E302
513 513 | def bar():
514 514 | pass
515 |+
516 |+
515 517 | def baz(): pass
516 518 | # end
517 519 |
E30.py:521:1: E302 [*] Expected 2 blank lines, found 0
|
519 | # E302
520 | def bar(): pass
521 | def baz():
| ^^^ E302
522 | pass
523 | # end
|
= help: Add missing blank line(s)
Safe fix
518 518 |
519 519 | # E302
520 520 | def bar(): pass
521 |+
522 |+
521 523 | def baz():
522 524 | pass
523 525 | # end
E30.py:531:1: E302 [*] Expected 2 blank lines, found 1
|
530 | # comment
531 | @decorator
| ^ E302
532 | def g():
533 | pass
|
= help: Add missing blank line(s)
Safe fix
527 527 | def f():
528 528 | pass
529 529 |
530 |+
531 |+
530 532 | # comment
531 533 | @decorator
532 534 | def g():

View file

@ -0,0 +1,215 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E30.py:542:5: E303 [*] Too many blank lines (2)
|
542 | # arbitrary comment
| ^^^^^^^^^^^^^^^^^^^ E303
543 |
544 | def inner(): # E306 not expected (pycodestyle detects E306)
|
= help: Remove extraneous blank line(s)
Safe fix
538 538 | def fn():
539 539 | _ = None
540 540 |
541 |-
542 541 | # arbitrary comment
543 542 |
544 543 | def inner(): # E306 not expected (pycodestyle detects E306)
E30.py:554:5: E303 [*] Too many blank lines (2)
|
554 | # arbitrary comment
| ^^^^^^^^^^^^^^^^^^^ E303
555 | def inner(): # E306 not expected (pycodestyle detects E306)
556 | pass
|
= help: Remove extraneous blank line(s)
Safe fix
550 550 | def fn():
551 551 | _ = None
552 552 |
553 |-
554 553 | # arbitrary comment
555 554 | def inner(): # E306 not expected (pycodestyle detects E306)
556 555 | pass
E30.py:565:1: E303 [*] Too many blank lines (3)
|
565 | print()
| ^^^^^ E303
566 | # end
|
= help: Remove extraneous blank line(s)
Safe fix
561 561 | print()
562 562 |
563 563 |
564 |-
565 564 | print()
566 565 | # end
567 566 |
E30.py:574:1: E303 [*] Too many blank lines (3)
|
574 | # comment
| ^^^^^^^^^ E303
575 |
576 | print()
|
= help: Remove extraneous blank line(s)
Safe fix
570 570 | print()
571 571 |
572 572 |
573 |-
574 573 | # comment
575 574 |
576 575 | print()
E30.py:585:5: E303 [*] Too many blank lines (2)
|
585 | # comment
| ^^^^^^^^^ E303
|
= help: Remove extraneous blank line(s)
Safe fix
581 581 | def a():
582 582 | print()
583 583 |
584 |-
585 584 | # comment
586 585 |
587 586 |
E30.py:588:5: E303 [*] Too many blank lines (2)
|
588 | # another comment
| ^^^^^^^^^^^^^^^^^ E303
589 |
590 | print()
|
= help: Remove extraneous blank line(s)
Safe fix
584 584 |
585 585 | # comment
586 586 |
587 |-
588 587 | # another comment
589 588 |
590 589 | print()
E30.py:599:1: E303 [*] Too many blank lines (3)
|
599 | / """This class docstring comes on line 5.
600 | | It gives error E303: too many blank lines (3)
601 | | """
| |___^ E303
602 | # end
|
= help: Remove extraneous blank line(s)
Safe fix
595 595 | #!python
596 596 |
597 597 |
598 |-
599 598 | """This class docstring comes on line 5.
600 599 | It gives error E303: too many blank lines (3)
601 600 | """
E30.py:611:5: E303 [*] Too many blank lines (2)
|
611 | def b(self):
| ^^^ E303
612 | pass
613 | # end
|
= help: Remove extraneous blank line(s)
Safe fix
607 607 | def a(self):
608 608 | pass
609 609 |
610 |-
611 610 | def b(self):
612 611 | pass
613 612 | # end
E30.py:621:5: E303 [*] Too many blank lines (2)
|
621 | a = 2
| ^ E303
622 | # end
|
= help: Remove extraneous blank line(s)
Safe fix
617 617 | if True:
618 618 | a = 1
619 619 |
620 |-
621 620 | a = 2
622 621 | # end
623 622 |
E30.py:629:5: E303 [*] Too many blank lines (2)
|
629 | # comment
| ^^^^^^^^^ E303
|
= help: Remove extraneous blank line(s)
Safe fix
625 625 | # E303
626 626 | class Test:
627 627 |
628 |-
629 628 | # comment
630 629 |
631 630 |
E30.py:632:5: E303 [*] Too many blank lines (2)
|
632 | # another comment
| ^^^^^^^^^^^^^^^^^ E303
633 |
634 | def test(self): pass
|
= help: Remove extraneous blank line(s)
Safe fix
628 628 |
629 629 | # comment
630 630 |
631 |-
632 631 | # another comment
633 632 |
634 633 | def test(self): pass
E30.py:646:5: E303 [*] Too many blank lines (2)
|
646 | def b(self):
| ^^^ E303
647 | pass
648 | # end
|
= help: Remove extraneous blank line(s)
Safe fix
642 642 |
643 643 | # wrongly indented comment
644 644 |
645 |-
646 645 | def b(self):
647 646 | pass
648 647 | # end

View file

@ -0,0 +1,65 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E30.py:654:1: E304 [*] Blank lines found after function decorator (1)
|
652 | @decorator
653 |
654 | def function():
| ^^^ E304
655 | pass
656 | # end
|
= help: Remove extraneous blank line(s)
Safe fix
650 650 |
651 651 | # E304
652 652 | @decorator
653 |-
654 653 | def function():
655 654 | pass
656 655 | # end
E30.py:663:1: E304 [*] Blank lines found after function decorator (1)
|
662 | # comment E304 not expected
663 | def function():
| ^^^ E304
664 | pass
665 | # end
|
= help: Remove extraneous blank line(s)
Safe fix
658 658 |
659 659 | # E304
660 660 | @decorator
661 |-
662 661 | # comment E304 not expected
663 662 | def function():
664 663 | pass
E30.py:675:1: E304 [*] Blank lines found after function decorator (2)
|
674 | # second comment E304 not expected
675 | def function():
| ^^^ E304
676 | pass
677 | # end
|
= help: Remove extraneous blank line(s)
Safe fix
667 667 |
668 668 | # E304
669 669 | @decorator
670 |-
671 670 | # comment E304 not expected
672 |-
673 |-
674 671 | # second comment E304 not expected
675 672 | def function():
676 673 | pass

View file

@ -0,0 +1,102 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E30.py:687:1: E305 [*] Expected 2 blank lines after class or function definition, found (1)
|
686 | # another comment
687 | fn()
| ^^ E305
688 | # end
|
= help: Add missing blank line(s)
Safe fix
684 684 | # comment
685 685 |
686 686 | # another comment
687 |+
688 |+
687 689 | fn()
688 690 | # end
689 691 |
E30.py:698:1: E305 [*] Expected 2 blank lines after class or function definition, found (1)
|
697 | # another comment
698 | a = 1
| ^ E305
699 | # end
|
= help: Add missing blank line(s)
Safe fix
695 695 | # comment
696 696 |
697 697 | # another comment
698 |+
699 |+
698 700 | a = 1
699 701 | # end
700 702 |
E30.py:710:1: E305 [*] Expected 2 blank lines after class or function definition, found (1)
|
708 | # another comment
709 |
710 | try:
| ^^^ E305
711 | fn()
712 | except Exception:
|
= help: Add missing blank line(s)
Safe fix
707 707 |
708 708 | # another comment
709 709 |
710 |+
710 711 | try:
711 712 | fn()
712 713 | except Exception:
E30.py:722:1: E305 [*] Expected 2 blank lines after class or function definition, found (1)
|
721 | # Two spaces before comments, too.
722 | if a():
| ^^ E305
723 | a()
724 | # end
|
= help: Add missing blank line(s)
Safe fix
719 719 | print()
720 720 |
721 721 | # Two spaces before comments, too.
722 |+
723 |+
722 724 | if a():
723 725 | a()
724 726 | # end
E30.py:735:1: E305 [*] Expected 2 blank lines after class or function definition, found (1)
|
733 | blah, blah
734 |
735 | if __name__ == '__main__':
| ^^ E305
736 | main()
737 | # end
|
= help: Add missing blank line(s)
Safe fix
732 732 | def main():
733 733 | blah, blah
734 734 |
735 |+
735 736 | if __name__ == '__main__':
736 737 | main()
737 738 | # end

View file

@ -0,0 +1,223 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E30.py:743:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
741 | def a():
742 | x = 1
743 | def b():
| ^^^ E306
744 | pass
745 | # end
|
= help: Add missing blank line
Safe fix
740 740 | # E306:3:5
741 741 | def a():
742 742 | x = 1
743 |+
743 744 | def b():
744 745 | pass
745 746 | # end
E30.py:751:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
749 | async def a():
750 | x = 1
751 | def b():
| ^^^ E306
752 | pass
753 | # end
|
= help: Add missing blank line
Safe fix
748 748 | #: E306:3:5
749 749 | async def a():
750 750 | x = 1
751 |+
751 752 | def b():
752 753 | pass
753 754 | # end
E30.py:759:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
757 | def a():
758 | x = 2
759 | def b():
| ^^^ E306
760 | x = 1
761 | def c():
|
= help: Add missing blank line
Safe fix
756 756 | #: E306:3:5 E306:5:9
757 757 | def a():
758 758 | x = 2
759 |+
759 760 | def b():
760 761 | x = 1
761 762 | def c():
E30.py:761:9: E306 [*] Expected 1 blank line before a nested definition, found 0
|
759 | def b():
760 | x = 1
761 | def c():
| ^^^ E306
762 | pass
763 | # end
|
= help: Add missing blank line
Safe fix
758 758 | x = 2
759 759 | def b():
760 760 | x = 1
761 |+
761 762 | def c():
762 763 | pass
763 764 | # end
E30.py:769:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
767 | def a():
768 | x = 1
769 | class C:
| ^^^^^ E306
770 | pass
771 | x = 2
|
= help: Add missing blank line
Safe fix
766 766 | # E306:3:5 E306:6:5
767 767 | def a():
768 768 | x = 1
769 |+
769 770 | class C:
770 771 | pass
771 772 | x = 2
E30.py:772:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
770 | pass
771 | x = 2
772 | def b():
| ^^^ E306
773 | pass
774 | # end
|
= help: Add missing blank line
Safe fix
769 769 | class C:
770 770 | pass
771 771 | x = 2
772 |+
772 773 | def b():
773 774 | pass
774 775 | # end
E30.py:781:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
779 | def bar():
780 | pass
781 | def baz(): pass
| ^^^ E306
782 | # end
|
= help: Add missing blank line
Safe fix
778 778 | def foo():
779 779 | def bar():
780 780 | pass
781 |+
781 782 | def baz(): pass
782 783 | # end
783 784 |
E30.py:788:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
786 | def foo():
787 | def bar(): pass
788 | def baz():
| ^^^ E306
789 | pass
790 | # end
|
= help: Add missing blank line
Safe fix
785 785 | # E306:3:5
786 786 | def foo():
787 787 | def bar(): pass
788 |+
788 789 | def baz():
789 790 | pass
790 791 | # end
E30.py:796:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
794 | def a():
795 | x = 2
796 | @decorator
| ^ E306
797 | def b():
798 | pass
|
= help: Add missing blank line
Safe fix
793 793 | # E306
794 794 | def a():
795 795 | x = 2
796 |+
796 797 | @decorator
797 798 | def b():
798 799 | pass
E30.py:805:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
803 | def a():
804 | x = 2
805 | @decorator
| ^ E306
806 | async def b():
807 | pass
|
= help: Add missing blank line
Safe fix
802 802 | # E306
803 803 | def a():
804 804 | x = 2
805 |+
805 806 | @decorator
806 807 | async def b():
807 808 | pass
E30.py:814:5: E306 [*] Expected 1 blank line before a nested definition, found 0
|
812 | def a():
813 | x = 2
814 | async def b():
| ^^^^^ E306
815 | pass
816 | # end
|
= help: Add missing blank line
Safe fix
811 811 | # E306
812 812 | def a():
813 813 | x = 2
814 |+
814 815 | async def b():
815 816 | pass
816 817 | # end

View file

@ -1483,6 +1483,12 @@ mod tests {
Rule::UnnecessaryEnumerate,
Rule::MathConstant,
Rule::PreviewTestRule,
Rule::BlankLineBetweenMethods,
Rule::BlankLinesTopLevel,
Rule::TooManyBlankLines,
Rule::BlankLineAfterDecorator,
Rule::BlankLinesAfterFunctionOrClass,
Rule::BlankLinesBeforeNestedDefinition,
];
#[allow(clippy::needless_pass_by_value)]

8
ruff.schema.json generated
View file

@ -2838,6 +2838,14 @@
"E273",
"E274",
"E275",
"E3",
"E30",
"E301",
"E302",
"E303",
"E304",
"E305",
"E306",
"E4",
"E40",
"E401",

View file

@ -32,6 +32,11 @@ KNOWN_FORMATTING_VIOLATIONS = [
"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",
"indent-with-spaces",
"indentation-with-invalid-multiple",
@ -68,6 +73,7 @@ KNOWN_FORMATTING_VIOLATIONS = [
"surrounding-whitespace",
"tab-indentation",
"too-few-spaces-before-inline-comment",
"too-many-blank-lines",
"too-many-boolean-expressions",
"trailing-comma-on-bare-tuple",
"triple-single-quotes",