Preserve quote style in generated code (#15726)

## Summary

This is a first step toward fixing #7799 by using the quoting style
stored in the `flags` field on `ast::StringLiteral`s to select a quoting
style. This PR does not include support for f-strings or byte strings.

Several rules also needed small updates to pass along existing quoting
styles instead of using `StringLiteralFlags::default()`. The remaining
snapshot changes are intentional and should preserve the quotes from the
input strings.

## Test Plan

Existing tests with some accepted updates, plus a few new RUF055 tests
for raw strings.

---------

Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
This commit is contained in:
Brent Westbrook 2025-01-27 13:41:03 -05:00 committed by GitHub
parent e994970538
commit 9bf138c45a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 550 additions and 343 deletions

View file

@ -29,47 +29,47 @@ no_sep = None
' 1 2 3 '.split() ' 1 2 3 '.split()
'1<>2<>3<4'.split('<>') '1<>2<>3<4'.split('<>')
" a*a a*a a ".split("*", -1) # [' a', 'a a', 'a a '] " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
"".split() # [] "".split() # []
""" """
""".split() # [] """.split() # []
" ".split() # [] " ".split() # []
"/abc/".split() # ['/abc/'] "/abc/".split() # ["/abc/"]
("a,b,c" ("a,b,c"
# comment # comment
.split() .split()
) # ['a,b,c'] ) # ["a,b,c"]
("a,b,c" ("a,b,c"
# comment1 # comment1
.split(",") .split(",")
) # ['a', 'b', 'c'] ) # ["a", "b", "c"]
("a," ("a,"
# comment # comment
"b," "b,"
"c" "c"
.split(",") .split(",")
) # ['a', 'b', 'c'] ) # ["a", "b", "c"]
"hello "\ "hello "\
"world".split() "world".split()
# ['hello', 'world'] # ["hello", "world"]
# prefixes and isc # prefixes and isc
u"a b".split() # ['a', 'b'] u"a b".split() # [u"a", u"b"]
r"a \n b".split() # ['a', '\\n', 'b'] r"a \n b".split() # [r"a", r"\n", r"b"]
("a " "b").split() # ['a', 'b'] ("a " "b").split() # ["a", "b"]
"a " "b".split() # ['a', 'b'] "a " "b".split() # ["a", "b"]
u"a " "b".split() # ['a', 'b'] u"a " "b".split() # [u"a", u"b"]
"a " u"b".split() # ['a', 'b'] "a " u"b".split() # ["a", "b"]
u"a " r"\n".split() # ['a', '\\n'] u"a " r"\n".split() # [u"a", u"\\n"]
r"\n " u"\n".split() # ['\\n'] r"\n " u"\n".split() # [r"\n"]
r"\n " "\n".split() # ['\\n'] r"\n " "\n".split() # [r"\n"]
"a " r"\n".split() # ['a', '\\n'] "a " r"\n".split() # ["a", "\\n"]
"a,b,c".split(',', maxsplit=0) # ['a,b,c'] "a,b,c".split(',', maxsplit=0) # ["a,b,c"]
"a,b,c".split(',', maxsplit=-1) # ['a', 'b', 'c'] "a,b,c".split(',', maxsplit=-1) # ["a", "b", "c"]
"a,b,c".split(',', maxsplit=-2) # ['a', 'b', 'c'] "a,b,c".split(',', maxsplit=-2) # ["a", "b", "c"]
"a,b,c".split(',', maxsplit=-0) # ['a,b,c'] "a,b,c".split(',', maxsplit=-0) # ["a,b,c"]
# negatives # negatives

View file

@ -82,3 +82,11 @@ class Collection(Protocol[*_B0]):
# Regression test for: https://github.com/astral-sh/ruff/issues/8609 # Regression test for: https://github.com/astral-sh/ruff/issues/8609
def f(x: Union[int, str, bytes]) -> None: def f(x: Union[int, str, bytes]) -> None:
... ...
# Regression test for https://github.com/astral-sh/ruff/issues/14132
class AClass:
...
def myfunc(param: "tuple[Union[int, 'AClass', None], str]"):
print(param)

View file

@ -93,3 +93,8 @@ re.sub(r"a", r"\a", "a")
re.sub(r"a", "\?", "a") re.sub(r"a", "\?", "a")
re.sub(r"a", r"\?", "a") re.sub(r"a", r"\?", "a")
# these double as tests for preserving raw string quoting style
re.sub(r'abc', "", s)
re.sub(r"""abc""", "", s)
re.sub(r'''abc''', "", s)

View file

@ -506,7 +506,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
checker, checker,
attr, attr,
call, call,
string_value.to_str(), string_value,
); );
} }
} else if attr == "format" { } else if attr == "format" {

View file

@ -296,11 +296,23 @@ impl<'a> Checker<'a> {
pub(crate) fn generator(&self) -> Generator { pub(crate) fn generator(&self) -> Generator {
Generator::new( Generator::new(
self.stylist.indentation(), self.stylist.indentation(),
self.f_string_quote_style().unwrap_or(self.stylist.quote()), self.preferred_quote(),
self.stylist.line_ending(), self.stylist.line_ending(),
) )
} }
/// Return the preferred quote for a generated `StringLiteral` node, given where we are in the
/// AST.
fn preferred_quote(&self) -> Quote {
self.f_string_quote_style().unwrap_or(self.stylist.quote())
}
/// Return the default string flags a generated `StringLiteral` node should use, given where we
/// are in the AST.
pub(crate) fn default_string_flags(&self) -> ast::StringLiteralFlags {
ast::StringLiteralFlags::empty().with_quote_style(self.preferred_quote())
}
/// Returns the appropriate quoting for f-string by reversing the one used outside of /// Returns the appropriate quoting for f-string by reversing the one used outside of
/// the f-string. /// the f-string.
/// ///

View file

@ -4,7 +4,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::{self as ast, Expr, ExprCall, ExprContext}; use ruff_python_ast::{self as ast, Expr, ExprCall, ExprContext, StringLiteralFlags};
use ruff_python_codegen::Generator; use ruff_python_codegen::Generator;
use ruff_python_trivia::CommentRanges; use ruff_python_trivia::CommentRanges;
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer}; use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
@ -280,7 +280,7 @@ impl Violation for PytestDuplicateParametrizeTestCases {
} }
} }
fn elts_to_csv(elts: &[Expr], generator: Generator) -> Option<String> { fn elts_to_csv(elts: &[Expr], generator: Generator, flags: StringLiteralFlags) -> Option<String> {
if !elts.iter().all(Expr::is_string_literal_expr) { if !elts.iter().all(Expr::is_string_literal_expr) {
return None; return None;
} }
@ -298,7 +298,8 @@ fn elts_to_csv(elts: &[Expr], generator: Generator) -> Option<String> {
acc acc
}) })
.into_boxed_str(), .into_boxed_str(),
..ast::StringLiteral::default() range: TextRange::default(),
flags,
}); });
Some(generator.expr(&node)) Some(generator.expr(&node))
} }
@ -358,8 +359,9 @@ fn check_names(checker: &mut Checker, call: &ExprCall, expr: &Expr, argvalues: &
.iter() .iter()
.map(|name| { .map(|name| {
Expr::from(ast::StringLiteral { Expr::from(ast::StringLiteral {
value: (*name).to_string().into_boxed_str(), value: Box::from(*name),
..ast::StringLiteral::default() range: TextRange::default(),
flags: checker.default_string_flags(),
}) })
}) })
.collect(), .collect(),
@ -393,8 +395,9 @@ fn check_names(checker: &mut Checker, call: &ExprCall, expr: &Expr, argvalues: &
.iter() .iter()
.map(|name| { .map(|name| {
Expr::from(ast::StringLiteral { Expr::from(ast::StringLiteral {
value: (*name).to_string().into_boxed_str(), value: Box::from(*name),
..ast::StringLiteral::default() range: TextRange::default(),
flags: checker.default_string_flags(),
}) })
}) })
.collect(), .collect(),
@ -444,7 +447,9 @@ fn check_names(checker: &mut Checker, call: &ExprCall, expr: &Expr, argvalues: &
}, },
expr.range(), expr.range(),
); );
if let Some(content) = elts_to_csv(elts, checker.generator()) { if let Some(content) =
elts_to_csv(elts, checker.generator(), checker.default_string_flags())
{
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
content, content,
expr.range(), expr.range(),
@ -489,7 +494,9 @@ fn check_names(checker: &mut Checker, call: &ExprCall, expr: &Expr, argvalues: &
}, },
expr.range(), expr.range(),
); );
if let Some(content) = elts_to_csv(elts, checker.generator()) { if let Some(content) =
elts_to_csv(elts, checker.generator(), checker.default_string_flags())
{
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
content, content,
expr.range(), expr.range(),

View file

@ -1,7 +1,5 @@
use ruff_python_ast::{ use ruff_python_ast::{self as ast, str_prefix::StringLiteralPrefix, Arguments, Expr};
self as ast, str_prefix::StringLiteralPrefix, Arguments, Expr, StringLiteralFlags, use ruff_text_size::{Ranged, TextRange};
};
use ruff_text_size::Ranged;
use crate::fix::snippet::SourceCodeSnippet; use crate::fix::snippet::SourceCodeSnippet;
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, FixAvailability, Violation};
@ -220,14 +218,14 @@ fn check_os_environ_subscript(checker: &mut Checker, expr: &Expr) {
); );
let node = ast::StringLiteral { let node = ast::StringLiteral {
value: capital_env_var.into_boxed_str(), value: capital_env_var.into_boxed_str(),
flags: StringLiteralFlags::default().with_prefix({ flags: checker.default_string_flags().with_prefix({
if env_var.is_unicode() { if env_var.is_unicode() {
StringLiteralPrefix::Unicode StringLiteralPrefix::Unicode
} else { } else {
StringLiteralPrefix::Empty StringLiteralPrefix::Empty
} }
}), }),
..ast::StringLiteral::default() range: TextRange::default(),
}; };
let new_env_var = node.into(); let new_env_var = node.into();
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(

View file

@ -62,7 +62,7 @@ pub(crate) fn split_static_string(
checker: &mut Checker, checker: &mut Checker,
attr: &str, attr: &str,
call: &ExprCall, call: &ExprCall,
str_value: &str, str_value: &StringLiteralValue,
) { ) {
let ExprCall { arguments, .. } = call; let ExprCall { arguments, .. } = call;
@ -115,16 +115,16 @@ pub(crate) fn split_static_string(
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
fn construct_replacement(elts: &[&str]) -> Expr { fn construct_replacement(elts: &[&str], flags: StringLiteralFlags) -> Expr {
Expr::List(ExprList { Expr::List(ExprList {
elts: elts elts: elts
.iter() .iter()
.map(|elt| { .map(|elt| {
Expr::StringLiteral(ExprStringLiteral { Expr::StringLiteral(ExprStringLiteral {
value: StringLiteralValue::single(StringLiteral { value: StringLiteralValue::single(StringLiteral {
value: (*elt).to_string().into_boxed_str(), value: Box::from(*elt),
range: TextRange::default(), range: TextRange::default(),
flags: StringLiteralFlags::default(), flags,
}), }),
range: TextRange::default(), range: TextRange::default(),
}) })
@ -135,7 +135,7 @@ fn construct_replacement(elts: &[&str]) -> Expr {
}) })
} }
fn split_default(str_value: &str, max_split: i32) -> Option<Expr> { fn split_default(str_value: &StringLiteralValue, max_split: i32) -> Option<Expr> {
// From the Python documentation: // From the Python documentation:
// > If sep is not specified or is None, a different splitting algorithm is applied: runs of // > If sep is not specified or is None, a different splitting algorithm is applied: runs of
// > consecutive whitespace are regarded as a single separator, and the result will contain // > consecutive whitespace are regarded as a single separator, and the result will contain
@ -151,30 +151,36 @@ fn split_default(str_value: &str, max_split: i32) -> Option<Expr> {
None None
} }
Ordering::Equal => { Ordering::Equal => {
let list_items: Vec<&str> = vec![str_value]; let list_items: Vec<&str> = vec![str_value.to_str()];
Some(construct_replacement(&list_items)) Some(construct_replacement(&list_items, str_value.flags()))
} }
Ordering::Less => { Ordering::Less => {
let list_items: Vec<&str> = str_value.split_whitespace().collect(); let list_items: Vec<&str> = str_value.to_str().split_whitespace().collect();
Some(construct_replacement(&list_items)) Some(construct_replacement(&list_items, str_value.flags()))
} }
} }
} }
fn split_sep(str_value: &str, sep_value: &str, max_split: i32, direction: Direction) -> Expr { fn split_sep(
str_value: &StringLiteralValue,
sep_value: &str,
max_split: i32,
direction: Direction,
) -> Expr {
let value = str_value.to_str();
let list_items: Vec<&str> = if let Ok(split_n) = usize::try_from(max_split) { let list_items: Vec<&str> = if let Ok(split_n) = usize::try_from(max_split) {
match direction { match direction {
Direction::Left => str_value.splitn(split_n + 1, sep_value).collect(), Direction::Left => value.splitn(split_n + 1, sep_value).collect(),
Direction::Right => str_value.rsplitn(split_n + 1, sep_value).collect(), Direction::Right => value.rsplitn(split_n + 1, sep_value).collect(),
} }
} else { } else {
match direction { match direction {
Direction::Left => str_value.split(sep_value).collect(), Direction::Left => value.split(sep_value).collect(),
Direction::Right => str_value.rsplit(sep_value).collect(), Direction::Right => value.rsplit(sep_value).collect(),
} }
}; };
construct_replacement(&list_items) construct_replacement(&list_items, str_value.flags())
} }
/// Returns the value of the `maxsplit` argument as an `i32`, if it is a numeric value. /// Returns the value of the `maxsplit` argument as an `i32`, if it is a numeric value.

View file

@ -319,10 +319,10 @@ SIM905.py:29:1: SIM905 [*] Consider using a list literal instead of `str.split`
27 27 | "a,b,c,d".split(maxsplit=0) 27 27 | "a,b,c,d".split(maxsplit=0)
28 28 | "VERB AUX PRON ADP DET".split(" ") 28 28 | "VERB AUX PRON ADP DET".split(" ")
29 |-' 1 2 3 '.split() 29 |-' 1 2 3 '.split()
29 |+["1", "2", "3"] 29 |+['1', '2', '3']
30 30 | '1<>2<>3<4'.split('<>') 30 30 | '1<>2<>3<4'.split('<>')
31 31 | 31 31 |
32 32 | " a*a a*a a ".split("*", -1) # [' a', 'a a', 'a a '] 32 32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
SIM905.py:30:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:30:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
@ -331,7 +331,7 @@ SIM905.py:30:1: SIM905 [*] Consider using a list literal instead of `str.split`
30 | '1<>2<>3<4'.split('<>') 30 | '1<>2<>3<4'.split('<>')
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^^^^^^^^ SIM905
31 | 31 |
32 | " a*a a*a a ".split("*", -1) # [' a', 'a a', 'a a '] 32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
| |
= help: Replace with list literal = help: Replace with list literal
@ -340,16 +340,16 @@ SIM905.py:30:1: SIM905 [*] Consider using a list literal instead of `str.split`
28 28 | "VERB AUX PRON ADP DET".split(" ") 28 28 | "VERB AUX PRON ADP DET".split(" ")
29 29 | ' 1 2 3 '.split() 29 29 | ' 1 2 3 '.split()
30 |-'1<>2<>3<4'.split('<>') 30 |-'1<>2<>3<4'.split('<>')
30 |+["1", "2", "3<4"] 30 |+['1', '2', '3<4']
31 31 | 31 31 |
32 32 | " a*a a*a a ".split("*", -1) # [' a', 'a a', 'a a '] 32 32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
33 33 | "".split() # [] 33 33 | "".split() # []
SIM905.py:32:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:32:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
30 | '1<>2<>3<4'.split('<>') 30 | '1<>2<>3<4'.split('<>')
31 | 31 |
32 | " a*a a*a a ".split("*", -1) # [' a', 'a a', 'a a '] 32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
33 | "".split() # [] 33 | "".split() # []
34 | """ 34 | """
@ -360,15 +360,15 @@ SIM905.py:32:1: SIM905 [*] Consider using a list literal instead of `str.split`
29 29 | ' 1 2 3 '.split() 29 29 | ' 1 2 3 '.split()
30 30 | '1<>2<>3<4'.split('<>') 30 30 | '1<>2<>3<4'.split('<>')
31 31 | 31 31 |
32 |-" a*a a*a a ".split("*", -1) # [' a', 'a a', 'a a '] 32 |-" a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
32 |+[" a", "a a", "a a "] # [' a', 'a a', 'a a '] 32 |+[" a", "a a", "a a "] # [" a", "a a", "a a "]
33 33 | "".split() # [] 33 33 | "".split() # []
34 34 | """ 34 34 | """
35 35 | """.split() # [] 35 35 | """.split() # []
SIM905.py:33:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:33:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
32 | " a*a a*a a ".split("*", -1) # [' a', 'a a', 'a a '] 32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
33 | "".split() # [] 33 | "".split() # []
| ^^^^^^^^^^ SIM905 | ^^^^^^^^^^ SIM905
34 | """ 34 | """
@ -379,7 +379,7 @@ SIM905.py:33:1: SIM905 [*] Consider using a list literal instead of `str.split`
Safe fix Safe fix
30 30 | '1<>2<>3<4'.split('<>') 30 30 | '1<>2<>3<4'.split('<>')
31 31 | 31 31 |
32 32 | " a*a a*a a ".split("*", -1) # [' a', 'a a', 'a a '] 32 32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
33 |-"".split() # [] 33 |-"".split() # []
33 |+[] # [] 33 |+[] # []
34 34 | """ 34 34 | """
@ -388,25 +388,25 @@ SIM905.py:33:1: SIM905 [*] Consider using a list literal instead of `str.split`
SIM905.py:34:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:34:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
32 | " a*a a*a a ".split("*", -1) # [' a', 'a a', 'a a '] 32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
33 | "".split() # [] 33 | "".split() # []
34 | / """ 34 | / """
35 | | """.split() # [] 35 | | """.split() # []
| |___________^ SIM905 | |___________^ SIM905
36 | " ".split() # [] 36 | " ".split() # []
37 | "/abc/".split() # ['/abc/'] 37 | "/abc/".split() # ["/abc/"]
| |
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
31 31 | 31 31 |
32 32 | " a*a a*a a ".split("*", -1) # [' a', 'a a', 'a a '] 32 32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "]
33 33 | "".split() # [] 33 33 | "".split() # []
34 |-""" 34 |-"""
35 |-""".split() # [] 35 |-""".split() # []
34 |+[] # [] 34 |+[] # []
36 35 | " ".split() # [] 36 35 | " ".split() # []
37 36 | "/abc/".split() # ['/abc/'] 37 36 | "/abc/".split() # ["/abc/"]
38 37 | ("a,b,c" 38 37 | ("a,b,c"
SIM905.py:36:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:36:1: SIM905 [*] Consider using a list literal instead of `str.split`
@ -415,7 +415,7 @@ SIM905.py:36:1: SIM905 [*] Consider using a list literal instead of `str.split`
35 | """.split() # [] 35 | """.split() # []
36 | " ".split() # [] 36 | " ".split() # []
| ^^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^^ SIM905
37 | "/abc/".split() # ['/abc/'] 37 | "/abc/".split() # ["/abc/"]
38 | ("a,b,c" 38 | ("a,b,c"
| |
= help: Replace with list literal = help: Replace with list literal
@ -426,7 +426,7 @@ SIM905.py:36:1: SIM905 [*] Consider using a list literal instead of `str.split`
35 35 | """.split() # [] 35 35 | """.split() # []
36 |-" ".split() # [] 36 |-" ".split() # []
36 |+[] # [] 36 |+[] # []
37 37 | "/abc/".split() # ['/abc/'] 37 37 | "/abc/".split() # ["/abc/"]
38 38 | ("a,b,c" 38 38 | ("a,b,c"
39 39 | # comment 39 39 | # comment
@ -434,7 +434,7 @@ SIM905.py:37:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
35 | """.split() # [] 35 | """.split() # []
36 | " ".split() # [] 36 | " ".split() # []
37 | "/abc/".split() # ['/abc/'] 37 | "/abc/".split() # ["/abc/"]
| ^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^ SIM905
38 | ("a,b,c" 38 | ("a,b,c"
39 | # comment 39 | # comment
@ -445,8 +445,8 @@ SIM905.py:37:1: SIM905 [*] Consider using a list literal instead of `str.split`
34 34 | """ 34 34 | """
35 35 | """.split() # [] 35 35 | """.split() # []
36 36 | " ".split() # [] 36 36 | " ".split() # []
37 |-"/abc/".split() # ['/abc/'] 37 |-"/abc/".split() # ["/abc/"]
37 |+["/abc/"] # ['/abc/'] 37 |+["/abc/"] # ["/abc/"]
38 38 | ("a,b,c" 38 38 | ("a,b,c"
39 39 | # comment 39 39 | # comment
40 40 | .split() 40 40 | .split()
@ -454,13 +454,13 @@ SIM905.py:37:1: SIM905 [*] Consider using a list literal instead of `str.split`
SIM905.py:38:2: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:38:2: SIM905 [*] Consider using a list literal instead of `str.split`
| |
36 | " ".split() # [] 36 | " ".split() # []
37 | "/abc/".split() # ['/abc/'] 37 | "/abc/".split() # ["/abc/"]
38 | ("a,b,c" 38 | ("a,b,c"
| __^ | __^
39 | | # comment 39 | | # comment
40 | | .split() 40 | | .split()
| |________^ SIM905 | |________^ SIM905
41 | ) # ['a,b,c'] 41 | ) # ["a,b,c"]
42 | ("a,b,c" 42 | ("a,b,c"
| |
= help: Replace with list literal = help: Replace with list literal
@ -468,25 +468,25 @@ SIM905.py:38:2: SIM905 [*] Consider using a list literal instead of `str.split`
Unsafe fix Unsafe fix
35 35 | """.split() # [] 35 35 | """.split() # []
36 36 | " ".split() # [] 36 36 | " ".split() # []
37 37 | "/abc/".split() # ['/abc/'] 37 37 | "/abc/".split() # ["/abc/"]
38 |-("a,b,c" 38 |-("a,b,c"
39 |-# comment 39 |-# comment
40 |-.split() 40 |-.split()
38 |+(["a,b,c"] 38 |+(["a,b,c"]
41 39 | ) # ['a,b,c'] 41 39 | ) # ["a,b,c"]
42 40 | ("a,b,c" 42 40 | ("a,b,c"
43 41 | # comment1 43 41 | # comment1
SIM905.py:42:2: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:42:2: SIM905 [*] Consider using a list literal instead of `str.split`
| |
40 | .split() 40 | .split()
41 | ) # ['a,b,c'] 41 | ) # ["a,b,c"]
42 | ("a,b,c" 42 | ("a,b,c"
| __^ | __^
43 | | # comment1 43 | | # comment1
44 | | .split(",") 44 | | .split(",")
| |___________^ SIM905 | |___________^ SIM905
45 | ) # ['a', 'b', 'c'] 45 | ) # ["a", "b", "c"]
46 | ("a," 46 | ("a,"
| |
= help: Replace with list literal = help: Replace with list literal
@ -494,19 +494,19 @@ SIM905.py:42:2: SIM905 [*] Consider using a list literal instead of `str.split`
Unsafe fix Unsafe fix
39 39 | # comment 39 39 | # comment
40 40 | .split() 40 40 | .split()
41 41 | ) # ['a,b,c'] 41 41 | ) # ["a,b,c"]
42 |-("a,b,c" 42 |-("a,b,c"
43 |-# comment1 43 |-# comment1
44 |-.split(",") 44 |-.split(",")
42 |+(["a", "b", "c"] 42 |+(["a", "b", "c"]
45 43 | ) # ['a', 'b', 'c'] 45 43 | ) # ["a", "b", "c"]
46 44 | ("a," 46 44 | ("a,"
47 45 | # comment 47 45 | # comment
SIM905.py:46:2: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:46:2: SIM905 [*] Consider using a list literal instead of `str.split`
| |
44 | .split(",") 44 | .split(",")
45 | ) # ['a', 'b', 'c'] 45 | ) # ["a", "b", "c"]
46 | ("a," 46 | ("a,"
| __^ | __^
47 | | # comment 47 | | # comment
@ -514,320 +514,320 @@ SIM905.py:46:2: SIM905 [*] Consider using a list literal instead of `str.split`
49 | | "c" 49 | | "c"
50 | | .split(",") 50 | | .split(",")
| |___________^ SIM905 | |___________^ SIM905
51 | ) # ['a', 'b', 'c'] 51 | ) # ["a", "b", "c"]
| |
= help: Replace with list literal = help: Replace with list literal
Unsafe fix Unsafe fix
43 43 | # comment1 43 43 | # comment1
44 44 | .split(",") 44 44 | .split(",")
45 45 | ) # ['a', 'b', 'c'] 45 45 | ) # ["a", "b", "c"]
46 |-("a," 46 |-("a,"
47 |-# comment 47 |-# comment
48 |-"b," 48 |-"b,"
49 |-"c" 49 |-"c"
50 |-.split(",") 50 |-.split(",")
46 |+(["a", "b", "c"] 46 |+(["a", "b", "c"]
51 47 | ) # ['a', 'b', 'c'] 51 47 | ) # ["a", "b", "c"]
52 48 | 52 48 |
53 49 | "hello "\ 53 49 | "hello "\
SIM905.py:53:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:53:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
51 | ) # ['a', 'b', 'c'] 51 | ) # ["a", "b", "c"]
52 | 52 |
53 | / "hello "\ 53 | / "hello "\
54 | | "world".split() 54 | | "world".split()
| |___________________^ SIM905 | |___________________^ SIM905
55 | # ['hello', 'world'] 55 | # ["hello", "world"]
| |
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
50 50 | .split(",") 50 50 | .split(",")
51 51 | ) # ['a', 'b', 'c'] 51 51 | ) # ["a", "b", "c"]
52 52 | 52 52 |
53 |-"hello "\ 53 |-"hello "\
54 |- "world".split() 54 |- "world".split()
53 |+["hello", "world"] 53 |+["hello", "world"]
55 54 | # ['hello', 'world'] 55 54 | # ["hello", "world"]
56 55 | 56 55 |
57 56 | # prefixes and isc 57 56 | # prefixes and isc
SIM905.py:58:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:58:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
57 | # prefixes and isc 57 | # prefixes and isc
58 | u"a b".split() # ['a', 'b'] 58 | u"a b".split() # [u"a", u"b"]
| ^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^ SIM905
59 | r"a \n b".split() # ['a', '\\n', 'b'] 59 | r"a \n b".split() # [r"a", r"\n", r"b"]
60 | ("a " "b").split() # ['a', 'b'] 60 | ("a " "b").split() # ["a", "b"]
| |
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
55 55 | # ['hello', 'world'] 55 55 | # ["hello", "world"]
56 56 | 56 56 |
57 57 | # prefixes and isc 57 57 | # prefixes and isc
58 |-u"a b".split() # ['a', 'b'] 58 |-u"a b".split() # [u"a", u"b"]
58 |+["a", "b"] # ['a', 'b'] 58 |+[u"a", u"b"] # [u"a", u"b"]
59 59 | r"a \n b".split() # ['a', '\\n', 'b'] 59 59 | r"a \n b".split() # [r"a", r"\n", r"b"]
60 60 | ("a " "b").split() # ['a', 'b'] 60 60 | ("a " "b").split() # ["a", "b"]
61 61 | "a " "b".split() # ['a', 'b'] 61 61 | "a " "b".split() # ["a", "b"]
SIM905.py:59:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:59:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
57 | # prefixes and isc 57 | # prefixes and isc
58 | u"a b".split() # ['a', 'b'] 58 | u"a b".split() # [u"a", u"b"]
59 | r"a \n b".split() # ['a', '\\n', 'b'] 59 | r"a \n b".split() # [r"a", r"\n", r"b"]
| ^^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^^ SIM905
60 | ("a " "b").split() # ['a', 'b'] 60 | ("a " "b").split() # ["a", "b"]
61 | "a " "b".split() # ['a', 'b'] 61 | "a " "b".split() # ["a", "b"]
| |
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
56 56 | 56 56 |
57 57 | # prefixes and isc 57 57 | # prefixes and isc
58 58 | u"a b".split() # ['a', 'b'] 58 58 | u"a b".split() # [u"a", u"b"]
59 |-r"a \n b".split() # ['a', '\\n', 'b'] 59 |-r"a \n b".split() # [r"a", r"\n", r"b"]
59 |+["a", "\\n", "b"] # ['a', '\\n', 'b'] 59 |+[r"a", r"\n", r"b"] # [r"a", r"\n", r"b"]
60 60 | ("a " "b").split() # ['a', 'b'] 60 60 | ("a " "b").split() # ["a", "b"]
61 61 | "a " "b".split() # ['a', 'b'] 61 61 | "a " "b".split() # ["a", "b"]
62 62 | u"a " "b".split() # ['a', 'b'] 62 62 | u"a " "b".split() # [u"a", u"b"]
SIM905.py:60:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:60:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
58 | u"a b".split() # ['a', 'b'] 58 | u"a b".split() # [u"a", u"b"]
59 | r"a \n b".split() # ['a', '\\n', 'b'] 59 | r"a \n b".split() # [r"a", r"\n", r"b"]
60 | ("a " "b").split() # ['a', 'b'] 60 | ("a " "b").split() # ["a", "b"]
| ^^^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^^^ SIM905
61 | "a " "b".split() # ['a', 'b'] 61 | "a " "b".split() # ["a", "b"]
62 | u"a " "b".split() # ['a', 'b'] 62 | u"a " "b".split() # [u"a", u"b"]
| |
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
57 57 | # prefixes and isc 57 57 | # prefixes and isc
58 58 | u"a b".split() # ['a', 'b'] 58 58 | u"a b".split() # [u"a", u"b"]
59 59 | r"a \n b".split() # ['a', '\\n', 'b'] 59 59 | r"a \n b".split() # [r"a", r"\n", r"b"]
60 |-("a " "b").split() # ['a', 'b'] 60 |-("a " "b").split() # ["a", "b"]
60 |+["a", "b"] # ['a', 'b'] 60 |+["a", "b"] # ["a", "b"]
61 61 | "a " "b".split() # ['a', 'b'] 61 61 | "a " "b".split() # ["a", "b"]
62 62 | u"a " "b".split() # ['a', 'b'] 62 62 | u"a " "b".split() # [u"a", u"b"]
63 63 | "a " u"b".split() # ['a', 'b'] 63 63 | "a " u"b".split() # ["a", "b"]
SIM905.py:61:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:61:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
59 | r"a \n b".split() # ['a', '\\n', 'b'] 59 | r"a \n b".split() # [r"a", r"\n", r"b"]
60 | ("a " "b").split() # ['a', 'b'] 60 | ("a " "b").split() # ["a", "b"]
61 | "a " "b".split() # ['a', 'b'] 61 | "a " "b".split() # ["a", "b"]
| ^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^ SIM905
62 | u"a " "b".split() # ['a', 'b'] 62 | u"a " "b".split() # [u"a", u"b"]
63 | "a " u"b".split() # ['a', 'b'] 63 | "a " u"b".split() # ["a", "b"]
| |
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
58 58 | u"a b".split() # ['a', 'b'] 58 58 | u"a b".split() # [u"a", u"b"]
59 59 | r"a \n b".split() # ['a', '\\n', 'b'] 59 59 | r"a \n b".split() # [r"a", r"\n", r"b"]
60 60 | ("a " "b").split() # ['a', 'b'] 60 60 | ("a " "b").split() # ["a", "b"]
61 |-"a " "b".split() # ['a', 'b'] 61 |-"a " "b".split() # ["a", "b"]
61 |+["a", "b"] # ['a', 'b'] 61 |+["a", "b"] # ["a", "b"]
62 62 | u"a " "b".split() # ['a', 'b'] 62 62 | u"a " "b".split() # [u"a", u"b"]
63 63 | "a " u"b".split() # ['a', 'b'] 63 63 | "a " u"b".split() # ["a", "b"]
64 64 | u"a " r"\n".split() # ['a', '\\n'] 64 64 | u"a " r"\n".split() # [u"a", u"\\n"]
SIM905.py:62:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:62:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
60 | ("a " "b").split() # ['a', 'b'] 60 | ("a " "b").split() # ["a", "b"]
61 | "a " "b".split() # ['a', 'b'] 61 | "a " "b".split() # ["a", "b"]
62 | u"a " "b".split() # ['a', 'b'] 62 | u"a " "b".split() # [u"a", u"b"]
| ^^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^^ SIM905
63 | "a " u"b".split() # ['a', 'b'] 63 | "a " u"b".split() # ["a", "b"]
64 | u"a " r"\n".split() # ['a', '\\n'] 64 | u"a " r"\n".split() # [u"a", u"\\n"]
| |
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
59 59 | r"a \n b".split() # ['a', '\\n', 'b'] 59 59 | r"a \n b".split() # [r"a", r"\n", r"b"]
60 60 | ("a " "b").split() # ['a', 'b'] 60 60 | ("a " "b").split() # ["a", "b"]
61 61 | "a " "b".split() # ['a', 'b'] 61 61 | "a " "b".split() # ["a", "b"]
62 |-u"a " "b".split() # ['a', 'b'] 62 |-u"a " "b".split() # [u"a", u"b"]
62 |+["a", "b"] # ['a', 'b'] 62 |+[u"a", u"b"] # [u"a", u"b"]
63 63 | "a " u"b".split() # ['a', 'b'] 63 63 | "a " u"b".split() # ["a", "b"]
64 64 | u"a " r"\n".split() # ['a', '\\n'] 64 64 | u"a " r"\n".split() # [u"a", u"\\n"]
65 65 | r"\n " u"\n".split() # ['\\n'] 65 65 | r"\n " u"\n".split() # [r"\n"]
SIM905.py:63:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:63:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
61 | "a " "b".split() # ['a', 'b'] 61 | "a " "b".split() # ["a", "b"]
62 | u"a " "b".split() # ['a', 'b'] 62 | u"a " "b".split() # [u"a", u"b"]
63 | "a " u"b".split() # ['a', 'b'] 63 | "a " u"b".split() # ["a", "b"]
| ^^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^^ SIM905
64 | u"a " r"\n".split() # ['a', '\\n'] 64 | u"a " r"\n".split() # [u"a", u"\\n"]
65 | r"\n " u"\n".split() # ['\\n'] 65 | r"\n " u"\n".split() # [r"\n"]
| |
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
60 60 | ("a " "b").split() # ['a', 'b'] 60 60 | ("a " "b").split() # ["a", "b"]
61 61 | "a " "b".split() # ['a', 'b'] 61 61 | "a " "b".split() # ["a", "b"]
62 62 | u"a " "b".split() # ['a', 'b'] 62 62 | u"a " "b".split() # [u"a", u"b"]
63 |-"a " u"b".split() # ['a', 'b'] 63 |-"a " u"b".split() # ["a", "b"]
63 |+["a", "b"] # ['a', 'b'] 63 |+["a", "b"] # ["a", "b"]
64 64 | u"a " r"\n".split() # ['a', '\\n'] 64 64 | u"a " r"\n".split() # [u"a", u"\\n"]
65 65 | r"\n " u"\n".split() # ['\\n'] 65 65 | r"\n " u"\n".split() # [r"\n"]
66 66 | r"\n " "\n".split() # ['\\n'] 66 66 | r"\n " "\n".split() # [r"\n"]
SIM905.py:64:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:64:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
62 | u"a " "b".split() # ['a', 'b'] 62 | u"a " "b".split() # [u"a", u"b"]
63 | "a " u"b".split() # ['a', 'b'] 63 | "a " u"b".split() # ["a", "b"]
64 | u"a " r"\n".split() # ['a', '\\n'] 64 | u"a " r"\n".split() # [u"a", u"\\n"]
| ^^^^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^^^^ SIM905
65 | r"\n " u"\n".split() # ['\\n'] 65 | r"\n " u"\n".split() # [r"\n"]
66 | r"\n " "\n".split() # ['\\n'] 66 | r"\n " "\n".split() # [r"\n"]
| |
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
61 61 | "a " "b".split() # ['a', 'b'] 61 61 | "a " "b".split() # ["a", "b"]
62 62 | u"a " "b".split() # ['a', 'b'] 62 62 | u"a " "b".split() # [u"a", u"b"]
63 63 | "a " u"b".split() # ['a', 'b'] 63 63 | "a " u"b".split() # ["a", "b"]
64 |-u"a " r"\n".split() # ['a', '\\n'] 64 |-u"a " r"\n".split() # [u"a", u"\\n"]
64 |+["a", "\\n"] # ['a', '\\n'] 64 |+[u"a", u"\\n"] # [u"a", u"\\n"]
65 65 | r"\n " u"\n".split() # ['\\n'] 65 65 | r"\n " u"\n".split() # [r"\n"]
66 66 | r"\n " "\n".split() # ['\\n'] 66 66 | r"\n " "\n".split() # [r"\n"]
67 67 | "a " r"\n".split() # ['a', '\\n'] 67 67 | "a " r"\n".split() # ["a", "\\n"]
SIM905.py:65:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:65:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
63 | "a " u"b".split() # ['a', 'b'] 63 | "a " u"b".split() # ["a", "b"]
64 | u"a " r"\n".split() # ['a', '\\n'] 64 | u"a " r"\n".split() # [u"a", u"\\n"]
65 | r"\n " u"\n".split() # ['\\n'] 65 | r"\n " u"\n".split() # [r"\n"]
| ^^^^^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^^^^^ SIM905
66 | r"\n " "\n".split() # ['\\n'] 66 | r"\n " "\n".split() # [r"\n"]
67 | "a " r"\n".split() # ['a', '\\n'] 67 | "a " r"\n".split() # ["a", "\\n"]
| |
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
62 62 | u"a " "b".split() # ['a', 'b'] 62 62 | u"a " "b".split() # [u"a", u"b"]
63 63 | "a " u"b".split() # ['a', 'b'] 63 63 | "a " u"b".split() # ["a", "b"]
64 64 | u"a " r"\n".split() # ['a', '\\n'] 64 64 | u"a " r"\n".split() # [u"a", u"\\n"]
65 |-r"\n " u"\n".split() # ['\\n'] 65 |-r"\n " u"\n".split() # [r"\n"]
65 |+["\\n"] # ['\\n'] 65 |+[r"\n"] # [r"\n"]
66 66 | r"\n " "\n".split() # ['\\n'] 66 66 | r"\n " "\n".split() # [r"\n"]
67 67 | "a " r"\n".split() # ['a', '\\n'] 67 67 | "a " r"\n".split() # ["a", "\\n"]
68 68 | 68 68 |
SIM905.py:66:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:66:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
64 | u"a " r"\n".split() # ['a', '\\n'] 64 | u"a " r"\n".split() # [u"a", u"\\n"]
65 | r"\n " u"\n".split() # ['\\n'] 65 | r"\n " u"\n".split() # [r"\n"]
66 | r"\n " "\n".split() # ['\\n'] 66 | r"\n " "\n".split() # [r"\n"]
| ^^^^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^^^^ SIM905
67 | "a " r"\n".split() # ['a', '\\n'] 67 | "a " r"\n".split() # ["a", "\\n"]
| |
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
63 63 | "a " u"b".split() # ['a', 'b'] 63 63 | "a " u"b".split() # ["a", "b"]
64 64 | u"a " r"\n".split() # ['a', '\\n'] 64 64 | u"a " r"\n".split() # [u"a", u"\\n"]
65 65 | r"\n " u"\n".split() # ['\\n'] 65 65 | r"\n " u"\n".split() # [r"\n"]
66 |-r"\n " "\n".split() # ['\\n'] 66 |-r"\n " "\n".split() # [r"\n"]
66 |+["\\n"] # ['\\n'] 66 |+[r"\n"] # [r"\n"]
67 67 | "a " r"\n".split() # ['a', '\\n'] 67 67 | "a " r"\n".split() # ["a", "\\n"]
68 68 | 68 68 |
69 69 | "a,b,c".split(',', maxsplit=0) # ['a,b,c'] 69 69 | "a,b,c".split(',', maxsplit=0) # ["a,b,c"]
SIM905.py:67:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:67:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
65 | r"\n " u"\n".split() # ['\\n'] 65 | r"\n " u"\n".split() # [r"\n"]
66 | r"\n " "\n".split() # ['\\n'] 66 | r"\n " "\n".split() # [r"\n"]
67 | "a " r"\n".split() # ['a', '\\n'] 67 | "a " r"\n".split() # ["a", "\\n"]
| ^^^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^^^ SIM905
68 | 68 |
69 | "a,b,c".split(',', maxsplit=0) # ['a,b,c'] 69 | "a,b,c".split(',', maxsplit=0) # ["a,b,c"]
| |
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
64 64 | u"a " r"\n".split() # ['a', '\\n'] 64 64 | u"a " r"\n".split() # [u"a", u"\\n"]
65 65 | r"\n " u"\n".split() # ['\\n'] 65 65 | r"\n " u"\n".split() # [r"\n"]
66 66 | r"\n " "\n".split() # ['\\n'] 66 66 | r"\n " "\n".split() # [r"\n"]
67 |-"a " r"\n".split() # ['a', '\\n'] 67 |-"a " r"\n".split() # ["a", "\\n"]
67 |+["a", "\\n"] # ['a', '\\n'] 67 |+["a", "\\n"] # ["a", "\\n"]
68 68 | 68 68 |
69 69 | "a,b,c".split(',', maxsplit=0) # ['a,b,c'] 69 69 | "a,b,c".split(',', maxsplit=0) # ["a,b,c"]
70 70 | "a,b,c".split(',', maxsplit=-1) # ['a', 'b', 'c'] 70 70 | "a,b,c".split(',', maxsplit=-1) # ["a", "b", "c"]
SIM905.py:69:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:69:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
67 | "a " r"\n".split() # ['a', '\\n'] 67 | "a " r"\n".split() # ["a", "\\n"]
68 | 68 |
69 | "a,b,c".split(',', maxsplit=0) # ['a,b,c'] 69 | "a,b,c".split(',', maxsplit=0) # ["a,b,c"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
70 | "a,b,c".split(',', maxsplit=-1) # ['a', 'b', 'c'] 70 | "a,b,c".split(',', maxsplit=-1) # ["a", "b", "c"]
71 | "a,b,c".split(',', maxsplit=-2) # ['a', 'b', 'c'] 71 | "a,b,c".split(',', maxsplit=-2) # ["a", "b", "c"]
| |
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
66 66 | r"\n " "\n".split() # ['\\n'] 66 66 | r"\n " "\n".split() # [r"\n"]
67 67 | "a " r"\n".split() # ['a', '\\n'] 67 67 | "a " r"\n".split() # ["a", "\\n"]
68 68 | 68 68 |
69 |-"a,b,c".split(',', maxsplit=0) # ['a,b,c'] 69 |-"a,b,c".split(',', maxsplit=0) # ["a,b,c"]
69 |+["a,b,c"] # ['a,b,c'] 69 |+["a,b,c"] # ["a,b,c"]
70 70 | "a,b,c".split(',', maxsplit=-1) # ['a', 'b', 'c'] 70 70 | "a,b,c".split(',', maxsplit=-1) # ["a", "b", "c"]
71 71 | "a,b,c".split(',', maxsplit=-2) # ['a', 'b', 'c'] 71 71 | "a,b,c".split(',', maxsplit=-2) # ["a", "b", "c"]
72 72 | "a,b,c".split(',', maxsplit=-0) # ['a,b,c'] 72 72 | "a,b,c".split(',', maxsplit=-0) # ["a,b,c"]
SIM905.py:70:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:70:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
69 | "a,b,c".split(',', maxsplit=0) # ['a,b,c'] 69 | "a,b,c".split(',', maxsplit=0) # ["a,b,c"]
70 | "a,b,c".split(',', maxsplit=-1) # ['a', 'b', 'c'] 70 | "a,b,c".split(',', maxsplit=-1) # ["a", "b", "c"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
71 | "a,b,c".split(',', maxsplit=-2) # ['a', 'b', 'c'] 71 | "a,b,c".split(',', maxsplit=-2) # ["a", "b", "c"]
72 | "a,b,c".split(',', maxsplit=-0) # ['a,b,c'] 72 | "a,b,c".split(',', maxsplit=-0) # ["a,b,c"]
| |
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
67 67 | "a " r"\n".split() # ['a', '\\n'] 67 67 | "a " r"\n".split() # ["a", "\\n"]
68 68 | 68 68 |
69 69 | "a,b,c".split(',', maxsplit=0) # ['a,b,c'] 69 69 | "a,b,c".split(',', maxsplit=0) # ["a,b,c"]
70 |-"a,b,c".split(',', maxsplit=-1) # ['a', 'b', 'c'] 70 |-"a,b,c".split(',', maxsplit=-1) # ["a", "b", "c"]
70 |+["a", "b", "c"] # ['a', 'b', 'c'] 70 |+["a", "b", "c"] # ["a", "b", "c"]
71 71 | "a,b,c".split(',', maxsplit=-2) # ['a', 'b', 'c'] 71 71 | "a,b,c".split(',', maxsplit=-2) # ["a", "b", "c"]
72 72 | "a,b,c".split(',', maxsplit=-0) # ['a,b,c'] 72 72 | "a,b,c".split(',', maxsplit=-0) # ["a,b,c"]
73 73 | 73 73 |
SIM905.py:71:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:71:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
69 | "a,b,c".split(',', maxsplit=0) # ['a,b,c'] 69 | "a,b,c".split(',', maxsplit=0) # ["a,b,c"]
70 | "a,b,c".split(',', maxsplit=-1) # ['a', 'b', 'c'] 70 | "a,b,c".split(',', maxsplit=-1) # ["a", "b", "c"]
71 | "a,b,c".split(',', maxsplit=-2) # ['a', 'b', 'c'] 71 | "a,b,c".split(',', maxsplit=-2) # ["a", "b", "c"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
72 | "a,b,c".split(',', maxsplit=-0) # ['a,b,c'] 72 | "a,b,c".split(',', maxsplit=-0) # ["a,b,c"]
| |
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
68 68 | 68 68 |
69 69 | "a,b,c".split(',', maxsplit=0) # ['a,b,c'] 69 69 | "a,b,c".split(',', maxsplit=0) # ["a,b,c"]
70 70 | "a,b,c".split(',', maxsplit=-1) # ['a', 'b', 'c'] 70 70 | "a,b,c".split(',', maxsplit=-1) # ["a", "b", "c"]
71 |-"a,b,c".split(',', maxsplit=-2) # ['a', 'b', 'c'] 71 |-"a,b,c".split(',', maxsplit=-2) # ["a", "b", "c"]
71 |+["a", "b", "c"] # ['a', 'b', 'c'] 71 |+["a", "b", "c"] # ["a", "b", "c"]
72 72 | "a,b,c".split(',', maxsplit=-0) # ['a,b,c'] 72 72 | "a,b,c".split(',', maxsplit=-0) # ["a,b,c"]
73 73 | 73 73 |
74 74 | # negatives 74 74 | # negatives
SIM905.py:72:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:72:1: SIM905 [*] Consider using a list literal instead of `str.split`
| |
70 | "a,b,c".split(',', maxsplit=-1) # ['a', 'b', 'c'] 70 | "a,b,c".split(',', maxsplit=-1) # ["a", "b", "c"]
71 | "a,b,c".split(',', maxsplit=-2) # ['a', 'b', 'c'] 71 | "a,b,c".split(',', maxsplit=-2) # ["a", "b", "c"]
72 | "a,b,c".split(',', maxsplit=-0) # ['a,b,c'] 72 | "a,b,c".split(',', maxsplit=-0) # ["a,b,c"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
73 | 73 |
74 | # negatives 74 | # negatives
@ -835,11 +835,11 @@ SIM905.py:72:1: SIM905 [*] Consider using a list literal instead of `str.split`
= help: Replace with list literal = help: Replace with list literal
Safe fix Safe fix
69 69 | "a,b,c".split(',', maxsplit=0) # ['a,b,c'] 69 69 | "a,b,c".split(',', maxsplit=0) # ["a,b,c"]
70 70 | "a,b,c".split(',', maxsplit=-1) # ['a', 'b', 'c'] 70 70 | "a,b,c".split(',', maxsplit=-1) # ["a", "b", "c"]
71 71 | "a,b,c".split(',', maxsplit=-2) # ['a', 'b', 'c'] 71 71 | "a,b,c".split(',', maxsplit=-2) # ["a", "b", "c"]
72 |-"a,b,c".split(',', maxsplit=-0) # ['a,b,c'] 72 |-"a,b,c".split(',', maxsplit=-0) # ["a,b,c"]
72 |+["a,b,c"] # ['a,b,c'] 72 |+["a,b,c"] # ["a,b,c"]
73 73 | 73 73 |
74 74 | # negatives 74 74 | # negatives
75 75 | 75 75 |

View file

@ -3,8 +3,9 @@ use std::cmp::Reverse;
use ruff_diagnostics::Edit; use ruff_diagnostics::Edit;
use ruff_python_ast::helpers::{map_callable, map_subscript}; use ruff_python_ast::helpers::{map_callable, map_subscript};
use ruff_python_ast::name::QualifiedName; use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::str::Quote;
use ruff_python_ast::visitor::transformer::{walk_expr, Transformer}; use ruff_python_ast::visitor::transformer::{walk_expr, Transformer};
use ruff_python_ast::{self as ast, Decorator, Expr}; use ruff_python_ast::{self as ast, Decorator, Expr, StringLiteralFlags};
use ruff_python_codegen::{Generator, Stylist}; use ruff_python_codegen::{Generator, Stylist};
use ruff_python_parser::typing::parse_type_annotation; use ruff_python_parser::typing::parse_type_annotation;
use ruff_python_semantic::{ use ruff_python_semantic::{
@ -249,6 +250,7 @@ pub(crate) fn quote_annotation(
semantic: &SemanticModel, semantic: &SemanticModel,
stylist: &Stylist, stylist: &Stylist,
locator: &Locator, locator: &Locator,
flags: StringLiteralFlags,
) -> Edit { ) -> Edit {
let expr = semantic.expression(node_id).expect("Expression not found"); let expr = semantic.expression(node_id).expect("Expression not found");
if let Some(parent_id) = semantic.parent_expression_id(node_id) { if let Some(parent_id) = semantic.parent_expression_id(node_id) {
@ -258,7 +260,7 @@ pub(crate) fn quote_annotation(
// If we're quoting the value of a subscript, we need to quote the entire // If we're quoting the value of a subscript, we need to quote the entire
// expression. For example, when quoting `DataFrame` in `DataFrame[int]`, we // expression. For example, when quoting `DataFrame` in `DataFrame[int]`, we
// should generate `"DataFrame[int]"`. // should generate `"DataFrame[int]"`.
return quote_annotation(parent_id, semantic, stylist, locator); return quote_annotation(parent_id, semantic, stylist, locator, flags);
} }
} }
Some(Expr::Attribute(parent)) => { Some(Expr::Attribute(parent)) => {
@ -266,7 +268,7 @@ pub(crate) fn quote_annotation(
// If we're quoting the value of an attribute, we need to quote the entire // If we're quoting the value of an attribute, we need to quote the entire
// expression. For example, when quoting `DataFrame` in `pd.DataFrame`, we // expression. For example, when quoting `DataFrame` in `pd.DataFrame`, we
// should generate `"pd.DataFrame"`. // should generate `"pd.DataFrame"`.
return quote_annotation(parent_id, semantic, stylist, locator); return quote_annotation(parent_id, semantic, stylist, locator, flags);
} }
} }
Some(Expr::Call(parent)) => { Some(Expr::Call(parent)) => {
@ -274,7 +276,7 @@ pub(crate) fn quote_annotation(
// If we're quoting the function of a call, we need to quote the entire // If we're quoting the function of a call, we need to quote the entire
// expression. For example, when quoting `DataFrame` in `DataFrame()`, we // expression. For example, when quoting `DataFrame` in `DataFrame()`, we
// should generate `"DataFrame()"`. // should generate `"DataFrame()"`.
return quote_annotation(parent_id, semantic, stylist, locator); return quote_annotation(parent_id, semantic, stylist, locator, flags);
} }
} }
Some(Expr::BinOp(parent)) => { Some(Expr::BinOp(parent)) => {
@ -282,14 +284,14 @@ pub(crate) fn quote_annotation(
// If we're quoting the left or right side of a binary operation, we need to // If we're quoting the left or right side of a binary operation, we need to
// quote the entire expression. For example, when quoting `DataFrame` in // quote the entire expression. For example, when quoting `DataFrame` in
// `DataFrame | Series`, we should generate `"DataFrame | Series"`. // `DataFrame | Series`, we should generate `"DataFrame | Series"`.
return quote_annotation(parent_id, semantic, stylist, locator); return quote_annotation(parent_id, semantic, stylist, locator, flags);
} }
} }
_ => {} _ => {}
} }
} }
quote_type_expression(expr, semantic, stylist, locator) quote_type_expression(expr, semantic, stylist, locator, flags)
} }
/// Wrap a type expression in quotes. /// Wrap a type expression in quotes.
@ -305,9 +307,10 @@ pub(crate) fn quote_type_expression(
semantic: &SemanticModel, semantic: &SemanticModel,
stylist: &Stylist, stylist: &Stylist,
locator: &Locator, locator: &Locator,
flags: StringLiteralFlags,
) -> Edit { ) -> Edit {
// Quote the entire expression. // Quote the entire expression.
let quote_annotator = QuoteAnnotator::new(semantic, stylist, locator); let quote_annotator = QuoteAnnotator::new(semantic, stylist, locator, flags);
Edit::range_replacement(quote_annotator.into_annotation(expr), expr.range()) Edit::range_replacement(quote_annotator.into_annotation(expr), expr.range())
} }
@ -336,6 +339,7 @@ pub(crate) struct QuoteAnnotator<'a> {
semantic: &'a SemanticModel<'a>, semantic: &'a SemanticModel<'a>,
stylist: &'a Stylist<'a>, stylist: &'a Stylist<'a>,
locator: &'a Locator<'a>, locator: &'a Locator<'a>,
flags: StringLiteralFlags,
} }
impl<'a> QuoteAnnotator<'a> { impl<'a> QuoteAnnotator<'a> {
@ -343,11 +347,13 @@ impl<'a> QuoteAnnotator<'a> {
semantic: &'a SemanticModel<'a>, semantic: &'a SemanticModel<'a>,
stylist: &'a Stylist<'a>, stylist: &'a Stylist<'a>,
locator: &'a Locator<'a>, locator: &'a Locator<'a>,
flags: StringLiteralFlags,
) -> Self { ) -> Self {
Self { Self {
semantic, semantic,
stylist, stylist,
locator, locator,
flags,
} }
} }
@ -366,7 +372,7 @@ impl<'a> QuoteAnnotator<'a> {
generator.expr(&Expr::from(ast::StringLiteral { generator.expr(&Expr::from(ast::StringLiteral {
range: TextRange::default(), range: TextRange::default(),
value: annotation.into_boxed_str(), value: annotation.into_boxed_str(),
flags: ast::StringLiteralFlags::default(), flags: self.flags,
})) }))
} }
@ -376,6 +382,12 @@ impl<'a> QuoteAnnotator<'a> {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice { if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice {
if !elts.is_empty() { if !elts.is_empty() {
self.visit_expr(&mut elts[0]); self.visit_expr(&mut elts[0]);
// The outer annotation will use the preferred quote.
// As such, any quotes found in metadata elements inside an `Annotated` slice
// should use the opposite quote to the preferred quote.
for elt in elts.iter_mut().skip(1) {
QuoteRewriter::new(self.stylist).visit_expr(elt);
}
} }
} }
} }
@ -390,8 +402,10 @@ impl Transformer for QuoteAnnotator<'_> {
.semantic .semantic
.match_typing_qualified_name(&qualified_name, "Literal") .match_typing_qualified_name(&qualified_name, "Literal")
{ {
// we don't want to modify anything inside `Literal` // The outer annotation will use the preferred quote.
// so skip visiting this subscripts' slice // As such, any quotes found inside a `Literal` slice
// should use the opposite quote to the preferred quote.
QuoteRewriter::new(self.stylist).visit_expr(slice);
} else if self } else if self
.semantic .semantic
.match_typing_qualified_name(&qualified_name, "Annotated") .match_typing_qualified_name(&qualified_name, "Annotated")
@ -420,3 +434,24 @@ impl Transformer for QuoteAnnotator<'_> {
} }
} }
} }
/// A [`Transformer`] struct that rewrites all strings in an expression
/// to use a specified quotation style
#[derive(Debug)]
struct QuoteRewriter {
preferred_inner_quote: Quote,
}
impl QuoteRewriter {
fn new(stylist: &Stylist) -> Self {
Self {
preferred_inner_quote: stylist.quote().opposite(),
}
}
}
impl Transformer for QuoteRewriter {
fn visit_string_literal(&self, literal: &mut ast::StringLiteral) {
literal.flags = literal.flags.with_quote_style(self.preferred_inner_quote);
}
}

View file

@ -68,6 +68,7 @@ pub(crate) fn runtime_cast_value(checker: &mut Checker, type_expr: &Expr) {
checker.semantic(), checker.semantic(),
checker.stylist(), checker.stylist(),
checker.locator(), checker.locator(),
checker.default_string_flags(),
); );
if checker.comment_ranges().intersects(type_expr.range()) { if checker.comment_ranges().intersects(type_expr.range()) {
diagnostic.set_fix(Fix::unsafe_edit(edit)); diagnostic.set_fix(Fix::unsafe_edit(edit));

View file

@ -278,6 +278,7 @@ fn quote_imports(checker: &Checker, node_id: NodeId, imports: &[ImportBinding])
checker.semantic(), checker.semantic(),
checker.stylist(), checker.stylist(),
checker.locator(), checker.locator(),
checker.default_string_flags(),
)) ))
} else { } else {
None None

View file

@ -175,6 +175,7 @@ pub(crate) fn unquoted_type_alias(checker: &Checker, binding: &Binding) -> Optio
checker.semantic(), checker.semantic(),
checker.stylist(), checker.stylist(),
checker.locator(), checker.locator(),
checker.default_string_flags(),
); );
let mut diagnostics = Vec::with_capacity(names.len()); let mut diagnostics = Vec::with_capacity(names.len());
for name in names { for name in names {

View file

@ -510,6 +510,7 @@ fn fix_imports(checker: &Checker, node_id: NodeId, imports: &[ImportBinding]) ->
checker.semantic(), checker.semantic(),
checker.stylist(), checker.stylist(),
checker.locator(), checker.locator(),
checker.default_string_flags(),
)) ))
} else { } else {
None None

View file

@ -63,11 +63,16 @@ fn is_static_length(elts: &[Expr]) -> bool {
fn build_fstring(joiner: &str, joinees: &[Expr]) -> Option<Expr> { fn build_fstring(joiner: &str, joinees: &[Expr]) -> Option<Expr> {
// If all elements are string constants, join them into a single string. // If all elements are string constants, join them into a single string.
if joinees.iter().all(Expr::is_string_literal_expr) { if joinees.iter().all(Expr::is_string_literal_expr) {
let mut flags = None;
let node = ast::StringLiteral { let node = ast::StringLiteral {
value: joinees value: joinees
.iter() .iter()
.filter_map(|expr| { .filter_map(|expr| {
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr { if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
if flags.is_none() {
// take the flags from the first Expr
flags = Some(value.flags());
}
Some(value.to_str()) Some(value.to_str())
} else { } else {
None None
@ -75,7 +80,8 @@ fn build_fstring(joiner: &str, joinees: &[Expr]) -> Option<Expr> {
}) })
.join(joiner) .join(joiner)
.into_boxed_str(), .into_boxed_str(),
..ast::StringLiteral::default() flags: flags?,
range: TextRange::default(),
}; };
return Some(node.into()); return Some(node.into());
} }

View file

@ -3,7 +3,7 @@ use std::fmt::{Display, Formatter};
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::name::QualifiedName; use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::{self as ast, Expr, StringLiteralFlags}; use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::SemanticModel; use ruff_python_semantic::SemanticModel;
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
@ -24,7 +24,7 @@ use crate::fix::edits::add_argument;
/// encoding. [PEP 597] recommends the use of `encoding="utf-8"` as a default, /// encoding. [PEP 597] recommends the use of `encoding="utf-8"` as a default,
/// and suggests that it may become the default in future versions of Python. /// and suggests that it may become the default in future versions of Python.
/// ///
/// If a local-specific encoding is intended, use `encoding="local"` on /// If a locale-specific encoding is intended, use `encoding="locale"` on
/// Python 3.10 and later, or `locale.getpreferredencoding()` on earlier versions, /// Python 3.10 and later, or `locale.getpreferredencoding()` on earlier versions,
/// to make the encoding explicit. /// to make the encoding explicit.
/// ///
@ -158,16 +158,11 @@ fn generate_keyword_fix(checker: &Checker, call: &ast::ExprCall) -> Fix {
Fix::unsafe_edit(add_argument( Fix::unsafe_edit(add_argument(
&format!( &format!(
"encoding={}", "encoding={}",
checker checker.generator().expr(&Expr::from(ast::StringLiteral {
.generator() value: Box::from("utf-8"),
.expr(&Expr::StringLiteral(ast::ExprStringLiteral { flags: checker.default_string_flags(),
value: ast::StringLiteralValue::single(ast::StringLiteral { range: TextRange::default(),
value: "utf-8".to_string().into_boxed_str(), }))
flags: StringLiteralFlags::default(),
range: TextRange::default(),
}),
range: TextRange::default(),
}))
), ),
&call.arguments, &call.arguments,
checker.comment_ranges(), checker.comment_ranges(),

View file

@ -3,7 +3,7 @@ use std::str::FromStr;
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::{self as ast, Expr, Int, LiteralExpressionRef, UnaryOp}; use ruff_python_ast::{self as ast, Expr, Int, LiteralExpressionRef, StringLiteralFlags, UnaryOp};
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -33,9 +33,14 @@ impl FromStr for LiteralType {
} }
impl LiteralType { impl LiteralType {
fn as_zero_value_expr(self) -> Expr { fn as_zero_value_expr(self, flags: StringLiteralFlags) -> Expr {
match self { match self {
LiteralType::Str => ast::ExprStringLiteral::default().into(), LiteralType::Str => ast::StringLiteral {
value: Box::default(),
range: TextRange::default(),
flags,
}
.into(),
LiteralType::Bytes => ast::ExprBytesLiteral::default().into(), LiteralType::Bytes => ast::ExprBytesLiteral::default().into(),
LiteralType::Int => ast::ExprNumberLiteral { LiteralType::Int => ast::ExprNumberLiteral {
value: ast::Number::Int(Int::from(0u8)), value: ast::Number::Int(Int::from(0u8)),
@ -186,7 +191,7 @@ pub(crate) fn native_literals(
return; return;
} }
let expr = literal_type.as_zero_value_expr(); let expr = literal_type.as_zero_value_expr(checker.default_string_flags());
let content = checker.generator().expr(&expr); let content = checker.generator().expr(&expr);
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
content, content,

View file

@ -1,6 +1,5 @@
--- ---
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
snapshot_kind: text
--- ---
UP007.py:5:10: UP007 [*] Use `X | Y` for type annotations UP007.py:5:10: UP007 [*] Use `X | Y` for type annotations
| |
@ -295,3 +294,23 @@ UP007.py:83:10: UP007 [*] Use `X | Y` for type annotations
83 |-def f(x: Union[int, str, bytes]) -> None: 83 |-def f(x: Union[int, str, bytes]) -> None:
83 |+def f(x: int | str | bytes) -> None: 83 |+def f(x: int | str | bytes) -> None:
84 84 | ... 84 84 | ...
85 85 |
86 86 |
UP007.py:91:26: UP007 [*] Use `X | Y` for type annotations
|
89 | ...
90 |
91 | def myfunc(param: "tuple[Union[int, 'AClass', None], str]"):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP007
92 | print(param)
|
= help: Convert to `X | Y`
Safe fix
88 88 | class AClass:
89 89 | ...
90 90 |
91 |-def myfunc(param: "tuple[Union[int, 'AClass', None], str]"):
91 |+def myfunc(param: "tuple[int | 'AClass' | None, str]"):
92 92 | print(param)

View file

@ -66,7 +66,8 @@ pub(crate) fn assert_with_print_message(checker: &mut Checker, stmt: &ast::StmtA
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().stmt(&Stmt::Assert(ast::StmtAssert { checker.generator().stmt(&Stmt::Assert(ast::StmtAssert {
test: stmt.test.clone(), test: stmt.test.clone(),
msg: print_arguments::to_expr(&call.arguments).map(Box::new), msg: print_arguments::to_expr(&call.arguments, checker.default_string_flags())
.map(Box::new),
range: TextRange::default(), range: TextRange::default(),
})), })),
// We have to replace the entire statement, // We have to replace the entire statement,
@ -140,12 +141,13 @@ mod print_arguments {
/// literals. /// literals.
fn fstring_elements_to_string_literals<'a>( fn fstring_elements_to_string_literals<'a>(
mut elements: impl ExactSizeIterator<Item = &'a FStringElement>, mut elements: impl ExactSizeIterator<Item = &'a FStringElement>,
flags: StringLiteralFlags,
) -> Option<Vec<StringLiteral>> { ) -> Option<Vec<StringLiteral>> {
elements.try_fold(Vec::with_capacity(elements.len()), |mut acc, element| { elements.try_fold(Vec::with_capacity(elements.len()), |mut acc, element| {
if let FStringElement::Literal(literal) = element { if let FStringElement::Literal(literal) = element {
acc.push(StringLiteral { acc.push(StringLiteral {
value: literal.value.clone(), value: literal.value.clone(),
flags: StringLiteralFlags::default(), flags,
range: TextRange::default(), range: TextRange::default(),
}); });
Some(acc) Some(acc)
@ -162,6 +164,7 @@ mod print_arguments {
fn args_to_string_literal_expr<'a>( fn args_to_string_literal_expr<'a>(
args: impl ExactSizeIterator<Item = &'a Vec<FStringElement>>, args: impl ExactSizeIterator<Item = &'a Vec<FStringElement>>,
sep: impl ExactSizeIterator<Item = &'a FStringElement>, sep: impl ExactSizeIterator<Item = &'a FStringElement>,
flags: StringLiteralFlags,
) -> Option<Expr> { ) -> Option<Expr> {
// If there are no arguments, short-circuit and return `None` // If there are no arguments, short-circuit and return `None`
if args.len() == 0 { if args.len() == 0 {
@ -174,8 +177,8 @@ mod print_arguments {
// of a concatenated string literal. (e.g. "text", "text" "text") The `sep` will // of a concatenated string literal. (e.g. "text", "text" "text") The `sep` will
// be inserted only between the outer Vecs. // be inserted only between the outer Vecs.
let (Some(sep), Some(args)) = ( let (Some(sep), Some(args)) = (
fstring_elements_to_string_literals(sep), fstring_elements_to_string_literals(sep, flags),
args.map(|arg| fstring_elements_to_string_literals(arg.iter())) args.map(|arg| fstring_elements_to_string_literals(arg.iter(), flags))
.collect::<Option<Vec<_>>>(), .collect::<Option<Vec<_>>>(),
) else { ) else {
// If any of the arguments are not string literals, return None // If any of the arguments are not string literals, return None
@ -203,7 +206,7 @@ mod print_arguments {
range: TextRange::default(), range: TextRange::default(),
value: StringLiteralValue::single(StringLiteral { value: StringLiteralValue::single(StringLiteral {
value: combined_string.into(), value: combined_string.into(),
flags: StringLiteralFlags::default(), flags,
range: TextRange::default(), range: TextRange::default(),
}), }),
})) }))
@ -256,7 +259,7 @@ mod print_arguments {
/// - [`Some`]<[`Expr::StringLiteral`]> if all arguments including `sep` are string literals. /// - [`Some`]<[`Expr::StringLiteral`]> if all arguments including `sep` are string literals.
/// - [`Some`]<[`Expr::FString`]> if any of the arguments are not string literals. /// - [`Some`]<[`Expr::FString`]> if any of the arguments are not string literals.
/// - [`None`] if the `print` contains no positional arguments at all. /// - [`None`] if the `print` contains no positional arguments at all.
pub(super) fn to_expr(arguments: &Arguments) -> Option<Expr> { pub(super) fn to_expr(arguments: &Arguments, flags: StringLiteralFlags) -> Option<Expr> {
// Convert the `sep` argument into `FStringElement`s // Convert the `sep` argument into `FStringElement`s
let sep = arguments let sep = arguments
.find_keyword("sep") .find_keyword("sep")
@ -286,7 +289,7 @@ mod print_arguments {
// Attempt to convert the `sep` and `args` arguments to a string literal, // Attempt to convert the `sep` and `args` arguments to a string literal,
// falling back to an f-string if the arguments are not all string literals. // falling back to an f-string if the arguments are not all string literals.
args_to_string_literal_expr(args.iter(), sep.iter()) args_to_string_literal_expr(args.iter(), sep.iter(), flags)
.or_else(|| args_to_fstring_expr(args.into_iter(), sep.into_iter())) .or_else(|| args_to_fstring_expr(args.into_iter(), sep.into_iter()))
} }
} }

View file

@ -1,6 +1,5 @@
--- ---
source: crates/ruff_linter/src/rules/ruff/mod.rs source: crates/ruff_linter/src/rules/ruff/mod.rs
snapshot_kind: text
--- ---
RUF055_0.py:6:1: RUF055 [*] Plain string pattern passed to `re` function RUF055_0.py:6:1: RUF055 [*] Plain string pattern passed to `re` function
| |
@ -211,12 +210,16 @@ RUF055_0.py:94:1: RUF055 [*] Plain string pattern passed to `re` function
94 |-re.sub(r"a", "\?", "a") 94 |-re.sub(r"a", "\?", "a")
94 |+"a".replace(r"a", "\\?") 94 |+"a".replace(r"a", "\\?")
95 95 | re.sub(r"a", r"\?", "a") 95 95 | re.sub(r"a", r"\?", "a")
96 96 |
97 97 | # these double as tests for preserving raw string quoting style
RUF055_0.py:95:1: RUF055 [*] Plain string pattern passed to `re` function RUF055_0.py:95:1: RUF055 [*] Plain string pattern passed to `re` function
| |
94 | re.sub(r"a", "\?", "a") 94 | re.sub(r"a", "\?", "a")
95 | re.sub(r"a", r"\?", "a") 95 | re.sub(r"a", r"\?", "a")
| ^^^^^^^^^^^^^^^^^^^^^^^^ RUF055 | ^^^^^^^^^^^^^^^^^^^^^^^^ RUF055
96 |
97 | # these double as tests for preserving raw string quoting style
| |
= help: Replace with `"a".replace(r"a", r"\?")` = help: Replace with `"a".replace(r"a", r"\?")`
@ -226,3 +229,59 @@ RUF055_0.py:95:1: RUF055 [*] Plain string pattern passed to `re` function
94 94 | re.sub(r"a", "\?", "a") 94 94 | re.sub(r"a", "\?", "a")
95 |-re.sub(r"a", r"\?", "a") 95 |-re.sub(r"a", r"\?", "a")
95 |+"a".replace(r"a", r"\?") 95 |+"a".replace(r"a", r"\?")
96 96 |
97 97 | # these double as tests for preserving raw string quoting style
98 98 | re.sub(r'abc', "", s)
RUF055_0.py:98:1: RUF055 [*] Plain string pattern passed to `re` function
|
97 | # these double as tests for preserving raw string quoting style
98 | re.sub(r'abc', "", s)
| ^^^^^^^^^^^^^^^^^^^^^ RUF055
99 | re.sub(r"""abc""", "", s)
100 | re.sub(r'''abc''', "", s)
|
= help: Replace with `s.replace(r'abc', "")`
Safe fix
95 95 | re.sub(r"a", r"\?", "a")
96 96 |
97 97 | # these double as tests for preserving raw string quoting style
98 |-re.sub(r'abc', "", s)
98 |+s.replace(r'abc', "")
99 99 | re.sub(r"""abc""", "", s)
100 100 | re.sub(r'''abc''', "", s)
RUF055_0.py:99:1: RUF055 [*] Plain string pattern passed to `re` function
|
97 | # these double as tests for preserving raw string quoting style
98 | re.sub(r'abc', "", s)
99 | re.sub(r"""abc""", "", s)
| ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF055
100 | re.sub(r'''abc''', "", s)
|
= help: Replace with `s.replace(r"""abc""", "")`
Safe fix
96 96 |
97 97 | # these double as tests for preserving raw string quoting style
98 98 | re.sub(r'abc', "", s)
99 |-re.sub(r"""abc""", "", s)
99 |+s.replace(r"""abc""", "")
100 100 | re.sub(r'''abc''', "", s)
RUF055_0.py:100:1: RUF055 [*] Plain string pattern passed to `re` function
|
98 | re.sub(r'abc', "", s)
99 | re.sub(r"""abc""", "", s)
100 | re.sub(r'''abc''', "", s)
| ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF055
|
= help: Replace with `s.replace(r'''abc''', "")`
Safe fix
97 97 | # these double as tests for preserving raw string quoting style
98 98 | re.sub(r'abc', "", s)
99 99 | re.sub(r"""abc""", "", s)
100 |-re.sub(r'''abc''', "", s)
100 |+s.replace(r'''abc''', "")

View file

@ -1221,14 +1221,14 @@ impl fmt::Debug for FStringElements {
/// An AST node that represents either a single string literal or an implicitly /// An AST node that represents either a single string literal or an implicitly
/// concatenated string literals. /// concatenated string literals.
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ExprStringLiteral { pub struct ExprStringLiteral {
pub range: TextRange, pub range: TextRange,
pub value: StringLiteralValue, pub value: StringLiteralValue,
} }
/// The value representing a [`ExprStringLiteral`]. /// The value representing a [`ExprStringLiteral`].
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct StringLiteralValue { pub struct StringLiteralValue {
inner: StringLiteralValueInner, inner: StringLiteralValueInner,
} }
@ -1241,6 +1241,18 @@ impl StringLiteralValue {
} }
} }
/// Returns the [`StringLiteralFlags`] associated with this string literal.
///
/// For an implicitly concatenated string, it returns the flags for the first literal.
pub fn flags(&self) -> StringLiteralFlags {
self.iter()
.next()
.expect(
"There should always be at least one string literal in an `ExprStringLiteral` node",
)
.flags
}
/// Creates a new string literal with the given values that represents an /// Creates a new string literal with the given values that represents an
/// implicitly concatenated strings. /// implicitly concatenated strings.
/// ///
@ -1371,12 +1383,6 @@ enum StringLiteralValueInner {
Concatenated(ConcatenatedStringLiteral), Concatenated(ConcatenatedStringLiteral),
} }
impl Default for StringLiteralValueInner {
fn default() -> Self {
Self::Single(StringLiteral::default())
}
}
bitflags! { bitflags! {
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
struct StringLiteralFlagsInner: u8 { struct StringLiteralFlagsInner: u8 {
@ -1414,10 +1420,33 @@ bitflags! {
/// Flags that can be queried to obtain information /// Flags that can be queried to obtain information
/// regarding the prefixes and quotes used for a string literal. /// regarding the prefixes and quotes used for a string literal.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] ///
/// ## Notes on usage
///
/// If you're using a `Generator` from the `ruff_python_codegen` crate to generate a lint-rule fix
/// from an existing string literal, consider passing along the [`StringLiteral::flags`] field or
/// the result of the [`StringLiteralValue::flags`] method. If you don't have an existing string but
/// have a `Checker` from the `ruff_linter` crate available, consider using
/// `Checker::default_string_flags` to create instances of this struct; this method will properly
/// handle surrounding f-strings. For usage that doesn't fit into one of these categories, the
/// public constructor [`StringLiteralFlags::empty`] can be used.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct StringLiteralFlags(StringLiteralFlagsInner); pub struct StringLiteralFlags(StringLiteralFlagsInner);
impl StringLiteralFlags { impl StringLiteralFlags {
/// Construct a new [`StringLiteralFlags`] with **no flags set**.
///
/// See [`StringLiteralFlags::with_quote_style`], [`StringLiteralFlags::with_triple_quotes`],
/// and [`StringLiteralFlags::with_prefix`] for ways of setting the quote style (single or
/// double), enabling triple quotes, and adding prefixes (such as `r` or `u`), respectively.
///
/// See the documentation for [`StringLiteralFlags`] for additional caveats on this constructor,
/// and situations in which alternative ways to construct this struct should be used, especially
/// when writing lint rules.
pub fn empty() -> Self {
Self(StringLiteralFlagsInner::empty())
}
#[must_use] #[must_use]
pub fn with_quote_style(mut self, quote_style: Quote) -> Self { pub fn with_quote_style(mut self, quote_style: Quote) -> Self {
self.0 self.0
@ -1520,7 +1549,7 @@ impl fmt::Debug for StringLiteralFlags {
/// An AST node that represents a single string literal which is part of an /// An AST node that represents a single string literal which is part of an
/// [`ExprStringLiteral`]. /// [`ExprStringLiteral`].
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct StringLiteral { pub struct StringLiteral {
pub range: TextRange, pub range: TextRange,
pub value: Box<str>, pub value: Box<str>,
@ -1546,7 +1575,7 @@ impl StringLiteral {
Self { Self {
range, range,
value: "".into(), value: "".into(),
flags: StringLiteralFlags::default().with_invalid(), flags: StringLiteralFlags::empty().with_invalid(),
} }
} }
} }
@ -2115,7 +2144,7 @@ impl From<AnyStringFlags> for StringLiteralFlags {
value.prefix() value.prefix()
) )
}; };
let new = StringLiteralFlags::default() let new = StringLiteralFlags::empty()
.with_quote_style(value.quote_style()) .with_quote_style(value.quote_style())
.with_prefix(prefix); .with_prefix(prefix);
if value.is_triple_quoted() { if value.is_triple_quoted() {

View file

@ -6,8 +6,8 @@ use ruff_python_ast::str::Quote;
use ruff_python_ast::{ use ruff_python_ast::{
self as ast, Alias, ArgOrKeyword, BoolOp, CmpOp, Comprehension, ConversionFlag, DebugText, self as ast, Alias, ArgOrKeyword, BoolOp, CmpOp, Comprehension, ConversionFlag, DebugText,
ExceptHandler, Expr, Identifier, MatchCase, Operator, Parameter, Parameters, Pattern, ExceptHandler, Expr, Identifier, MatchCase, Operator, Parameter, Parameters, Pattern,
Singleton, Stmt, Suite, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, Singleton, Stmt, StringFlags, Suite, TypeParam, TypeParamParamSpec, TypeParamTypeVar,
WithItem, TypeParamTypeVarTuple, WithItem,
}; };
use ruff_python_ast::{ParameterWithDefault, TypeParams}; use ruff_python_ast::{ParameterWithDefault, TypeParams};
use ruff_python_literal::escape::{AsciiEscape, Escape, UnicodeEscape}; use ruff_python_literal::escape::{AsciiEscape, Escape, UnicodeEscape};
@ -65,7 +65,10 @@ mod precedence {
pub struct Generator<'a> { pub struct Generator<'a> {
/// The indentation style to use. /// The indentation style to use.
indent: &'a Indentation, indent: &'a Indentation,
/// The quote style to use for string literals. /// The quote style to use for bytestring and f-string literals. For a plain
/// [`StringLiteral`](ast::StringLiteral), modify its `flags` field using
/// [`StringLiteralFlags::with_quote_style`](ast::StringLiteralFlags::with_quote_style) before
/// passing it to the [`Generator`].
quote: Quote, quote: Quote,
/// The line ending to use. /// The line ending to use.
line_ending: LineEnding, line_ending: LineEnding,
@ -158,8 +161,8 @@ impl<'a> Generator<'a> {
escape.bytes_repr().write(&mut self.buffer).unwrap(); // write to string doesn't fail escape.bytes_repr().write(&mut self.buffer).unwrap(); // write to string doesn't fail
} }
fn p_str_repr(&mut self, s: &str) { fn p_str_repr(&mut self, s: &str, quote: Quote) {
let escape = UnicodeEscape::with_preferred_quote(s, self.quote); let escape = UnicodeEscape::with_preferred_quote(s, quote);
if let Some(len) = escape.layout().len { if let Some(len) = escape.layout().len {
self.buffer.reserve(len); self.buffer.reserve(len);
} }
@ -1288,14 +1291,14 @@ impl<'a> Generator<'a> {
// replacement here // replacement here
if flags.prefix().is_raw() { if flags.prefix().is_raw() {
self.p(flags.prefix().as_str()); self.p(flags.prefix().as_str());
self.p(self.quote.as_str()); self.p(flags.quote_str());
self.p(value); self.p(value);
self.p(self.quote.as_str()); self.p(flags.quote_str());
} else { } else {
if flags.prefix().is_unicode() { if flags.prefix().is_unicode() {
self.p("u"); self.p("u");
} }
self.p_str_repr(value); self.p_str_repr(value, flags.quote_style());
} }
} }
@ -1403,7 +1406,7 @@ impl<'a> Generator<'a> {
Generator::new(self.indent, self.quote.opposite(), self.line_ending); Generator::new(self.indent, self.quote.opposite(), self.line_ending);
generator.unparse_f_string_body(values); generator.unparse_f_string_body(values);
let body = &generator.buffer; let body = &generator.buffer;
self.p_str_repr(body); self.p_str_repr(body, self.quote);
} }
} }
@ -1444,6 +1447,24 @@ mod tests {
generator.generate() generator.generate()
} }
/// Like [`round_trip`] but configure the [`Generator`] with the requested `indentation`,
/// `quote`, and `line_ending` settings.
///
/// Note that quoting styles for string literals are taken from their [`StringLiteralFlags`],
/// not from the [`Generator`] itself, so using this function on a plain string literal can give
/// surprising results.
///
/// ```rust
/// assert_eq!(
/// round_trip_with(
/// &Indentation::default(),
/// Quote::Double,
/// LineEnding::default(),
/// r#"'hello'"#
/// ),
/// r#"'hello'"#
/// );
/// ```
fn round_trip_with( fn round_trip_with(
indentation: &Indentation, indentation: &Indentation,
quote: Quote, quote: Quote,
@ -1719,14 +1740,14 @@ class Foo:
#[test] #[test]
fn quote() { fn quote() {
assert_eq!(round_trip(r#""hello""#), r#""hello""#); assert_eq!(round_trip(r#""hello""#), r#""hello""#);
assert_eq!(round_trip(r"'hello'"), r#""hello""#); assert_round_trip!(r"'hello'");
assert_eq!(round_trip(r"u'hello'"), r#"u"hello""#); assert_round_trip!(r"u'hello'");
assert_eq!(round_trip(r"r'hello'"), r#"r"hello""#); assert_round_trip!(r"r'hello'");
assert_eq!(round_trip(r"b'hello'"), r#"b"hello""#); assert_eq!(round_trip(r"b'hello'"), r#"b"hello""#);
assert_eq!(round_trip(r#"("abc" "def" "ghi")"#), r#""abc" "def" "ghi""#); assert_eq!(round_trip(r#"("abc" "def" "ghi")"#), r#""abc" "def" "ghi""#);
assert_eq!(round_trip(r#""he\"llo""#), r#"'he"llo'"#); assert_eq!(round_trip(r#""he\"llo""#), r#"'he"llo'"#);
assert_eq!(round_trip(r#"f"abc{'def'}{1}""#), r#"f"abc{'def'}{1}""#); assert_eq!(round_trip(r#"f"abc{'def'}{1}""#), r#"f"abc{'def'}{1}""#);
assert_eq!(round_trip(r#"f'abc{"def"}{1}'"#), r#"f"abc{'def'}{1}""#); assert_round_trip!(r#"f'abc{"def"}{1}'"#);
} }
#[test] #[test]
@ -1773,42 +1794,37 @@ if True:
#[test] #[test]
fn set_quote() { fn set_quote() {
assert_eq!( macro_rules! round_trip_with {
round_trip_with( ($quote:expr, $start:expr, $end:expr) => {
&Indentation::default(), assert_eq!(
Quote::Double, round_trip_with(
LineEnding::default(), &Indentation::default(),
r#""hello""# $quote,
), LineEnding::default(),
r#""hello""# $start
); ),
assert_eq!( $end,
round_trip_with( );
&Indentation::default(), };
Quote::Single, }
LineEnding::default(),
r#""hello""# // setting Generator::quote works for bytestrings
), round_trip_with!(Quote::Double, r#"b"hello""#, r#"b"hello""#);
r"'hello'" round_trip_with!(Quote::Single, r#"b"hello""#, r"b'hello'");
); round_trip_with!(Quote::Double, r"b'hello'", r#"b"hello""#);
assert_eq!( round_trip_with!(Quote::Single, r"b'hello'", r"b'hello'");
round_trip_with(
&Indentation::default(), // and for f-strings
Quote::Double, round_trip_with!(Quote::Double, r#"f"hello""#, r#"f"hello""#);
LineEnding::default(), round_trip_with!(Quote::Single, r#"f"hello""#, r"f'hello'");
r"'hello'" round_trip_with!(Quote::Double, r"f'hello'", r#"f"hello""#);
), round_trip_with!(Quote::Single, r"f'hello'", r"f'hello'");
r#""hello""#
); // but not for string literals, where the `Quote` is taken directly from their flags
assert_eq!( round_trip_with!(Quote::Double, r#""hello""#, r#""hello""#);
round_trip_with( round_trip_with!(Quote::Single, r#""hello""#, r#""hello""#); // no effect
&Indentation::default(), round_trip_with!(Quote::Double, r"'hello'", r#"'hello'"#); // no effect
Quote::Single, round_trip_with!(Quote::Single, r"'hello'", r"'hello'");
LineEnding::default(),
r"'hello'"
),
r"'hello'"
);
} }
#[test] #[test]

View file

@ -4,12 +4,12 @@ use {
regex::Regex, regex::Regex,
}; };
use ruff_python_ast::visitor::transformer;
use ruff_python_ast::visitor::transformer::Transformer; use ruff_python_ast::visitor::transformer::Transformer;
use ruff_python_ast::{ use ruff_python_ast::{
self as ast, BytesLiteralFlags, Expr, FStringElement, FStringFlags, FStringLiteralElement, self as ast, BytesLiteralFlags, Expr, FStringElement, FStringFlags, FStringLiteralElement,
FStringPart, Stmt, StringFlags, StringLiteralFlags, FStringPart, Stmt, StringFlags,
}; };
use ruff_python_ast::{visitor::transformer, StringLiteralFlags};
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
/// A struct to normalize AST nodes for the purpose of comparing formatted representations for /// A struct to normalize AST nodes for the purpose of comparing formatted representations for
@ -81,7 +81,7 @@ impl Transformer for Normalizer {
string.value = ast::StringLiteralValue::single(ast::StringLiteral { string.value = ast::StringLiteralValue::single(ast::StringLiteral {
value: string.value.to_str().to_string().into_boxed_str(), value: string.value.to_str().to_string().into_boxed_str(),
range: string.range, range: string.range,
flags: StringLiteralFlags::default(), flags: StringLiteralFlags::empty(),
}); });
} }
} }

View file

@ -205,7 +205,7 @@ pub fn parse_parenthesized_expression_range(
/// ///
/// let string = StringLiteral { /// let string = StringLiteral {
/// value: "'''\n int | str'''".to_string().into_boxed_str(), /// value: "'''\n int | str'''".to_string().into_boxed_str(),
/// flags: StringLiteralFlags::default(), /// flags: StringLiteralFlags::empty(),
/// range: TextRange::new(TextSize::new(0), TextSize::new(16)), /// range: TextRange::new(TextSize::new(0), TextSize::new(16)),
/// }; /// };
/// let parsed = parse_string_annotation("'''\n int | str'''", &string); /// let parsed = parse_string_annotation("'''\n int | str'''", &string);