mirror of
https://github.com/astral-sh/ruff.git
synced 2026-05-06 08:56:57 -04:00
[flake8-pyi] Fix PYI016 false positive for f-string debug specifier (#24098)
Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
@@ -145,4 +145,14 @@ field49: typing.Optional[complex | complex] | complex
|
||||
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/19403
|
||||
# Should throw duplicate union member but not fix
|
||||
isinstance(None, typing.Union[None, None])
|
||||
isinstance(None, typing.Union[None, None])
|
||||
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/19914
|
||||
# f-string debug (=) specifier: different source text means different output
|
||||
field50: typing.Literal[f"{00=}"] | typing.Literal[f"{000=}"] # OK (f"{00=}" -> "00=0", f"{000=}" -> "000=0")
|
||||
field51: typing.Literal[f"{x=}"] | typing.Literal[f"{x}"] # OK (f"{x=}" -> "x=1", f"{x}" -> "1")
|
||||
field52: typing.Literal[f"{x=}"] | typing.Literal[f"{x=}"] # Error (true duplicate)
|
||||
field53: typing.Literal[f"{x:.2f}"] | typing.Literal[f"{x:.3f}"] # OK (different format specs)
|
||||
field54: typing.Literal[f"{x}"] | typing.Literal[f"{x}"] # Error (true duplicate)
|
||||
field55: typing.Literal[f"{x =}"] | typing.Literal[f"{x=}"] # OK (different debug text due to spaces)
|
||||
field56: typing.Literal[f"{0x0=}"] | typing.Literal[f"{0o0=}"] # OK (different source text: "0x0" vs "0o0")
|
||||
|
||||
@@ -12,7 +12,8 @@ mod tests {
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::pep8_naming;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::test_path;
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::test::{test_contents, test_path};
|
||||
use crate::{assert_diagnostics, assert_diagnostics_diff, settings};
|
||||
|
||||
#[test_case(Rule::AnyEqNeAnnotation, Path::new("PYI032.py"))]
|
||||
@@ -214,4 +215,22 @@ mod tests {
|
||||
assert_diagnostics!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pyi016_multiline_debug_fstring_mixed_newlines() {
|
||||
let path = Path::new("<filename>.pyi");
|
||||
let diagnostics = test_contents(
|
||||
&SourceKind::Python {
|
||||
code: "from typing import Literal\n\
|
||||
value: Literal[f\"\"\"{(\r\n1\r\n)=}\"\"\"] | Literal[f\"\"\"{(\n1\n)=}\"\"\"]\n"
|
||||
.to_string(),
|
||||
is_stub: true,
|
||||
},
|
||||
path,
|
||||
&settings::LinterSettings::for_rule(Rule::DuplicateUnionMember),
|
||||
)
|
||||
.0;
|
||||
|
||||
assert_eq!(diagnostics.len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
+41
@@ -1175,5 +1175,46 @@ PYI016 Duplicate union member `None`
|
||||
147 | # Should throw duplicate union member but not fix
|
||||
148 | isinstance(None, typing.Union[None, None])
|
||||
| ^^^^
|
||||
149 |
|
||||
150 | # Regression test for https://github.com/astral-sh/ruff/issues/19914
|
||||
|
|
||||
help: Remove duplicate union member `None`
|
||||
|
||||
PYI016 [*] Duplicate union member `typing.Literal[f"{x=}"]`
|
||||
--> PYI016.py:154:36
|
||||
|
|
||||
152 | field50: typing.Literal[f"{00=}"] | typing.Literal[f"{000=}"] # OK (f"{00=}" -> "00=0", f"{000=}" -> "000=0")
|
||||
153 | field51: typing.Literal[f"{x=}"] | typing.Literal[f"{x}"] # OK (f"{x=}" -> "x=1", f"{x}" -> "1")
|
||||
154 | field52: typing.Literal[f"{x=}"] | typing.Literal[f"{x=}"] # Error (true duplicate)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
155 | field53: typing.Literal[f"{x:.2f}"] | typing.Literal[f"{x:.3f}"] # OK (different format specs)
|
||||
156 | field54: typing.Literal[f"{x}"] | typing.Literal[f"{x}"] # Error (true duplicate)
|
||||
|
|
||||
help: Remove duplicate union member `typing.Literal[f"{x=}"]`
|
||||
151 | # f-string debug (=) specifier: different source text means different output
|
||||
152 | field50: typing.Literal[f"{00=}"] | typing.Literal[f"{000=}"] # OK (f"{00=}" -> "00=0", f"{000=}" -> "000=0")
|
||||
153 | field51: typing.Literal[f"{x=}"] | typing.Literal[f"{x}"] # OK (f"{x=}" -> "x=1", f"{x}" -> "1")
|
||||
- field52: typing.Literal[f"{x=}"] | typing.Literal[f"{x=}"] # Error (true duplicate)
|
||||
154 + field52: typing.Literal[f"{x=}"] # Error (true duplicate)
|
||||
155 | field53: typing.Literal[f"{x:.2f}"] | typing.Literal[f"{x:.3f}"] # OK (different format specs)
|
||||
156 | field54: typing.Literal[f"{x}"] | typing.Literal[f"{x}"] # Error (true duplicate)
|
||||
157 | field55: typing.Literal[f"{x =}"] | typing.Literal[f"{x=}"] # OK (different debug text due to spaces)
|
||||
|
||||
PYI016 [*] Duplicate union member `typing.Literal[f"{x}"]`
|
||||
--> PYI016.py:156:35
|
||||
|
|
||||
154 | field52: typing.Literal[f"{x=}"] | typing.Literal[f"{x=}"] # Error (true duplicate)
|
||||
155 | field53: typing.Literal[f"{x:.2f}"] | typing.Literal[f"{x:.3f}"] # OK (different format specs)
|
||||
156 | field54: typing.Literal[f"{x}"] | typing.Literal[f"{x}"] # Error (true duplicate)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
157 | field55: typing.Literal[f"{x =}"] | typing.Literal[f"{x=}"] # OK (different debug text due to spaces)
|
||||
158 | field56: typing.Literal[f"{0x0=}"] | typing.Literal[f"{0o0=}"] # OK (different source text: "0x0" vs "0o0")
|
||||
|
|
||||
help: Remove duplicate union member `typing.Literal[f"{x}"]`
|
||||
153 | field51: typing.Literal[f"{x=}"] | typing.Literal[f"{x}"] # OK (f"{x=}" -> "x=1", f"{x}" -> "1")
|
||||
154 | field52: typing.Literal[f"{x=}"] | typing.Literal[f"{x=}"] # Error (true duplicate)
|
||||
155 | field53: typing.Literal[f"{x:.2f}"] | typing.Literal[f"{x:.3f}"] # OK (different format specs)
|
||||
- field54: typing.Literal[f"{x}"] | typing.Literal[f"{x}"] # Error (true duplicate)
|
||||
156 + field54: typing.Literal[f"{x}"] # Error (true duplicate)
|
||||
157 | field55: typing.Literal[f"{x =}"] | typing.Literal[f"{x=}"] # OK (different debug text due to spaces)
|
||||
158 | field56: typing.Literal[f"{0x0=}"] | typing.Literal[f"{0o0=}"] # OK (different source text: "0x0" vs "0o0")
|
||||
|
||||
@@ -517,10 +517,39 @@ pub enum ComparableInterpolatedStringElement<'a> {
|
||||
InterpolatedElement(InterpolatedElement<'a>),
|
||||
}
|
||||
|
||||
/// Comparable wrapper for [`ast::DebugText`].
|
||||
///
|
||||
/// Compares the full debug text (leading + expression source + trailing) rather than only the
|
||||
/// expression source, because whitespace is part of the f-string's runtime output: `f"{x =}"`
|
||||
/// produces `"x =<value>"` while `f"{x=}"` produces `"x=<value>"`, making them distinct
|
||||
/// `Literal` types.
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ComparableDebugText<'a> {
|
||||
text: Cow<'a, str>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::DebugText> for ComparableDebugText<'a> {
|
||||
fn from(debug_text: &'a ast::DebugText) -> Self {
|
||||
// Normalizing newlines is safe because Python normalizes `\r\n` and `\r` to `\n`
|
||||
// at compile time, so they produce identical runtime values.
|
||||
Self {
|
||||
text: normalize_newlines(debug_text.as_str()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_newlines(contents: &str) -> Cow<'_, str> {
|
||||
if contents.contains('\r') {
|
||||
Cow::Owned(contents.replace("\r\n", "\n").replace('\r', "\n"))
|
||||
} else {
|
||||
Cow::Borrowed(contents)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct InterpolatedElement<'a> {
|
||||
expression: ComparableExpr<'a>,
|
||||
debug_text: Option<&'a ast::DebugText>,
|
||||
debug_text: Option<ComparableDebugText<'a>>,
|
||||
conversion: ast::ConversionFlag,
|
||||
format_spec: Option<Vec<ComparableInterpolatedStringElement<'a>>>,
|
||||
}
|
||||
@@ -552,7 +581,7 @@ impl<'a> From<&'a ast::InterpolatedElement> for InterpolatedElement<'a> {
|
||||
|
||||
Self {
|
||||
expression: (expression).into(),
|
||||
debug_text: debug_text.as_ref(),
|
||||
debug_text: debug_text.as_ref().map(Into::into),
|
||||
conversion: *conversion,
|
||||
format_spec: format_spec
|
||||
.as_ref()
|
||||
@@ -926,7 +955,7 @@ pub struct ExprCall<'a> {
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ExprInterpolatedElement<'a> {
|
||||
value: Box<ComparableExpr<'a>>,
|
||||
debug_text: Option<&'a ast::DebugText>,
|
||||
debug_text: Option<ComparableDebugText<'a>>,
|
||||
conversion: ast::ConversionFlag,
|
||||
format_spec: Vec<ComparableInterpolatedStringElement<'a>>,
|
||||
}
|
||||
|
||||
@@ -387,13 +387,71 @@ impl ConversionFlag {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
/// The debug text of a self-documenting f-string expression (e.g., `f"{x=}"`).
|
||||
///
|
||||
/// Stores the concatenation of leading text, expression source, and trailing text as a single
|
||||
/// [`CompactString`], with byte offsets to split them. The offsets are needed because the leading
|
||||
/// and trailing portions can contain non-whitespace characters (grouping parentheses, comments in
|
||||
/// triple-quoted f-strings) that cannot be distinguished from expression content by scanning.
|
||||
///
|
||||
/// [`CompactString`]: compact_str::CompactString
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
|
||||
pub struct DebugText {
|
||||
/// The full text between the `{` and the conversion / `format_spec` / `}`.
|
||||
text: compact_str::CompactString,
|
||||
/// Byte offset where the expression source begins.
|
||||
expression_start: u32,
|
||||
/// Byte offset where the expression source ends.
|
||||
expression_end: u32,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DebugText {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("DebugText")
|
||||
.field("leading", &self.leading())
|
||||
.field("expression", &self.expression())
|
||||
.field("trailing", &self.trailing())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl DebugText {
|
||||
pub fn new(leading: &str, expression: &str, trailing: &str) -> Self {
|
||||
let expression_start = leading.text_len().to_u32();
|
||||
let expression_end = expression_start + expression.text_len().to_u32();
|
||||
let mut buf = compact_str::CompactString::with_capacity(
|
||||
leading.len() + expression.len() + trailing.len(),
|
||||
);
|
||||
buf.push_str(leading);
|
||||
buf.push_str(expression);
|
||||
buf.push_str(trailing);
|
||||
Self {
|
||||
text: buf,
|
||||
expression_start,
|
||||
expression_end,
|
||||
}
|
||||
}
|
||||
|
||||
/// The full debug text between the `{` and the conversion / `format_spec` / `}`.
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.text
|
||||
}
|
||||
|
||||
/// The text between the `{` and the expression node.
|
||||
pub leading: String,
|
||||
/// The text between the expression and the conversion, the `format_spec`, or the `}`, depending on what's present in the source
|
||||
pub trailing: String,
|
||||
pub fn leading(&self) -> &str {
|
||||
&self.text[..self.expression_start as usize]
|
||||
}
|
||||
|
||||
/// The source text of the expression (e.g., `0x0` in `f"{0x0=}"`).
|
||||
pub fn expression(&self) -> &str {
|
||||
&self.text[self.expression_start as usize..self.expression_end as usize]
|
||||
}
|
||||
|
||||
/// The text between the expression and the conversion, the `format_spec`, or the `}`.
|
||||
pub fn trailing(&self) -> &str {
|
||||
&self.text[self.expression_end as usize..]
|
||||
}
|
||||
}
|
||||
|
||||
impl ExprFString {
|
||||
|
||||
@@ -1500,13 +1500,13 @@ impl<'a> Generator<'a> {
|
||||
self.p(brace);
|
||||
|
||||
if let Some(debug_text) = debug_text {
|
||||
self.buffer += debug_text.leading.as_str();
|
||||
self.buffer += debug_text.leading();
|
||||
}
|
||||
|
||||
self.buffer += &generator.buffer;
|
||||
|
||||
if let Some(debug_text) = debug_text {
|
||||
self.buffer += debug_text.trailing.as_str();
|
||||
self.buffer += debug_text.trailing();
|
||||
}
|
||||
|
||||
if !conversion.is_none() {
|
||||
|
||||
@@ -133,9 +133,9 @@ impl Format<PyFormatContext<'_>> for FormatInterpolatedElement<'_> {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
NormalizedDebugText(&debug_text.leading),
|
||||
NormalizedDebugText(debug_text.leading()),
|
||||
verbatim_text(expression),
|
||||
NormalizedDebugText(&debug_text.trailing),
|
||||
NormalizedDebugText(debug_text.trailing()),
|
||||
]
|
||||
)?;
|
||||
|
||||
|
||||
@@ -112,8 +112,8 @@ impl StringLikeExtensions for ast::StringLike<'_> {
|
||||
contains_line_break_or_comments(&spec.elements, context, triple_quotes)
|
||||
})
|
||||
|| expression.debug_text.as_ref().is_some_and(|debug_text| {
|
||||
memchr2(b'\n', b'\r', debug_text.leading.as_bytes()).is_some()
|
||||
|| memchr2(b'\n', b'\r', debug_text.trailing.as_bytes()).is_some()
|
||||
memchr2(b'\n', b'\r', debug_text.leading().as_bytes()).is_some()
|
||||
|| memchr2(b'\n', b'\r', debug_text.trailing().as_bytes()).is_some()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -189,9 +189,11 @@ impl Transformer for Normalizer {
|
||||
return;
|
||||
};
|
||||
|
||||
// Changing the newlines to the configured newline is okay because Python normalizes all newlines to `\n`
|
||||
debug.leading = debug.leading.replace("\r\n", "\n").replace('\r', "\n");
|
||||
debug.trailing = debug.trailing.replace("\r\n", "\n").replace('\r', "\n");
|
||||
// The formatter normalizes newlines in the text around a debug expression.
|
||||
let leading = debug.leading().replace("\r\n", "\n").replace('\r', "\n");
|
||||
let expression = debug.expression().to_string();
|
||||
let trailing = debug.trailing().replace("\r\n", "\n").replace('\r', "\n");
|
||||
*debug = ast::DebugText::new(&leading, &expression, &trailing);
|
||||
}
|
||||
|
||||
fn visit_string_literal(&self, string_literal: &mut ast::StringLiteral) {
|
||||
|
||||
@@ -1719,10 +1719,11 @@ impl<'src> Parser<'src> {
|
||||
let debug_text = if self.eat(TokenKind::Equal) {
|
||||
let leading_range = TextRange::new(start + "{".text_len(), value.start());
|
||||
let trailing_range = TextRange::new(value.end(), self.current_token_range().start());
|
||||
Some(ast::DebugText {
|
||||
leading: self.src_text(leading_range).to_string(),
|
||||
trailing: self.src_text(trailing_range).to_string(),
|
||||
})
|
||||
Some(ast::DebugText::new(
|
||||
self.src_text(leading_range),
|
||||
self.src_text(value.range()),
|
||||
self.src_text(trailing_range),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
+1
@@ -33,6 +33,7 @@ expression: suite
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "user",
|
||||
trailing: "=",
|
||||
},
|
||||
),
|
||||
|
||||
+2
@@ -40,6 +40,7 @@ expression: suite
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "user",
|
||||
trailing: "=",
|
||||
},
|
||||
),
|
||||
@@ -69,6 +70,7 @@ expression: suite
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "second",
|
||||
trailing: "=",
|
||||
},
|
||||
),
|
||||
|
||||
+1
@@ -33,6 +33,7 @@ expression: suite
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "user",
|
||||
trailing: "=",
|
||||
},
|
||||
),
|
||||
|
||||
+1
@@ -33,6 +33,7 @@ expression: suite
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "x",
|
||||
trailing: " =",
|
||||
},
|
||||
),
|
||||
|
||||
+1
@@ -33,6 +33,7 @@ expression: suite
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "x",
|
||||
trailing: "= ",
|
||||
},
|
||||
),
|
||||
|
||||
+1
@@ -32,6 +32,7 @@ expression: suite
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "x",
|
||||
trailing: " =",
|
||||
},
|
||||
),
|
||||
|
||||
+1
@@ -32,6 +32,7 @@ expression: suite
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "x",
|
||||
trailing: "= ",
|
||||
},
|
||||
),
|
||||
|
||||
+1
@@ -32,6 +32,7 @@ expression: suite
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "user",
|
||||
trailing: "=",
|
||||
},
|
||||
),
|
||||
|
||||
+2
@@ -39,6 +39,7 @@ expression: suite
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "user",
|
||||
trailing: "=",
|
||||
},
|
||||
),
|
||||
@@ -68,6 +69,7 @@ expression: suite
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "second",
|
||||
trailing: "=",
|
||||
},
|
||||
),
|
||||
|
||||
+1
@@ -32,6 +32,7 @@ expression: suite
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "user",
|
||||
trailing: "=",
|
||||
},
|
||||
),
|
||||
|
||||
+1
-1
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/err/f_string_unclosed_lbrace.py
|
||||
---
|
||||
## AST
|
||||
|
||||
@@ -134,6 +133,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "foo",
|
||||
trailing: "=",
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/invalid/re_lexing/ty_1828.py
|
||||
---
|
||||
## AST
|
||||
|
||||
@@ -75,6 +74,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "d",
|
||||
trailing: "=",
|
||||
},
|
||||
),
|
||||
|
||||
+1
-1
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/err/t_string_unclosed_lbrace.py
|
||||
---
|
||||
## AST
|
||||
|
||||
@@ -129,6 +128,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "foo",
|
||||
trailing: "=",
|
||||
},
|
||||
),
|
||||
|
||||
+10
-1
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/valid/expressions/f_string.py
|
||||
---
|
||||
## AST
|
||||
|
||||
@@ -608,6 +607,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: " ",
|
||||
expression: "foo",
|
||||
trailing: " = ",
|
||||
},
|
||||
),
|
||||
@@ -660,6 +660,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: " ",
|
||||
expression: "foo",
|
||||
trailing: " = ",
|
||||
},
|
||||
),
|
||||
@@ -726,6 +727,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: " ",
|
||||
expression: "foo",
|
||||
trailing: " = ",
|
||||
},
|
||||
),
|
||||
@@ -798,6 +800,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: " ",
|
||||
expression: "1, 2",
|
||||
trailing: " = ",
|
||||
},
|
||||
),
|
||||
@@ -866,6 +869,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "3.1415",
|
||||
trailing: "=",
|
||||
},
|
||||
),
|
||||
@@ -1391,6 +1395,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: " ( ",
|
||||
expression: "foo",
|
||||
trailing: " ) = ",
|
||||
},
|
||||
),
|
||||
@@ -1938,6 +1943,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "x",
|
||||
trailing: " =",
|
||||
},
|
||||
),
|
||||
@@ -1990,6 +1996,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: " ",
|
||||
expression: "x",
|
||||
trailing: " = ",
|
||||
},
|
||||
),
|
||||
@@ -2042,6 +2049,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "x",
|
||||
trailing: "=",
|
||||
},
|
||||
),
|
||||
@@ -2155,6 +2163,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "x",
|
||||
trailing: " = ",
|
||||
},
|
||||
),
|
||||
|
||||
+10
-1
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/valid/expressions/t_string.py
|
||||
---
|
||||
## AST
|
||||
|
||||
@@ -585,6 +584,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: " ",
|
||||
expression: "foo",
|
||||
trailing: " = ",
|
||||
},
|
||||
),
|
||||
@@ -635,6 +635,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: " ",
|
||||
expression: "foo",
|
||||
trailing: " = ",
|
||||
},
|
||||
),
|
||||
@@ -699,6 +700,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: " ",
|
||||
expression: "foo",
|
||||
trailing: " = ",
|
||||
},
|
||||
),
|
||||
@@ -769,6 +771,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: " ",
|
||||
expression: "1, 2",
|
||||
trailing: " = ",
|
||||
},
|
||||
),
|
||||
@@ -834,6 +837,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "3.1415",
|
||||
trailing: "=",
|
||||
},
|
||||
),
|
||||
@@ -1360,6 +1364,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: " ( ",
|
||||
expression: "foo",
|
||||
trailing: " ) = ",
|
||||
},
|
||||
),
|
||||
@@ -1895,6 +1900,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "x",
|
||||
trailing: " =",
|
||||
},
|
||||
),
|
||||
@@ -1945,6 +1951,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: " ",
|
||||
expression: "x",
|
||||
trailing: " = ",
|
||||
},
|
||||
),
|
||||
@@ -1995,6 +2002,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "x",
|
||||
trailing: "=",
|
||||
},
|
||||
),
|
||||
@@ -2104,6 +2112,7 @@ Module(
|
||||
debug_text: Some(
|
||||
DebugText {
|
||||
leading: "",
|
||||
expression: "x",
|
||||
trailing: " = ",
|
||||
},
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user