mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:56 +00:00
Parenthesize numbers during attribute accesses (#3189)
This commit is contained in:
parent
32d165b7ad
commit
bda2a0007a
6 changed files with 60 additions and 153 deletions
|
@ -23,9 +23,17 @@ pub fn trailing_quote(content: &str) -> Option<&&str> {
|
|||
.find(|&pattern| content.ends_with(pattern))
|
||||
}
|
||||
|
||||
pub fn is_radix_literal(content: &str) -> bool {
|
||||
content.starts_with("0b")
|
||||
|| content.starts_with("0o")
|
||||
|| content.starts_with("0x")
|
||||
|| content.starts_with("0B")
|
||||
|| content.starts_with("0O")
|
||||
|| content.starts_with("0X")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_prefixes() {
|
||||
let prefixes = ruff_python::str::TRIPLE_QUOTE_PREFIXES
|
||||
|
|
|
@ -22,6 +22,9 @@ pub mod shared_traits;
|
|||
pub mod trivia;
|
||||
|
||||
pub fn fmt(contents: &str) -> Result<Formatted<ASTFormatContext>> {
|
||||
// Create a reusable locator.
|
||||
let locator = Locator::new(contents);
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(contents);
|
||||
|
||||
|
@ -37,7 +40,7 @@ pub fn fmt(contents: &str) -> Result<Formatted<ASTFormatContext>> {
|
|||
// Attach trivia.
|
||||
attach(&mut python_cst, trivia);
|
||||
normalize_newlines(&mut python_cst);
|
||||
normalize_parentheses(&mut python_cst);
|
||||
normalize_parentheses(&mut python_cst, &locator);
|
||||
|
||||
format!(
|
||||
ASTFormatContext::new(
|
||||
|
@ -45,7 +48,7 @@ pub fn fmt(contents: &str) -> Result<Formatted<ASTFormatContext>> {
|
|||
indent_style: IndentStyle::Space(4),
|
||||
line_width: 88.try_into().unwrap(),
|
||||
},
|
||||
Locator::new(contents)
|
||||
locator,
|
||||
),
|
||||
[format::builders::block(&python_cst)]
|
||||
)
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
use crate::core::helpers::is_radix_literal;
|
||||
use crate::core::locator::Locator;
|
||||
use crate::core::types::Range;
|
||||
use crate::core::visitor;
|
||||
use crate::core::visitor::Visitor;
|
||||
use crate::cst::{Expr, ExprKind, Stmt, StmtKind};
|
||||
use crate::trivia::{Parenthesize, TriviaKind};
|
||||
use rustpython_parser::ast::Constant;
|
||||
|
||||
/// Modify an [`Expr`] to infer parentheses, rather than respecting any user-provided trivia.
|
||||
fn use_inferred_parens(expr: &mut Expr) {
|
||||
|
@ -22,9 +26,11 @@ fn use_inferred_parens(expr: &mut Expr) {
|
|||
}
|
||||
}
|
||||
|
||||
struct ParenthesesNormalizer {}
|
||||
struct ParenthesesNormalizer<'a> {
|
||||
locator: &'a Locator<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for ParenthesesNormalizer {
|
||||
impl<'a> Visitor<'a> for ParenthesesNormalizer<'_> {
|
||||
fn visit_stmt(&mut self, stmt: &'a mut Stmt) {
|
||||
// Always remove parentheses around statements, unless it's an expression statement,
|
||||
// in which case, remove parentheses around the expression.
|
||||
|
@ -134,7 +140,29 @@ impl<'a> Visitor<'a> for ParenthesesNormalizer {
|
|||
ExprKind::FormattedValue { .. } => {}
|
||||
ExprKind::JoinedStr { .. } => {}
|
||||
ExprKind::Constant { .. } => {}
|
||||
ExprKind::Attribute { .. } => {}
|
||||
ExprKind::Attribute { value, .. } => {
|
||||
if matches!(
|
||||
value.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::Float(..),
|
||||
..
|
||||
},
|
||||
) {
|
||||
value.parentheses = Parenthesize::Always;
|
||||
} else if matches!(
|
||||
value.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::Int(..),
|
||||
..
|
||||
},
|
||||
) {
|
||||
let (source, start, end) = self.locator.slice(Range::from_located(value));
|
||||
// TODO(charlie): Encode this in the AST via separate node types.
|
||||
if !is_radix_literal(&source[start..end]) {
|
||||
value.parentheses = Parenthesize::Always;
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Subscript { value, slice, .. } => {
|
||||
// If the slice isn't manually parenthesized, ensure that we _never_ parenthesize
|
||||
// the value.
|
||||
|
@ -166,7 +194,7 @@ impl<'a> Visitor<'a> for ParenthesesNormalizer {
|
|||
///
|
||||
/// TODO(charlie): It's weird that we have both `TriviaKind::Parentheses` (which aren't used
|
||||
/// during formatting) and `Parenthesize` (which are used during formatting).
|
||||
pub fn normalize_parentheses(python_cst: &mut [Stmt]) {
|
||||
let mut normalizer = ParenthesesNormalizer {};
|
||||
pub fn normalize_parentheses(python_cst: &mut [Stmt], locator: &Locator) {
|
||||
let mut normalizer = ParenthesesNormalizer { locator };
|
||||
normalizer.visit_body(python_cst);
|
||||
}
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
---
|
||||
source: crates/ruff_python_formatter/src/lib.rs
|
||||
expression: snapshot
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/attribute_access_on_number_literals.py
|
||||
---
|
||||
## Input
|
||||
|
||||
```py
|
||||
x = 123456789 .bit_count()
|
||||
x = (123456).__abs__()
|
||||
x = .1.is_integer()
|
||||
x = 1. .imag
|
||||
x = 1E+1.imag
|
||||
x = 1E-1.real
|
||||
x = 123456789.123456789.hex()
|
||||
x = 123456789.123456789E123456789 .real
|
||||
x = 123456789E123456789 .conjugate()
|
||||
x = 123456789J.real
|
||||
x = 123456789.123456789J.__add__(0b1011.bit_length())
|
||||
x = 0XB1ACC.conjugate()
|
||||
x = 0B1011 .conjugate()
|
||||
x = 0O777 .real
|
||||
x = 0.000000006 .hex()
|
||||
x = -100.0000J
|
||||
|
||||
if 10 .real:
|
||||
...
|
||||
|
||||
y = 100[no]
|
||||
y = 100(no)
|
||||
```
|
||||
|
||||
## Black Differences
|
||||
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -1,22 +1,22 @@
|
||||
-x = (123456789).bit_count()
|
||||
+x = 123456789.bit_count()
|
||||
x = (123456).__abs__()
|
||||
-x = (0.1).is_integer()
|
||||
-x = (1.0).imag
|
||||
-x = (1e1).imag
|
||||
-x = (1e-1).real
|
||||
-x = (123456789.123456789).hex()
|
||||
-x = (123456789.123456789e123456789).real
|
||||
-x = (123456789e123456789).conjugate()
|
||||
+x = 0.1.is_integer()
|
||||
+x = 1.0.imag
|
||||
+x = 1e1.imag
|
||||
+x = 1e-1.real
|
||||
+x = 123456789.123456789.hex()
|
||||
+x = 123456789.123456789e123456789.real
|
||||
+x = 123456789e123456789.conjugate()
|
||||
x = 123456789j.real
|
||||
x = 123456789.123456789j.__add__(0b1011.bit_length())
|
||||
x = 0xB1ACC.conjugate()
|
||||
x = 0b1011.conjugate()
|
||||
x = 0o777.real
|
||||
-x = (0.000000006).hex()
|
||||
+x = 0.000000006.hex()
|
||||
x = -100.0000j
|
||||
|
||||
-if (10).real:
|
||||
+if 10.real:
|
||||
...
|
||||
|
||||
y = 100[no]
|
||||
-y = 100(no)
|
||||
+y = 100((no))
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
||||
```py
|
||||
x = 123456789.bit_count()
|
||||
x = (123456).__abs__()
|
||||
x = 0.1.is_integer()
|
||||
x = 1.0.imag
|
||||
x = 1e1.imag
|
||||
x = 1e-1.real
|
||||
x = 123456789.123456789.hex()
|
||||
x = 123456789.123456789e123456789.real
|
||||
x = 123456789e123456789.conjugate()
|
||||
x = 123456789j.real
|
||||
x = 123456789.123456789j.__add__(0b1011.bit_length())
|
||||
x = 0xB1ACC.conjugate()
|
||||
x = 0b1011.conjugate()
|
||||
x = 0o777.real
|
||||
x = 0.000000006.hex()
|
||||
x = -100.0000j
|
||||
|
||||
if 10.real:
|
||||
...
|
||||
|
||||
y = 100[no]
|
||||
y = 100((no))
|
||||
```
|
||||
|
||||
## Black Output
|
||||
|
||||
```py
|
||||
x = (123456789).bit_count()
|
||||
x = (123456).__abs__()
|
||||
x = (0.1).is_integer()
|
||||
x = (1.0).imag
|
||||
x = (1e1).imag
|
||||
x = (1e-1).real
|
||||
x = (123456789.123456789).hex()
|
||||
x = (123456789.123456789e123456789).real
|
||||
x = (123456789e123456789).conjugate()
|
||||
x = 123456789j.real
|
||||
x = 123456789.123456789j.__add__(0b1011.bit_length())
|
||||
x = 0xB1ACC.conjugate()
|
||||
x = 0b1011.conjugate()
|
||||
x = 0o777.real
|
||||
x = (0.000000006).hex()
|
||||
x = -100.0000j
|
||||
|
||||
if (10).real:
|
||||
...
|
||||
|
||||
y = 100[no]
|
||||
y = 100(no)
|
||||
```
|
||||
|
||||
|
|
@ -292,17 +292,6 @@ last_call()
|
|||
}
|
||||
Python3 > Python2 > COBOL
|
||||
Life is Life
|
||||
@@ -122,8 +125,8 @@
|
||||
call(b, **self.screen_kwargs)
|
||||
lukasz.langa.pl
|
||||
call.me(maybe)
|
||||
-(1).real
|
||||
-(1.0).real
|
||||
+1.real
|
||||
+1.0.real
|
||||
....__class__
|
||||
list[str]
|
||||
dict[str, int]
|
||||
@@ -138,33 +141,33 @@
|
||||
very_long_variable_name_filters: t.List[
|
||||
t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]],
|
||||
|
@ -310,14 +299,14 @@ last_call()
|
|||
-xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore
|
||||
- sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
|
||||
+xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = (
|
||||
+ classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore
|
||||
+)
|
||||
+xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = (
|
||||
+ classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore
|
||||
)
|
||||
-xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore
|
||||
- sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
|
||||
+xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = (
|
||||
+ classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore
|
||||
+)
|
||||
+xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = (
|
||||
+ classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__))
|
||||
)
|
||||
-xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(
|
||||
|
@ -583,8 +572,8 @@ call(**self.screen_kwargs)
|
|||
call(b, **self.screen_kwargs)
|
||||
lukasz.langa.pl
|
||||
call.me(maybe)
|
||||
1.real
|
||||
1.0.real
|
||||
(1).real
|
||||
(1.0).real
|
||||
....__class__
|
||||
list[str]
|
||||
dict[str, int]
|
||||
|
|
|
@ -183,7 +183,14 @@ pub fn extract_trivia_tokens(lxr: &[LexResult]) -> Vec<TriviaToken> {
|
|||
|
||||
if matches!(tok, Tok::Lpar) {
|
||||
if prev_tok.map_or(true, |(_, prev_tok, _)| {
|
||||
!matches!(prev_tok, Tok::Name { .. })
|
||||
!matches!(
|
||||
prev_tok,
|
||||
Tok::Name { .. }
|
||||
| Tok::Int { .. }
|
||||
| Tok::Float { .. }
|
||||
| Tok::Complex { .. }
|
||||
| Tok::String { .. }
|
||||
)
|
||||
}) {
|
||||
parens.push((start, true));
|
||||
} else {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue