[pycodestyle] Respect isort settings in blank line rules (E3*) (#10096)

## Summary

This PR changes the `E3*` rules to respect the `isort`
`lines-after-imports` and `lines-between-types` settings. Specifically,
the following rules required changing

* `TooManyBlannkLines` : Respects both settings.
* `BlankLinesTopLevel`: Respects `lines-after-imports`. Doesn't need to
respect `lines-between-types` because it only applies to classes and
functions


The downside of this approach is that `isort` and the blank line rules
emit a diagnostic when there are too many blank lines. The fixes aren't
identical, the blank line is less opinionated, but blank lines accepts
the fix of `isort`.

<details>
	<summary>Outdated approach</summary>
Fixes
https://github.com/astral-sh/ruff/issues/10077#issuecomment-1961266981

This PR changes the blank line rules to not enforce the number of blank
lines after imports (top-level) if isort is enabled and leave it to
isort to enforce the right number of lines (depends on the
`isort.lines-after-imports` and `isort.lines-between-types` settings).

The reason to give `isort` precedence over the blank line rules is that
they are configurable. Users that always want to blank lines after
imports can use `isort.lines-after-imports=2` to enforce that
(specifically for imports).

This PR does not fix the incompatibility with the formatter in pyi files
that only uses 0 to 1 blank lines. I'll address this separately.

</details>

## Review
The first commit is a small refactor that simplified implementing the
fix (and makes it easier to reason about what's mutable and what's not).


## Test Plan

I added a new test and verified that it fails with an error that the fix
never converges. I verified the snapshot output after implementing the
fix.

---------

Co-authored-by: Hoël Bagard <34478245+hoel-bagard@users.noreply.github.com>
This commit is contained in:
Micha Reiser 2024-03-05 11:09:15 +01:00 committed by GitHub
parent d441338358
commit 46ab9dec18
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1601 additions and 284 deletions

View file

@ -0,0 +1,8 @@
# These rules test for intentional "odd" formatting. Using a formatter fixes that
[E{1,2,3}*.py]
generated_code = true
ij_formatter_enabled = false
[W*.py]
generated_code = true
ij_formatter_enabled = false

View file

@ -0,0 +1,62 @@
import json
from typing import Any, Sequence
class MissingCommand(TypeError): ... # noqa: N818
class BackendProxy:
backend_module: str
backend_object: str | None
backend: Any
if __name__ == "__main__":
import abcd
abcd.foo()
def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
if TYPE_CHECKING:
import os
from typing_extensions import TypeAlias
abcd.foo()
def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
...
if TYPE_CHECKING:
from typing_extensions import TypeAlias
def __call__2(self, name: str, *args: Any, **kwargs: Any) -> Any:
...
def _exit(self) -> None: ...
def _optional_commands(self) -> dict[str, bool]: ...
def run(argv: Sequence[str]) -> int: ...
def read_line(fd: int = 0) -> bytearray: ...
def flush() -> None: ...
from typing import Any, Sequence
class MissingCommand(TypeError): ... # noqa: N818

View file

@ -41,14 +41,7 @@ pub(crate) fn check_tokens(
Rule::BlankLinesAfterFunctionOrClass,
Rule::BlankLinesBeforeNestedDefinition,
]) {
let mut blank_lines_checker = BlankLinesChecker::default();
blank_lines_checker.check_lines(
tokens,
locator,
stylist,
settings.tab_size,
&mut diagnostics,
);
BlankLinesChecker::new(locator, stylist, settings).check_lines(tokens, &mut diagnostics);
}
if settings.rules.enabled(Rule::BlanketNOQA) {

View file

@ -16,7 +16,7 @@ mod tests {
use crate::line_width::LineLength;
use crate::registry::Rule;
use crate::rules::pycodestyle;
use crate::rules::{isort, pycodestyle};
use crate::settings::types::PreviewMode;
use crate::test::test_path;
use crate::{assert_messages, settings};
@ -138,14 +138,23 @@ mod tests {
Path::new("E25.py")
)]
#[test_case(Rule::MissingWhitespaceAroundParameterEquals, Path::new("E25.py"))]
fn logical(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("pycodestyle").join(path).as_path(),
&settings::LinterSettings::for_rule(rule_code),
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::BlankLineBetweenMethods, Path::new("E30.py"))]
#[test_case(Rule::BlankLinesTopLevel, Path::new("E30.py"))]
#[test_case(Rule::TooManyBlankLines, Path::new("E30.py"))]
#[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<()> {
fn blank_lines(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("pycodestyle").join(path).as_path(),
@ -155,6 +164,64 @@ mod tests {
Ok(())
}
/// Tests the compatibility of the blank line top level rule and isort.
#[test_case(-1, 0)]
#[test_case(1, 1)]
#[test_case(0, 0)]
#[test_case(4, 4)]
fn blank_lines_top_level_isort_compatibility(
lines_after_imports: isize,
lines_between_types: usize,
) -> Result<()> {
let snapshot = format!(
"blank_lines_top_level_isort_compatibility-lines-after({lines_after_imports})-between({lines_between_types})"
);
let diagnostics = test_path(
Path::new("pycodestyle").join("E30_isort.py"),
&settings::LinterSettings {
isort: isort::settings::Settings {
lines_after_imports,
lines_between_types,
..isort::settings::Settings::default()
},
..settings::LinterSettings::for_rules([
Rule::BlankLinesTopLevel,
Rule::UnsortedImports,
])
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
/// Tests the compatibility of the blank line too many lines and isort.
#[test_case(-1, 0)]
#[test_case(1, 1)]
#[test_case(0, 0)]
#[test_case(4, 4)]
fn too_many_blank_lines_isort_compatibility(
lines_after_imports: isize,
lines_between_types: usize,
) -> Result<()> {
let snapshot = format!("too_many_blank_lines_isort_compatibility-lines-after({lines_after_imports})-between({lines_between_types})");
let diagnostics = test_path(
Path::new("pycodestyle").join("E30_isort.py"),
&settings::LinterSettings {
isort: isort::settings::Settings {
lines_after_imports,
lines_between_types,
..isort::settings::Settings::default()
},
..settings::LinterSettings::for_rules([
Rule::TooManyBlankLines,
Rule::UnsortedImports,
])
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
#[test]
fn constant_literals() -> Result<()> {
let diagnostics = test_path(

View file

@ -24,7 +24,7 @@ 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;
const BLANK_LINES_NESTED_LEVEL: u32 = 1;
/// ## What it does
/// Checks for missing blank lines between methods of a class.
@ -60,7 +60,7 @@ pub struct BlankLineBetweenMethods;
impl AlwaysFixableViolation for BlankLineBetweenMethods {
#[derive_message_formats]
fn message(&self) -> String {
format!("Expected {BLANK_LINES_METHOD_LEVEL:?} blank line, found 0")
format!("Expected {BLANK_LINES_NESTED_LEVEL:?} blank line, found 0")
}
fn fix_title(&self) -> String {
@ -74,6 +74,10 @@ impl AlwaysFixableViolation for BlankLineBetweenMethods {
/// ## Why is this bad?
/// PEP 8 recommends exactly two blank lines between top level functions and classes.
///
/// Note: The rule respects the [`lint.isort.lines-after-imports`] setting when determining
/// the required number of blank lines between top-level `import` statements and function or class definitions
/// for compatibility with isort.
///
/// ## Example
/// ```python
/// def func1():
@ -98,16 +102,18 @@ impl AlwaysFixableViolation for BlankLineBetweenMethods {
#[violation]
pub struct BlankLinesTopLevel {
actual_blank_lines: u32,
expected_blank_lines: u32,
}
impl AlwaysFixableViolation for BlankLinesTopLevel {
#[derive_message_formats]
fn message(&self) -> String {
let BlankLinesTopLevel {
actual_blank_lines: nb_blank_lines,
actual_blank_lines,
expected_blank_lines,
} = self;
format!("Expected {BLANK_LINES_TOP_LEVEL:?} blank lines, found {nb_blank_lines}")
format!("Expected {expected_blank_lines:?} blank lines, found {actual_blank_lines}")
}
fn fix_title(&self) -> String {
@ -144,6 +150,10 @@ impl AlwaysFixableViolation for BlankLinesTopLevel {
/// pass
/// ```
///
/// Note: The rule respects the following `isort` settings when determining the maximum number of blank lines allowed between two statements:
/// * [`lint.isort.lines-after-imports`]: For top-level statements directly following an import statement.
/// * [`lint.isort.lines-between-types`]: For `import` statements directly following a `from ... import ...` statement or vice versa.
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E303.html)
@ -155,10 +165,9 @@ pub struct TooManyBlankLines {
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})")
let TooManyBlankLines { actual_blank_lines } = self;
format!("Too many blank lines ({actual_blank_lines})")
}
fn fix_title(&self) -> String {
@ -415,6 +424,8 @@ impl<'a> Iterator for LinePreprocessor<'a> {
{
LogicalLineKind::Function
}
TokenKind::Import => LogicalLineKind::Import,
TokenKind::From => LogicalLineKind::FromImport,
_ => LogicalLineKind::Other,
};
@ -560,9 +571,17 @@ enum Follows {
Other,
Decorator,
Def,
Import,
FromImport,
Docstring,
}
impl Follows {
const fn is_any_import(self) -> bool {
matches!(self, Follows::Import | Follows::FromImport)
}
}
#[derive(Copy, Clone, Debug, Default)]
enum Status {
/// Stores the indent level where the nesting started.
@ -602,40 +621,99 @@ impl Status {
}
/// 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>,
#[derive(Debug)]
pub(crate) struct BlankLinesChecker<'a> {
stylist: &'a Stylist<'a>,
locator: &'a Locator<'a>,
indent_width: IndentWidth,
lines_after_imports: isize,
lines_between_types: usize,
}
impl BlankLinesChecker {
impl<'a> BlankLinesChecker<'a> {
pub(crate) fn new(
locator: &'a Locator<'a>,
stylist: &'a Stylist<'a>,
settings: &crate::settings::LinterSettings,
) -> BlankLinesChecker<'a> {
BlankLinesChecker {
stylist,
locator,
indent_width: settings.tab_size,
lines_after_imports: settings.isort.lines_after_imports,
lines_between_types: settings.isort.lines_between_types,
}
}
/// 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>,
) {
pub(crate) fn check_lines(&self, tokens: &[LexResult], diagnostics: &mut Vec<Diagnostic>) {
let mut prev_indent_length: Option<usize> = None;
let line_preprocessor = LinePreprocessor::new(tokens, locator, indent_width);
let mut state = BlankLinesState::default();
let line_preprocessor = LinePreprocessor::new(tokens, self.locator, self.indent_width);
for logical_line in line_preprocessor {
self.check_line(
&logical_line,
prev_indent_length,
locator,
stylist,
diagnostics,
);
// Reset `follows` after a dedent:
// ```python
// if True:
// import test
// a = 10
// ```
// The `a` statement doesn't follow the `import` statement but the `if` statement.
if let Some(prev_indent_length) = prev_indent_length {
if prev_indent_length > logical_line.indent_length {
state.follows = Follows::Other;
}
}
state.class_status.update(&logical_line);
state.fn_status.update(&logical_line);
if state.is_not_first_logical_line {
self.check_line(&logical_line, &state, prev_indent_length, diagnostics);
}
match logical_line.kind {
LogicalLineKind::Class => {
if matches!(state.class_status, Status::Outside) {
state.class_status = Status::Inside(logical_line.indent_length);
}
state.follows = Follows::Other;
}
LogicalLineKind::Decorator => {
state.follows = Follows::Decorator;
}
LogicalLineKind::Function => {
if matches!(state.fn_status, Status::Outside) {
state.fn_status = Status::Inside(logical_line.indent_length);
}
state.follows = Follows::Def;
}
LogicalLineKind::Comment => {}
LogicalLineKind::Import => {
state.follows = Follows::Import;
}
LogicalLineKind::FromImport => {
state.follows = Follows::FromImport;
}
LogicalLineKind::Other => {
state.follows = Follows::Other;
}
}
if logical_line.is_docstring {
state.follows = Follows::Docstring;
}
if !logical_line.is_comment_only {
state.is_not_first_logical_line = true;
state.last_non_comment_line_end = logical_line.logical_line_end;
if logical_line.indent_length == 0 {
state.previous_unindented_line_kind = Some(logical_line.kind);
}
}
if !logical_line.is_comment_only {
prev_indent_length = Some(logical_line.indent_length);
}
@ -644,81 +722,109 @@ impl BlankLinesChecker {
#[allow(clippy::nonminimal_bool)]
fn check_line(
&mut self,
&self,
line: &LogicalLineInfo,
state: &BlankLinesState,
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 | LogicalLineKind::Decorator)
// Allow groups of one-liners.
&& !(matches!(self.follows, Follows::Def) && !matches!(line.last_token, TokenKind::Colon))
&& matches!(self.class_status, Status::Inside(_))
&& !(matches!(state.follows, Follows::Def) && !matches!(line.last_token, TokenKind::Colon))
&& matches!(state.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)
&& !matches!(state.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);
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),
self.stylist.line_ending().to_string(),
self.locator.line_start(state.last_non_comment_line_end),
)));
diagnostics.push(diagnostic);
}
if line.preceding_blank_lines < BLANK_LINES_TOP_LEVEL
let expected_blank_lines_before_definition = if line.indent_length == 0 {
// Mimic the isort rules for the number of blank lines before classes and functions
if state.follows.is_any_import() {
// Fallback to the default if the value is too large for an u32 or if it is negative.
// A negative value means that isort should determine the blank lines automatically.
// `isort` defaults to 2 if before a class or function definition and 1 otherwise.
// Defaulting to 2 here is correct because the variable is only used when testing the
// blank lines before a class or function definition.
u32::try_from(self.lines_after_imports).unwrap_or(BLANK_LINES_TOP_LEVEL)
} else {
BLANK_LINES_TOP_LEVEL
}
} else {
BLANK_LINES_NESTED_LEVEL
};
if line.preceding_blank_lines < expected_blank_lines_before_definition
// Allow following a decorator (if there is an error it will be triggered on the first decorator).
&& !matches!(self.follows, Follows::Decorator)
&& !matches!(state.follows, Follows::Decorator)
// Allow groups of one-liners.
&& !(matches!(self.follows, Follows::Def) && !matches!(line.last_token, TokenKind::Colon))
&& !(matches!(state.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()
&& line.kind.is_class_function_or_decorator()
{
// E302
let mut diagnostic = Diagnostic::new(
BlankLinesTopLevel {
actual_blank_lines: line.preceding_blank_lines.count(),
expected_blank_lines: expected_blank_lines_before_definition,
},
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),
self.stylist
.line_ending()
.repeat(expected_blank_lines_before_definition 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),
self.stylist
.line_ending()
.repeat(expected_blank_lines_before_definition as usize),
self.locator.line_start(state.last_non_comment_line_end),
)));
}
diagnostics.push(diagnostic);
}
let expected_blank_lines = if line.indent_length > 0 {
BLANK_LINES_METHOD_LEVEL
} else {
let max_lines_level = if line.indent_length == 0 {
BLANK_LINES_TOP_LEVEL
} else {
BLANK_LINES_NESTED_LEVEL
};
if line.blank_lines > expected_blank_lines {
// If between `import` and `from .. import ..` or the other way round,
// allow up to `lines_between_types` newlines for isort compatibility.
// We let `isort` remove extra blank lines when the imports belong
// to different sections.
let max_blank_lines = if matches!(
(line.kind, state.follows),
(LogicalLineKind::Import, Follows::FromImport)
| (LogicalLineKind::FromImport, Follows::Import)
) {
max_lines_level.max(u32::try_from(self.lines_between_types).unwrap_or(u32::MAX))
} else {
expected_blank_lines_before_definition
};
if line.blank_lines > max_blank_lines {
// E303
let mut diagnostic = Diagnostic::new(
TooManyBlankLines {
@ -728,16 +834,20 @@ impl BlankLinesChecker {
);
if let Some(blank_lines_range) = line.blank_lines.range() {
if max_blank_lines == 0 {
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(blank_lines_range)));
} else {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
stylist.line_ending().repeat(expected_blank_lines as usize),
self.stylist.line_ending().repeat(max_blank_lines as usize),
blank_lines_range,
)));
}
}
diagnostics.push(diagnostic);
}
if matches!(self.follows, Follows::Decorator)
if matches!(state.follows, Follows::Decorator)
&& !line.is_comment_only
&& line.preceding_blank_lines > 0
{
@ -752,21 +862,19 @@ impl BlankLinesChecker {
// 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()),
state.last_non_comment_line_end,
self.locator.line_start(line.first_token_range.start()),
);
let trivia_text = locator.slice(trivia_range);
let trivia_text = self.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());
.filter_map(|line| (!line.trim_whitespace().is_empty()).then_some(line.as_str()))
.join(&self.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());
trivia_without_blank_lines.push_str(&self.stylist.line_ending());
Fix::safe_edit(Edit::range_replacement(
trivia_without_blank_lines,
trivia_range,
@ -779,12 +887,12 @@ impl BlankLinesChecker {
}
if line.preceding_blank_lines < BLANK_LINES_TOP_LEVEL
&& self
&& state
.previous_unindented_line_kind
.is_some_and(LogicalLineKind::is_top_level)
.is_some_and(LogicalLineKind::is_class_function_or_decorator)
&& line.indent_length == 0
&& !line.is_comment_only
&& !line.kind.is_top_level()
&& !line.kind.is_class_function_or_decorator()
{
// E305
let mut diagnostic = Diagnostic::new(
@ -796,13 +904,17 @@ impl BlankLinesChecker {
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),
self.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()),
self.stylist
.line_ending()
.repeat(BLANK_LINES_TOP_LEVEL as usize),
self.locator.line_start(line.first_token_range.start()),
)));
}
@ -811,66 +923,42 @@ impl BlankLinesChecker {
if line.preceding_blank_lines == 0
// Only apply to nested functions.
&& matches!(self.fn_status, Status::Inside(_))
&& line.kind.is_top_level()
&& matches!(state.fn_status, Status::Inside(_))
&& line.kind.is_class_function_or_decorator()
// Allow following a decorator (if there is an error it will be triggered on the first decorator).
&& !matches!(self.follows, Follows::Decorator)
&& !matches!(state.follows, Follows::Decorator)
// The class's docstring can directly precede the first function.
&& !matches!(self.follows, Follows::Docstring)
&& !matches!(state.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)
&& !(matches!(state.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()),
self.stylist.line_ending().to_string(),
self.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(Clone, Debug, Default)]
struct BlankLinesState {
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>,
}
#[derive(Copy, Clone, Debug)]
@ -883,12 +971,16 @@ enum LogicalLineKind {
Function,
/// A comment only line
Comment,
/// An import statement
Import,
/// A from.. import statement
FromImport,
/// Any other statement or clause header
Other,
}
impl LogicalLineKind {
fn is_top_level(self) -> bool {
fn is_class_function_or_decorator(self) -> bool {
matches!(
self,
LogicalLineKind::Class | LogicalLineKind::Function | LogicalLineKind::Decorator

View file

@ -246,5 +246,3 @@ E30.py:702:5: E303 [*] Too many blank lines (2)
702 701 | pass
703 702 | # end
704 703 |

View file

@ -0,0 +1,140 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E30_isort.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / import json
2 | |
3 | |
4 | |
5 | | from typing import Any, Sequence
6 | |
7 | |
8 | | class MissingCommand(TypeError): ... # noqa: N818
| |_^ I001
|
= help: Organize imports
Safe fix
1 1 | import json
2 |-
3 |-
4 |-
5 2 | from typing import Any, Sequence
6 3 |
7 4 |
E30_isort.py:23:1: E302 [*] Expected 2 blank lines, found 1
|
21 | abcd.foo()
22 |
23 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
| ^^^ E302
24 |
25 | if TYPE_CHECKING:
|
= help: Add missing blank line(s)
Safe fix
20 20 |
21 21 | abcd.foo()
22 22 |
23 |+
23 24 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
24 25 |
25 26 | if TYPE_CHECKING:
E30_isort.py:26:1: I001 [*] Import block is un-sorted or un-formatted
|
25 | if TYPE_CHECKING:
26 | / import os
27 | |
28 | |
29 | |
30 | | from typing_extensions import TypeAlias
31 | |
| |_^ I001
32 |
33 | abcd.foo()
|
= help: Organize imports
Safe fix
25 25 | if TYPE_CHECKING:
26 26 | import os
27 27 |
28 |-
29 |-
30 28 | from typing_extensions import TypeAlias
31 29 |
32 30 |
E30_isort.py:35:1: E302 [*] Expected 2 blank lines, found 1
|
33 | abcd.foo()
34 |
35 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
| ^^^ E302
36 | ...
|
= help: Add missing blank line(s)
Safe fix
32 32 |
33 33 | abcd.foo()
34 34 |
35 |+
35 36 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
36 37 | ...
37 38 |
E30_isort.py:41:1: E302 [*] Expected 2 blank lines, found 1
|
39 | from typing_extensions import TypeAlias
40 |
41 | def __call__2(self, name: str, *args: Any, **kwargs: Any) -> Any:
| ^^^ E302
42 | ...
|
= help: Add missing blank line(s)
Safe fix
38 38 | if TYPE_CHECKING:
39 39 | from typing_extensions import TypeAlias
40 40 |
41 |+
41 42 | def __call__2(self, name: str, *args: Any, **kwargs: Any) -> Any:
42 43 | ...
43 44 |
E30_isort.py:60:1: I001 [*] Import block is un-sorted or un-formatted
|
60 | / from typing import Any, Sequence
61 | |
62 | | class MissingCommand(TypeError): ... # noqa: N818
| |_^ I001
|
= help: Organize imports
Safe fix
59 59 |
60 60 | from typing import Any, Sequence
61 61 |
62 |+
62 63 | class MissingCommand(TypeError): ... # noqa: N818
E30_isort.py:62:1: E302 [*] Expected 2 blank lines, found 1
|
60 | from typing import Any, Sequence
61 |
62 | class MissingCommand(TypeError): ... # noqa: N818
| ^^^^^ E302
|
= help: Add missing blank line(s)
Safe fix
59 59 |
60 60 | from typing import Any, Sequence
61 61 |
62 |+
62 63 | class MissingCommand(TypeError): ... # noqa: N818

View file

@ -0,0 +1,127 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E30_isort.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / import json
2 | |
3 | |
4 | |
5 | | from typing import Any, Sequence
6 | |
7 | |
8 | | class MissingCommand(TypeError): ... # noqa: N818
| |_^ I001
|
= help: Organize imports
Safe fix
1 1 | import json
2 |-
3 |-
4 |-
5 2 | from typing import Any, Sequence
6 |-
7 |-
8 3 | class MissingCommand(TypeError): ... # noqa: N818
9 4 |
10 5 |
E30_isort.py:23:1: E302 [*] Expected 2 blank lines, found 1
|
21 | abcd.foo()
22 |
23 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
| ^^^ E302
24 |
25 | if TYPE_CHECKING:
|
= help: Add missing blank line(s)
Safe fix
20 20 |
21 21 | abcd.foo()
22 22 |
23 |+
23 24 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
24 25 |
25 26 | if TYPE_CHECKING:
E30_isort.py:26:1: I001 [*] Import block is un-sorted or un-formatted
|
25 | if TYPE_CHECKING:
26 | / import os
27 | |
28 | |
29 | |
30 | | from typing_extensions import TypeAlias
31 | |
| |_^ I001
32 |
33 | abcd.foo()
|
= help: Organize imports
Safe fix
25 25 | if TYPE_CHECKING:
26 26 | import os
27 27 |
28 |-
29 |-
30 28 | from typing_extensions import TypeAlias
31 29 |
32 30 |
E30_isort.py:35:1: E302 [*] Expected 2 blank lines, found 1
|
33 | abcd.foo()
34 |
35 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
| ^^^ E302
36 | ...
|
= help: Add missing blank line(s)
Safe fix
32 32 |
33 33 | abcd.foo()
34 34 |
35 |+
35 36 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
36 37 | ...
37 38 |
E30_isort.py:41:1: E302 [*] Expected 2 blank lines, found 1
|
39 | from typing_extensions import TypeAlias
40 |
41 | def __call__2(self, name: str, *args: Any, **kwargs: Any) -> Any:
| ^^^ E302
42 | ...
|
= help: Add missing blank line(s)
Safe fix
38 38 | if TYPE_CHECKING:
39 39 | from typing_extensions import TypeAlias
40 40 |
41 |+
41 42 | def __call__2(self, name: str, *args: Any, **kwargs: Any) -> Any:
42 43 | ...
43 44 |
E30_isort.py:60:1: I001 [*] Import block is un-sorted or un-formatted
|
60 | / from typing import Any, Sequence
61 | |
62 | | class MissingCommand(TypeError): ... # noqa: N818
| |_^ I001
|
= help: Organize imports
Safe fix
58 58 |
59 59 |
60 60 | from typing import Any, Sequence
61 |-
62 61 | class MissingCommand(TypeError): ... # noqa: N818

View file

@ -0,0 +1,110 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E30_isort.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / import json
2 | |
3 | |
4 | |
5 | | from typing import Any, Sequence
6 | |
7 | |
8 | | class MissingCommand(TypeError): ... # noqa: N818
| |_^ I001
|
= help: Organize imports
Safe fix
1 1 | import json
2 2 |
3 |-
4 |-
5 3 | from typing import Any, Sequence
6 |-
7 4 |
8 5 | class MissingCommand(TypeError): ... # noqa: N818
9 6 |
E30_isort.py:23:1: E302 [*] Expected 2 blank lines, found 1
|
21 | abcd.foo()
22 |
23 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
| ^^^ E302
24 |
25 | if TYPE_CHECKING:
|
= help: Add missing blank line(s)
Safe fix
20 20 |
21 21 | abcd.foo()
22 22 |
23 |+
23 24 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
24 25 |
25 26 | if TYPE_CHECKING:
E30_isort.py:26:1: I001 [*] Import block is un-sorted or un-formatted
|
25 | if TYPE_CHECKING:
26 | / import os
27 | |
28 | |
29 | |
30 | | from typing_extensions import TypeAlias
31 | |
| |_^ I001
32 |
33 | abcd.foo()
|
= help: Organize imports
Safe fix
25 25 | if TYPE_CHECKING:
26 26 | import os
27 27 |
28 |-
29 |-
30 28 | from typing_extensions import TypeAlias
31 29 |
32 30 |
E30_isort.py:35:1: E302 [*] Expected 2 blank lines, found 1
|
33 | abcd.foo()
34 |
35 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
| ^^^ E302
36 | ...
|
= help: Add missing blank line(s)
Safe fix
32 32 |
33 33 | abcd.foo()
34 34 |
35 |+
35 36 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
36 37 | ...
37 38 |
E30_isort.py:41:1: E302 [*] Expected 2 blank lines, found 1
|
39 | from typing_extensions import TypeAlias
40 |
41 | def __call__2(self, name: str, *args: Any, **kwargs: Any) -> Any:
| ^^^ E302
42 | ...
|
= help: Add missing blank line(s)
Safe fix
38 38 | if TYPE_CHECKING:
39 39 | from typing_extensions import TypeAlias
40 40 |
41 |+
41 42 | def __call__2(self, name: str, *args: Any, **kwargs: Any) -> Any:
42 43 | ...
43 44 |

View file

@ -0,0 +1,166 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E30_isort.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / import json
2 | |
3 | |
4 | |
5 | | from typing import Any, Sequence
6 | |
7 | |
8 | | class MissingCommand(TypeError): ... # noqa: N818
| |_^ I001
|
= help: Organize imports
Safe fix
2 2 |
3 3 |
4 4 |
5 |+
5 6 | from typing import Any, Sequence
6 7 |
7 8 |
9 |+
10 |+
8 11 | class MissingCommand(TypeError): ... # noqa: N818
9 12 |
10 13 |
E30_isort.py:8:1: E302 [*] Expected 4 blank lines, found 2
|
8 | class MissingCommand(TypeError): ... # noqa: N818
| ^^^^^ E302
|
= help: Add missing blank line(s)
Safe fix
5 5 | from typing import Any, Sequence
6 6 |
7 7 |
8 |+
9 |+
8 10 | class MissingCommand(TypeError): ... # noqa: N818
9 11 |
10 12 |
E30_isort.py:23:1: E302 [*] Expected 2 blank lines, found 1
|
21 | abcd.foo()
22 |
23 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
| ^^^ E302
24 |
25 | if TYPE_CHECKING:
|
= help: Add missing blank line(s)
Safe fix
20 20 |
21 21 | abcd.foo()
22 22 |
23 |+
23 24 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
24 25 |
25 26 | if TYPE_CHECKING:
E30_isort.py:26:1: I001 [*] Import block is un-sorted or un-formatted
|
25 | if TYPE_CHECKING:
26 | / import os
27 | |
28 | |
29 | |
30 | | from typing_extensions import TypeAlias
31 | |
| |_^ I001
32 |
33 | abcd.foo()
|
= help: Organize imports
Safe fix
25 25 | if TYPE_CHECKING:
26 26 | import os
27 27 |
28 |-
29 |-
30 28 | from typing_extensions import TypeAlias
31 29 |
32 30 |
E30_isort.py:35:1: E302 [*] Expected 2 blank lines, found 1
|
33 | abcd.foo()
34 |
35 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
| ^^^ E302
36 | ...
|
= help: Add missing blank line(s)
Safe fix
32 32 |
33 33 | abcd.foo()
34 34 |
35 |+
35 36 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
36 37 | ...
37 38 |
E30_isort.py:41:1: E302 [*] Expected 2 blank lines, found 1
|
39 | from typing_extensions import TypeAlias
40 |
41 | def __call__2(self, name: str, *args: Any, **kwargs: Any) -> Any:
| ^^^ E302
42 | ...
|
= help: Add missing blank line(s)
Safe fix
38 38 | if TYPE_CHECKING:
39 39 | from typing_extensions import TypeAlias
40 40 |
41 |+
41 42 | def __call__2(self, name: str, *args: Any, **kwargs: Any) -> Any:
42 43 | ...
43 44 |
E30_isort.py:60:1: I001 [*] Import block is un-sorted or un-formatted
|
60 | / from typing import Any, Sequence
61 | |
62 | | class MissingCommand(TypeError): ... # noqa: N818
| |_^ I001
|
= help: Organize imports
Safe fix
59 59 |
60 60 | from typing import Any, Sequence
61 61 |
62 |+
63 |+
64 |+
62 65 | class MissingCommand(TypeError): ... # noqa: N818
E30_isort.py:62:1: E302 [*] Expected 4 blank lines, found 1
|
60 | from typing import Any, Sequence
61 |
62 | class MissingCommand(TypeError): ... # noqa: N818
| ^^^^^ E302
|
= help: Add missing blank line(s)
Safe fix
59 59 |
60 60 | from typing import Any, Sequence
61 61 |
62 |+
63 |+
64 |+
62 65 | class MissingCommand(TypeError): ... # noqa: N818

View file

@ -0,0 +1,135 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E30_isort.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / import json
2 | |
3 | |
4 | |
5 | | from typing import Any, Sequence
6 | |
7 | |
8 | | class MissingCommand(TypeError): ... # noqa: N818
| |_^ I001
|
= help: Organize imports
Safe fix
1 1 | import json
2 |-
3 |-
4 |-
5 2 | from typing import Any, Sequence
6 3 |
7 4 |
E30_isort.py:5:1: E303 [*] Too many blank lines (3)
|
5 | from typing import Any, Sequence
| ^^^^ E303
|
= help: Remove extraneous blank line(s)
Safe fix
1 1 | import json
2 2 |
3 3 |
4 |-
5 4 | from typing import Any, Sequence
6 5 |
7 6 |
E30_isort.py:21:5: E303 [*] Too many blank lines (2)
|
21 | abcd.foo()
| ^^^^ E303
22 |
23 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
|
= help: Remove extraneous blank line(s)
Safe fix
17 17 | if __name__ == "__main__":
18 18 | import abcd
19 19 |
20 |-
21 20 | abcd.foo()
22 21 |
23 22 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
E30_isort.py:26:1: I001 [*] Import block is un-sorted or un-formatted
|
25 | if TYPE_CHECKING:
26 | / import os
27 | |
28 | |
29 | |
30 | | from typing_extensions import TypeAlias
31 | |
| |_^ I001
32 |
33 | abcd.foo()
|
= help: Organize imports
Safe fix
25 25 | if TYPE_CHECKING:
26 26 | import os
27 27 |
28 |-
29 |-
30 28 | from typing_extensions import TypeAlias
31 29 |
32 30 |
E30_isort.py:30:5: E303 [*] Too many blank lines (3)
|
30 | from typing_extensions import TypeAlias
| ^^^^ E303
|
= help: Remove extraneous blank line(s)
Safe fix
25 25 | if TYPE_CHECKING:
26 26 | import os
27 27 |
28 |-
29 |-
30 28 | from typing_extensions import TypeAlias
31 29 |
32 30 |
E30_isort.py:33:5: E303 [*] Too many blank lines (2)
|
33 | abcd.foo()
| ^^^^ E303
34 |
35 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
|
= help: Remove extraneous blank line(s)
Safe fix
29 29 |
30 30 | from typing_extensions import TypeAlias
31 31 |
32 |-
33 32 | abcd.foo()
34 33 |
35 34 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
E30_isort.py:60:1: I001 [*] Import block is un-sorted or un-formatted
|
60 | / from typing import Any, Sequence
61 | |
62 | | class MissingCommand(TypeError): ... # noqa: N818
| |_^ I001
|
= help: Organize imports
Safe fix
59 59 |
60 60 | from typing import Any, Sequence
61 61 |
62 |+
62 63 | class MissingCommand(TypeError): ... # noqa: N818

View file

@ -0,0 +1,171 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E30_isort.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / import json
2 | |
3 | |
4 | |
5 | | from typing import Any, Sequence
6 | |
7 | |
8 | | class MissingCommand(TypeError): ... # noqa: N818
| |_^ I001
|
= help: Organize imports
Safe fix
1 1 | import json
2 |-
3 |-
4 |-
5 2 | from typing import Any, Sequence
6 |-
7 |-
8 3 | class MissingCommand(TypeError): ... # noqa: N818
9 4 |
10 5 |
E30_isort.py:5:1: E303 [*] Too many blank lines (3)
|
5 | from typing import Any, Sequence
| ^^^^ E303
|
= help: Remove extraneous blank line(s)
Safe fix
1 1 | import json
2 2 |
3 3 |
4 |-
5 4 | from typing import Any, Sequence
6 5 |
7 6 |
E30_isort.py:8:1: E303 [*] Too many blank lines (2)
|
8 | class MissingCommand(TypeError): ... # noqa: N818
| ^^^^^ E303
|
= help: Remove extraneous blank line(s)
Safe fix
3 3 |
4 4 |
5 5 | from typing import Any, Sequence
6 |-
7 |-
8 6 | class MissingCommand(TypeError): ... # noqa: N818
9 7 |
10 8 |
E30_isort.py:21:5: E303 [*] Too many blank lines (2)
|
21 | abcd.foo()
| ^^^^ E303
22 |
23 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
|
= help: Remove extraneous blank line(s)
Safe fix
17 17 | if __name__ == "__main__":
18 18 | import abcd
19 19 |
20 |-
21 20 | abcd.foo()
22 21 |
23 22 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
E30_isort.py:26:1: I001 [*] Import block is un-sorted or un-formatted
|
25 | if TYPE_CHECKING:
26 | / import os
27 | |
28 | |
29 | |
30 | | from typing_extensions import TypeAlias
31 | |
| |_^ I001
32 |
33 | abcd.foo()
|
= help: Organize imports
Safe fix
25 25 | if TYPE_CHECKING:
26 26 | import os
27 27 |
28 |-
29 |-
30 28 | from typing_extensions import TypeAlias
31 29 |
32 30 |
E30_isort.py:30:5: E303 [*] Too many blank lines (3)
|
30 | from typing_extensions import TypeAlias
| ^^^^ E303
|
= help: Remove extraneous blank line(s)
Safe fix
25 25 | if TYPE_CHECKING:
26 26 | import os
27 27 |
28 |-
29 |-
30 28 | from typing_extensions import TypeAlias
31 29 |
32 30 |
E30_isort.py:33:5: E303 [*] Too many blank lines (2)
|
33 | abcd.foo()
| ^^^^ E303
34 |
35 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
|
= help: Remove extraneous blank line(s)
Safe fix
29 29 |
30 30 | from typing_extensions import TypeAlias
31 31 |
32 |-
33 32 | abcd.foo()
34 33 |
35 34 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
E30_isort.py:60:1: I001 [*] Import block is un-sorted or un-formatted
|
60 | / from typing import Any, Sequence
61 | |
62 | | class MissingCommand(TypeError): ... # noqa: N818
| |_^ I001
|
= help: Organize imports
Safe fix
58 58 |
59 59 |
60 60 | from typing import Any, Sequence
61 |-
62 61 | class MissingCommand(TypeError): ... # noqa: N818
E30_isort.py:62:1: E303 [*] Too many blank lines (1)
|
60 | from typing import Any, Sequence
61 |
62 | class MissingCommand(TypeError): ... # noqa: N818
| ^^^^^ E303
|
= help: Remove extraneous blank line(s)
Safe fix
58 58 |
59 59 |
60 60 | from typing import Any, Sequence
61 |-
62 61 | class MissingCommand(TypeError): ... # noqa: N818

View file

@ -0,0 +1,137 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E30_isort.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / import json
2 | |
3 | |
4 | |
5 | | from typing import Any, Sequence
6 | |
7 | |
8 | | class MissingCommand(TypeError): ... # noqa: N818
| |_^ I001
|
= help: Organize imports
Safe fix
1 1 | import json
2 2 |
3 |-
4 |-
5 3 | from typing import Any, Sequence
6 |-
7 4 |
8 5 | class MissingCommand(TypeError): ... # noqa: N818
9 6 |
E30_isort.py:5:1: E303 [*] Too many blank lines (3)
|
5 | from typing import Any, Sequence
| ^^^^ E303
|
= help: Remove extraneous blank line(s)
Safe fix
1 1 | import json
2 2 |
3 3 |
4 |-
5 4 | from typing import Any, Sequence
6 5 |
7 6 |
E30_isort.py:8:1: E303 [*] Too many blank lines (2)
|
8 | class MissingCommand(TypeError): ... # noqa: N818
| ^^^^^ E303
|
= help: Remove extraneous blank line(s)
Safe fix
4 4 |
5 5 | from typing import Any, Sequence
6 6 |
7 |-
8 7 | class MissingCommand(TypeError): ... # noqa: N818
9 8 |
10 9 |
E30_isort.py:21:5: E303 [*] Too many blank lines (2)
|
21 | abcd.foo()
| ^^^^ E303
22 |
23 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
|
= help: Remove extraneous blank line(s)
Safe fix
17 17 | if __name__ == "__main__":
18 18 | import abcd
19 19 |
20 |-
21 20 | abcd.foo()
22 21 |
23 22 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
E30_isort.py:26:1: I001 [*] Import block is un-sorted or un-formatted
|
25 | if TYPE_CHECKING:
26 | / import os
27 | |
28 | |
29 | |
30 | | from typing_extensions import TypeAlias
31 | |
| |_^ I001
32 |
33 | abcd.foo()
|
= help: Organize imports
Safe fix
25 25 | if TYPE_CHECKING:
26 26 | import os
27 27 |
28 |-
29 |-
30 28 | from typing_extensions import TypeAlias
31 29 |
32 30 |
E30_isort.py:30:5: E303 [*] Too many blank lines (3)
|
30 | from typing_extensions import TypeAlias
| ^^^^ E303
|
= help: Remove extraneous blank line(s)
Safe fix
25 25 | if TYPE_CHECKING:
26 26 | import os
27 27 |
28 |-
29 |-
30 28 | from typing_extensions import TypeAlias
31 29 |
32 30 |
E30_isort.py:33:5: E303 [*] Too many blank lines (2)
|
33 | abcd.foo()
| ^^^^ E303
34 |
35 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
|
= help: Remove extraneous blank line(s)
Safe fix
29 29 |
30 30 | from typing_extensions import TypeAlias
31 31 |
32 |-
33 32 | abcd.foo()
34 33 |
35 34 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:

View file

@ -0,0 +1,109 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E30_isort.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / import json
2 | |
3 | |
4 | |
5 | | from typing import Any, Sequence
6 | |
7 | |
8 | | class MissingCommand(TypeError): ... # noqa: N818
| |_^ I001
|
= help: Organize imports
Safe fix
2 2 |
3 3 |
4 4 |
5 |+
5 6 | from typing import Any, Sequence
6 7 |
7 8 |
9 |+
10 |+
8 11 | class MissingCommand(TypeError): ... # noqa: N818
9 12 |
10 13 |
E30_isort.py:21:5: E303 [*] Too many blank lines (2)
|
21 | abcd.foo()
| ^^^^ E303
22 |
23 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
|
= help: Remove extraneous blank line(s)
Safe fix
17 17 | if __name__ == "__main__":
18 18 | import abcd
19 19 |
20 |-
21 20 | abcd.foo()
22 21 |
23 22 | def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
E30_isort.py:26:1: I001 [*] Import block is un-sorted or un-formatted
|
25 | if TYPE_CHECKING:
26 | / import os
27 | |
28 | |
29 | |
30 | | from typing_extensions import TypeAlias
31 | |
| |_^ I001
32 |
33 | abcd.foo()
|
= help: Organize imports
Safe fix
25 25 | if TYPE_CHECKING:
26 26 | import os
27 27 |
28 |-
29 |-
30 28 | from typing_extensions import TypeAlias
31 29 |
32 30 |
E30_isort.py:33:5: E303 [*] Too many blank lines (2)
|
33 | abcd.foo()
| ^^^^ E303
34 |
35 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
|
= help: Remove extraneous blank line(s)
Safe fix
29 29 |
30 30 | from typing_extensions import TypeAlias
31 31 |
32 |-
33 32 | abcd.foo()
34 33 |
35 34 | def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
E30_isort.py:60:1: I001 [*] Import block is un-sorted or un-formatted
|
60 | / from typing import Any, Sequence
61 | |
62 | | class MissingCommand(TypeError): ... # noqa: N818
| |_^ I001
|
= help: Organize imports
Safe fix
59 59 |
60 60 | from typing import Any, Sequence
61 61 |
62 |+
63 |+
64 |+
62 65 | class MissingCommand(TypeError): ... # noqa: N818

View file

@ -12,6 +12,7 @@ use ruff_source_file::{find_newline, LineEnding};
use ruff_python_ast::str::leading_quote;
use ruff_source_file::Locator;
#[derive(Debug, Clone)]
pub struct Stylist<'a> {
locator: &'a Locator<'a>,
indentation: Indentation,
@ -145,7 +146,7 @@ impl fmt::Display for Quote {
}
/// The indentation style used in Python source code.
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Indentation(String);
impl Indentation {

View file

@ -9,6 +9,7 @@ use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use crate::newlines::find_newline;
use crate::{LineIndex, OneIndexed, SourceCode, SourceLocation};
#[derive(Debug)]
pub struct Locator<'a> {
contents: &'a str,
index: OnceCell<LineIndex>,