mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-26 22:52:32 +00:00
[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:
parent
688177ff6a
commit
9027169125
18 changed files with 2616 additions and 3 deletions
816
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.py
vendored
Normal file
816
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.py
vendored
Normal 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
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ pub fn check_path(
|
|||
path,
|
||||
locator,
|
||||
indexer,
|
||||
stylist,
|
||||
settings,
|
||||
source_type,
|
||||
source_kind.as_ipy_notebook().map(Notebook::cell_offsets),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
896
crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs
Normal file
896
crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs
Normal 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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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():
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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
8
ruff.schema.json
generated
|
|
@ -2838,6 +2838,14 @@
|
|||
"E273",
|
||||
"E274",
|
||||
"E275",
|
||||
"E3",
|
||||
"E30",
|
||||
"E301",
|
||||
"E302",
|
||||
"E303",
|
||||
"E304",
|
||||
"E305",
|
||||
"E306",
|
||||
"E4",
|
||||
"E40",
|
||||
"E401",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue