mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-18 17:41:12 +00:00
Infer indentation with imports when logical indent is absent (#11608)
## Summary In an `__init__.py` file, it's not uncommon to lack a logical indent (since it may just contain imports). In such cases, we were always falling back to four-space indent. This PR adds detection for indents within import groups. Closes https://github.com/astral-sh/ruff/issues/11606.
This commit is contained in:
parent
a8d1328c1a
commit
bd46cd1fcf
6 changed files with 571 additions and 498 deletions
10
crates/ruff_linter/resources/test/fixtures/isort/two_space.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/isort/two_space.py
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
# If the file doesn't contain a logical indent token, we should still detect two-space indentation on imports.
|
||||
from math import (
|
||||
sin,
|
||||
tan,
|
||||
cos,
|
||||
nan,
|
||||
pi,
|
||||
)
|
||||
|
||||
del sin, cos, tan, pi, nan
|
|
@ -29,6 +29,11 @@ print("%#o" % (123,))
|
|||
|
||||
print("brace {} %s" % (1,))
|
||||
|
||||
print((
|
||||
"foo %s "
|
||||
"bar %s" % (x, y)
|
||||
))
|
||||
|
||||
print(
|
||||
"%s" % (
|
||||
"trailing comma",
|
||||
|
@ -52,10 +57,6 @@ print("%(ab)s" % {"a" "b": 1})
|
|||
|
||||
print("%(a)s" % {"a" : 1})
|
||||
|
||||
print((
|
||||
"foo %s "
|
||||
"bar %s" % (x, y)
|
||||
))
|
||||
|
||||
print(
|
||||
"foo %(foo)s "
|
||||
|
|
|
@ -341,6 +341,7 @@ mod tests {
|
|||
#[test_case(Path::new("split.py"))]
|
||||
#[test_case(Path::new("star_before_others.py"))]
|
||||
#[test_case(Path::new("trailing_suffix.py"))]
|
||||
#[test_case(Path::new("two_space.py"))]
|
||||
#[test_case(Path::new("type_comments.py"))]
|
||||
#[test_case(Path::new("unicode.py"))]
|
||||
fn default(path: &Path) -> Result<()> {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/isort/mod.rs
|
||||
---
|
||||
two_space.py:2:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
|
|
||||
1 | # If the file doesn't contain a logical indent token, we should still detect two-space indentation on imports.
|
||||
2 | / from math import (
|
||||
3 | | sin,
|
||||
4 | | tan,
|
||||
5 | | cos,
|
||||
6 | | nan,
|
||||
7 | | pi,
|
||||
8 | | )
|
||||
9 | |
|
||||
10 | | del sin, cos, tan, pi, nan
|
||||
| |_^ I001
|
||||
|
|
||||
= help: Organize imports
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | # If the file doesn't contain a logical indent token, we should still detect two-space indentation on imports.
|
||||
2 2 | from math import (
|
||||
3 |- sin,
|
||||
4 |- tan,
|
||||
5 3 | cos,
|
||||
6 4 | nan,
|
||||
7 5 | pi,
|
||||
6 |+ sin,
|
||||
7 |+ tan,
|
||||
8 8 | )
|
||||
9 9 |
|
||||
10 10 | del sin, cos, tan, pi, nan
|
File diff suppressed because it is too large
Load diff
|
@ -6,7 +6,7 @@ use once_cell::unsync::OnceCell;
|
|||
|
||||
use ruff_python_ast::{str::Quote, StringFlags};
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::Tok;
|
||||
use ruff_python_parser::{Tok, TokenKind};
|
||||
use ruff_source_file::{find_newline, LineEnding, Locator};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -86,6 +86,38 @@ fn detect_indention(tokens: &[LexResult], locator: &Locator) -> Indentation {
|
|||
|
||||
Indentation(whitespace.to_string())
|
||||
} else {
|
||||
// If we can't find a logical indent token, search for a non-logical indent, to cover cases
|
||||
// like:
|
||||
//```python
|
||||
// from math import (
|
||||
// sin,
|
||||
// tan,
|
||||
// cos,
|
||||
// )
|
||||
// ```
|
||||
let mut depth = 0usize;
|
||||
for (token, range) in tokens.iter().flatten() {
|
||||
match token.kind() {
|
||||
TokenKind::Lpar | TokenKind::Lbrace | TokenKind::Lsqb => {
|
||||
depth = depth.saturating_add(1);
|
||||
}
|
||||
TokenKind::Rpar | TokenKind::Rbrace | TokenKind::Rsqb => {
|
||||
depth = depth.saturating_sub(1);
|
||||
}
|
||||
TokenKind::NonLogicalNewline => {
|
||||
let line = locator.line(range.end());
|
||||
let indent_index = line.chars().position(|c| !c.is_whitespace());
|
||||
if let Some(indent_index) = indent_index {
|
||||
if indent_index > 0 {
|
||||
let whitespace = &line[..indent_index];
|
||||
return Indentation(whitespace.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Indentation::default()
|
||||
}
|
||||
}
|
||||
|
@ -177,7 +209,6 @@ if True:
|
|||
&Indentation("\t".to_string())
|
||||
);
|
||||
|
||||
// TODO(charlie): Should non-significant whitespace be detected?
|
||||
let contents = r"
|
||||
x = (
|
||||
1,
|
||||
|
@ -189,7 +220,7 @@ x = (
|
|||
let tokens: Vec<_> = lex(contents, Mode::Module).collect();
|
||||
assert_eq!(
|
||||
Stylist::from_tokens(&tokens, &locator).indentation(),
|
||||
&Indentation::default()
|
||||
&Indentation(" ".to_string())
|
||||
);
|
||||
|
||||
// formfeed indent, see `detect_indention` comment.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue