mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
Fix stylist indentation with a formfeed (#7489)
**Summary** In python, a formfeed is technically undefined behaviour (https://docs.python.org/3/reference/lexical_analysis.html#indentation): > A formfeed character may be present at the start of the line; it will be ignored for > the indentation calculations above. Formfeed characters occurring elsewhere in the > leading whitespace have an undefined effect (for instance, they may reset the space > count to zero). In practice, they just reset the indentation:df8b3a46a7/Parser/tokenizer.c (L1819-L1821)
a41bb2733f/crates/ruff_python_parser/src/lexer.rs (L664-L667)
The stylist didn't handle formfeeds previously and would produce invalid indents. The remedy is to cut everything before a form feed. Checks box for https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458825 **Test Plan** Unit test for the stylist and a regression test for the rule
This commit is contained in:
parent
ef34c5cbec
commit
94b68f201b
4 changed files with 62 additions and 1 deletions
9
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_4.py
vendored
Normal file
9
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_4.py
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
# formfeed indent
|
||||
# https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458825
|
||||
# This is technically a stylist bug (and has a test there), but it surfaced in B006
|
||||
|
||||
|
||||
class FormFeedIndent:
|
||||
def __init__(self, a=[]):
|
||||
print(a)
|
||||
|
|
@ -36,6 +36,7 @@ mod tests {
|
|||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_1.py"))]
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_2.py"))]
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_3.py"))]
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_4.py"))]
|
||||
#[test_case(Rule::NoExplicitStacklevel, Path::new("B028.py"))]
|
||||
#[test_case(Rule::RaiseLiteral, Path::new("B016.py"))]
|
||||
#[test_case(Rule::RaiseWithoutFromInsideExcept, Path::new("B904.py"))]
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B006_4.py:7:26: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
6 | class FormFeedIndent:
|
||||
7 | def __init__(self, a=[]):
|
||||
| ^^ B006
|
||||
8 | print(a)
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
4 4 |
|
||||
5 5 |
|
||||
6 6 | class FormFeedIndent:
|
||||
7 |- def __init__(self, a=[]):
|
||||
7 |+ def __init__(self, a=None):
|
||||
8 |+ if a is None:
|
||||
9 |+ a = []
|
||||
8 10 | print(a)
|
||||
9 11 |
|
||||
|
||||
|
|
@ -84,7 +84,21 @@ fn detect_indention(tokens: &[LexResult], locator: &Locator) -> Indentation {
|
|||
});
|
||||
|
||||
if let Some(indent_range) = indent_range {
|
||||
let whitespace = locator.slice(*indent_range);
|
||||
let mut whitespace = locator.slice(*indent_range);
|
||||
// https://docs.python.org/3/reference/lexical_analysis.html#indentation
|
||||
// > A formfeed character may be present at the start of the line; it will be ignored for
|
||||
// > the indentation calculations above. Formfeed characters occurring elsewhere in the
|
||||
// > leading whitespace have an undefined effect (for instance, they may reset the space
|
||||
// > count to zero).
|
||||
// So there's UB in python lexer -.-
|
||||
// In practice, they just reset the indentation:
|
||||
// https://github.com/python/cpython/blob/df8b3a46a7aa369f246a09ffd11ceedf1d34e921/Parser/tokenizer.c#L1819-L1821
|
||||
// https://github.com/astral-sh/ruff/blob/a41bb2733fe75a71f4cf6d4bb21e659fc4630b30/crates/ruff_python_parser/src/lexer.rs#L664-L667
|
||||
// We also reset the indentation when we see a formfeed character.
|
||||
// See also https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458825
|
||||
if let Some((_before, after)) = whitespace.rsplit_once('\x0C') {
|
||||
whitespace = after;
|
||||
}
|
||||
|
||||
Indentation(whitespace.to_string())
|
||||
} else {
|
||||
|
@ -228,6 +242,19 @@ x = (
|
|||
Stylist::from_tokens(&tokens, &locator).indentation(),
|
||||
&Indentation::default()
|
||||
);
|
||||
|
||||
// formfeed indent, see `detect_indention` comment.
|
||||
let contents = r#"
|
||||
class FormFeedIndent:
|
||||
def __init__(self, a=[]):
|
||||
print(a)
|
||||
"#;
|
||||
let locator = Locator::new(contents);
|
||||
let tokens: Vec<_> = lex(contents, Mode::Module).collect();
|
||||
assert_eq!(
|
||||
Stylist::from_tokens(&tokens, &locator).indentation(),
|
||||
&Indentation(" ".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue