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:
Charlie Marsh 2024-05-30 00:18:07 -04:00 committed by GitHub
parent a8d1328c1a
commit bd46cd1fcf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 571 additions and 498 deletions

View file

@ -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.