Generate verbatim nodes

This commit is contained in:
Charlie Marsh 2024-01-28 21:10:24 -05:00
parent 7329bf459c
commit 00b3663b2d
14 changed files with 80 additions and 49 deletions

1
Cargo.lock generated
View File

@ -2287,6 +2287,7 @@ dependencies = [
"ruff_python_literal", "ruff_python_literal",
"ruff_python_parser", "ruff_python_parser",
"ruff_source_file", "ruff_source_file",
"ruff_text_size",
] ]
[[package]] [[package]]

View File

@ -180,6 +180,7 @@ impl<'a> Checker<'a> {
self.f_string_quote_style().unwrap_or(self.stylist.quote()), self.f_string_quote_style().unwrap_or(self.stylist.quote()),
self.stylist.line_ending(), self.stylist.line_ending(),
) )
.with_locator(self.locator)
} }
/// 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

View File

@ -360,20 +360,6 @@ S608.py:48:12: S608 Possible SQL injection vector through string-based query con
54 | def query38(): 54 | def query38():
| |
S608.py:55:12: S608 Possible SQL injection vector through string-based query construction
|
54 | def query38():
55 | return """
| ____________^
56 | | SELECT *
57 | | FROM TABLE
58 | | WHERE var =
59 | | """ + var
| |_____________^ S608
60 |
61 | def query39():
|
S608.py:62:12: S608 Possible SQL injection vector through string-based query construction S608.py:62:12: S608 Possible SQL injection vector through string-based query construction
| |
61 | def query39(): 61 | def query39():

View File

@ -79,14 +79,15 @@ B006_B008.py:77:31: B006 [*] Do not use mutable data structures for argument def
75 75 | 75 75 |
76 76 | 76 76 |
77 |-def multiline_arg_wrong(value={ 77 |-def multiline_arg_wrong(value={
78 |-
79 |-}):
77 |+def multiline_arg_wrong(value=None): 77 |+def multiline_arg_wrong(value=None):
78 |+ if value is None: 78 |+ if value is None:
79 |+ value = {} 79 |+ value = {
80 80 | ... 78 80 |
81 81 | 79 |-}):
82 82 | def single_line_func_wrong(value = {}): ... 81 |+ }
80 82 | ...
81 83 |
82 84 | def single_line_func_wrong(value = {}): ...
B006_B008.py:82:36: B006 Do not use mutable data structures for argument defaults B006_B008.py:82:36: B006 Do not use mutable data structures for argument defaults
| |
@ -193,7 +194,7 @@ B006_B008.py:102:46: B006 [*] Do not use mutable data structures for argument de
102 |-def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]): 102 |-def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
102 |+def list_comprehension_also_not_okay(default=None): 102 |+def list_comprehension_also_not_okay(default=None):
103 |+ if default is None: 103 |+ if default is None:
104 |+ default = [i ** 2 for i in range(3)] 104 |+ default = [i**2 for i in range(3)]
103 105 | pass 103 105 | pass
104 106 | 104 106 |
105 107 | 105 107 |
@ -213,7 +214,7 @@ B006_B008.py:106:46: B006 [*] Do not use mutable data structures for argument de
106 |-def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}): 106 |-def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
106 |+def dict_comprehension_also_not_okay(default=None): 106 |+def dict_comprehension_also_not_okay(default=None):
107 |+ if default is None: 107 |+ if default is None:
108 |+ default = {i: i ** 2 for i in range(3)} 108 |+ default = {i: i**2 for i in range(3)}
107 109 | pass 107 109 | pass
108 110 | 108 110 |
109 111 | 109 111 |
@ -233,7 +234,7 @@ B006_B008.py:110:45: B006 [*] Do not use mutable data structures for argument de
110 |-def set_comprehension_also_not_okay(default={i**2 for i in range(3)}): 110 |-def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
110 |+def set_comprehension_also_not_okay(default=None): 110 |+def set_comprehension_also_not_okay(default=None):
111 |+ if default is None: 111 |+ if default is None:
112 |+ default = {i ** 2 for i in range(3)} 112 |+ default = {i**2 for i in range(3)}
111 113 | pass 111 113 | pass
112 114 | 112 114 |
113 115 | 113 115 |
@ -486,10 +487,12 @@ B006_B008.py:302:52: B006 [*] Do not use mutable data structures for argument de
302 |+def single_line_func_wrong(value: dict[str, str] = None): 302 |+def single_line_func_wrong(value: dict[str, str] = None):
305 303 | """Docstring""" 305 303 | """Docstring"""
304 |+ if value is None: 304 |+ if value is None:
305 |+ value = {} 305 |+ value = {
306 306 | 306 |+ # This is a comment
307 307 | 307 |+ }
308 308 | def single_line_func_wrong(value: dict[str, str] = {}) \ 306 308 |
307 309 |
308 310 | def single_line_func_wrong(value: dict[str, str] = {}) \
B006_B008.py:308:52: B006 Do not use mutable data structures for argument defaults B006_B008.py:308:52: B006 Do not use mutable data structures for argument defaults
| |

View File

@ -195,7 +195,10 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
}); });
let bool_op = node; let bool_op = node;
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&bool_op), checker
.generator()
.with_locator(checker.locator())
.expr(&bool_op),
expr.range(), expr.range(),
))); )));
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);

View File

@ -382,7 +382,9 @@ PYI016.py:44:30: PYI016 [*] Duplicate union member `typing.Literal[1]`
46 46 | # Shouldn't emit if in new parent type 46 46 | # Shouldn't emit if in new parent type
47 47 | field16: int | dict[int, str] # OK 47 47 | field16: int | dict[int, str] # OK
PYI016.py:57:5: PYI016 Duplicate union member `set[int]` PYI016.py:57:5: PYI016 Duplicate union member `set[
int # bar
]`
| |
55 | int # foo 55 | int # foo
56 | ], 56 | ],
@ -393,7 +395,9 @@ PYI016.py:57:5: PYI016 Duplicate union member `set[int]`
| |_____^ PYI016 | |_____^ PYI016
60 | ] # Error, newline and comment will not be emitted in message 60 | ] # Error, newline and comment will not be emitted in message
| |
= help: Remove duplicate union member `set[int]` = help: Remove duplicate union member `set[
int # bar
]`
PYI016.py:63:28: PYI016 Duplicate union member `int` PYI016.py:63:28: PYI016 Duplicate union member `int`
| |

View File

@ -382,7 +382,9 @@ PYI016.pyi:44:30: PYI016 [*] Duplicate union member `typing.Literal[1]`
46 46 | # Shouldn't emit if in new parent type 46 46 | # Shouldn't emit if in new parent type
47 47 | field16: int | dict[int, str] # OK 47 47 | field16: int | dict[int, str] # OK
PYI016.pyi:57:5: PYI016 Duplicate union member `set[int]` PYI016.pyi:57:5: PYI016 Duplicate union member `set[
int # bar
]`
| |
55 | int # foo 55 | int # foo
56 | ], 56 | ],
@ -393,7 +395,9 @@ PYI016.pyi:57:5: PYI016 Duplicate union member `set[int]`
| |_____^ PYI016 | |_____^ PYI016
60 | ] # Error, newline and comment will not be emitted in message 60 | ] # Error, newline and comment will not be emitted in message
| |
= help: Remove duplicate union member `set[int]` = help: Remove duplicate union member `set[
int # bar
]`
PYI016.pyi:63:28: PYI016 Duplicate union member `int` PYI016.pyi:63:28: PYI016 Duplicate union member `int`
| |

View File

@ -498,7 +498,7 @@ PT009.py:73:9: PT009 [*] Use a regular `assert` instead of unittest-style `asser
71 71 | 71 71 |
72 72 | def test_assert_regex(self): 72 72 | def test_assert_regex(self):
73 |- self.assertRegex("abc", r"def") # Error 73 |- self.assertRegex("abc", r"def") # Error
73 |+ assert re.search("def", "abc") # Error 73 |+ assert re.search(r"def", "abc") # Error
74 74 | 74 74 |
75 75 | def test_assert_not_regex(self): 75 75 | def test_assert_not_regex(self):
76 76 | self.assertNotRegex("abc", r"abc") # Error 76 76 | self.assertNotRegex("abc", r"abc") # Error
@ -518,7 +518,7 @@ PT009.py:76:9: PT009 [*] Use a regular `assert` instead of unittest-style `asser
74 74 | 74 74 |
75 75 | def test_assert_not_regex(self): 75 75 | def test_assert_not_regex(self):
76 |- self.assertNotRegex("abc", r"abc") # Error 76 |- self.assertNotRegex("abc", r"abc") # Error
76 |+ assert not re.search("abc", "abc") # Error 76 |+ assert not re.search(r"abc", "abc") # Error
77 77 | 77 77 |
78 78 | def test_assert_regexp_matches(self): 78 78 | def test_assert_regexp_matches(self):
79 79 | self.assertRegexpMatches("abc", r"def") # Error 79 79 | self.assertRegexpMatches("abc", r"def") # Error
@ -538,7 +538,7 @@ PT009.py:79:9: PT009 [*] Use a regular `assert` instead of unittest-style `asser
77 77 | 77 77 |
78 78 | def test_assert_regexp_matches(self): 78 78 | def test_assert_regexp_matches(self):
79 |- self.assertRegexpMatches("abc", r"def") # Error 79 |- self.assertRegexpMatches("abc", r"def") # Error
79 |+ assert re.search("def", "abc") # Error 79 |+ assert re.search(r"def", "abc") # Error
80 80 | 80 80 |
81 81 | def test_assert_not_regexp_matches(self): 81 81 | def test_assert_not_regexp_matches(self):
82 82 | self.assertNotRegex("abc", r"abc") # Error 82 82 | self.assertNotRegex("abc", r"abc") # Error
@ -558,7 +558,7 @@ PT009.py:82:9: PT009 [*] Use a regular `assert` instead of unittest-style `asser
80 80 | 80 80 |
81 81 | def test_assert_not_regexp_matches(self): 81 81 | def test_assert_not_regexp_matches(self):
82 |- self.assertNotRegex("abc", r"abc") # Error 82 |- self.assertNotRegex("abc", r"abc") # Error
82 |+ assert not re.search("abc", "abc") # Error 82 |+ assert not re.search(r"abc", "abc") # Error
83 83 | 83 83 |
84 84 | def test_fail_if(self): 84 84 | def test_fail_if(self):
85 85 | self.failIf("abc") # Error 85 85 | self.failIf("abc") # Error
@ -658,6 +658,7 @@ PT009.py:98:2: PT009 [*] Use a regular `assert` instead of unittest-style `asser
98 |-(self.assertTrue( 98 |-(self.assertTrue(
99 |- "piAx_piAy_beta[r][x][y] = {17}".format( 99 |- "piAx_piAy_beta[r][x][y] = {17}".format(
100 |- self.model.piAx_piAy_beta[r][x][y]))) 100 |- self.model.piAx_piAy_beta[r][x][y])))
98 |+assert "piAx_piAy_beta[r][x][y] = {17}".format(self.model.piAx_piAy_beta[r][x][y]) 98 |+assert "piAx_piAy_beta[r][x][y] = {17}".format(
99 |+ self.model.piAx_piAy_beta[r][x][y])

View File

@ -534,7 +534,7 @@ SIM222.py:85:6: SIM222 [*] Use `True` instead of `... or True`
87 87 | a or (1,) or True or (2,) # SIM222 87 87 | a or (1,) or True or (2,) # SIM222
88 88 | 88 88 |
SIM222.py:87:6: SIM222 [*] Use `(1,)` instead of `(1,) or ...` SIM222.py:87:6: SIM222 [*] Use `((1,))` instead of `((1,)) or ...`
| |
85 | a or tuple(()) or True # SIM222 85 | a or tuple(()) or True # SIM222
86 | 86 |
@ -543,14 +543,14 @@ SIM222.py:87:6: SIM222 [*] Use `(1,)` instead of `(1,) or ...`
88 | 88 |
89 | a or tuple((1,)) or True or tuple((2,)) # SIM222 89 | a or tuple((1,)) or True or tuple((2,)) # SIM222
| |
= help: Replace with `(1,)` = help: Replace with `((1,))`
Unsafe fix Unsafe fix
84 84 | 84 84 |
85 85 | a or tuple(()) or True # SIM222 85 85 | a or tuple(()) or True # SIM222
86 86 | 86 86 |
87 |-a or (1,) or True or (2,) # SIM222 87 |-a or (1,) or True or (2,) # SIM222
87 |+a or (1,) # SIM222 87 |+a or ((1,)) # SIM222
88 88 | 88 88 |
89 89 | a or tuple((1,)) or True or tuple((2,)) # SIM222 89 89 | a or tuple((1,)) or True or tuple((2,)) # SIM222
90 90 | 90 90 |
@ -1006,39 +1006,39 @@ SIM222.py:153:11: SIM222 [*] Use `[1]` instead of `[1] or ...`
155 155 | 155 155 |
156 156 | # Regression test for: https://github.com/astral-sh/ruff/issues/7099 156 156 | # Regression test for: https://github.com/astral-sh/ruff/issues/7099
SIM222.py:157:30: SIM222 [*] Use `(int, int, int)` instead of `(int, int, int) or ...` SIM222.py:157:30: SIM222 [*] Use `((int, int, int))` instead of `((int, int, int)) or ...`
| |
156 | # Regression test for: https://github.com/astral-sh/ruff/issues/7099 156 | # Regression test for: https://github.com/astral-sh/ruff/issues/7099
157 | def secondToTime(s0: int) -> (int, int, int) or str: 157 | def secondToTime(s0: int) -> (int, int, int) or str:
| ^^^^^^^^^^^^^^^^^^^^^^ SIM222 | ^^^^^^^^^^^^^^^^^^^^^^ SIM222
158 | m, s = divmod(s0, 60) 158 | m, s = divmod(s0, 60)
| |
= help: Replace with `(int, int, int)` = help: Replace with `((int, int, int))`
Unsafe fix Unsafe fix
154 154 | pass 154 154 | pass
155 155 | 155 155 |
156 156 | # Regression test for: https://github.com/astral-sh/ruff/issues/7099 156 156 | # Regression test for: https://github.com/astral-sh/ruff/issues/7099
157 |-def secondToTime(s0: int) -> (int, int, int) or str: 157 |-def secondToTime(s0: int) -> (int, int, int) or str:
157 |+def secondToTime(s0: int) -> (int, int, int): 157 |+def secondToTime(s0: int) -> ((int, int, int)):
158 158 | m, s = divmod(s0, 60) 158 158 | m, s = divmod(s0, 60)
159 159 | 159 159 |
160 160 | 160 160 |
SIM222.py:161:31: SIM222 [*] Use `(int, int, int)` instead of `(int, int, int) or ...` SIM222.py:161:31: SIM222 [*] Use `((int, int, int))` instead of `((int, int, int)) or ...`
| |
161 | def secondToTime(s0: int) -> ((int, int, int) or str): 161 | def secondToTime(s0: int) -> ((int, int, int) or str):
| ^^^^^^^^^^^^^^^^^^^^^^ SIM222 | ^^^^^^^^^^^^^^^^^^^^^^ SIM222
162 | m, s = divmod(s0, 60) 162 | m, s = divmod(s0, 60)
| |
= help: Replace with `(int, int, int)` = help: Replace with `((int, int, int))`
Unsafe fix Unsafe fix
158 158 | m, s = divmod(s0, 60) 158 158 | m, s = divmod(s0, 60)
159 159 | 159 159 |
160 160 | 160 160 |
161 |-def secondToTime(s0: int) -> ((int, int, int) or str): 161 |-def secondToTime(s0: int) -> ((int, int, int) or str):
161 |+def secondToTime(s0: int) -> ((int, int, int)): 161 |+def secondToTime(s0: int) -> (((int, int, int))):
162 162 | m, s = divmod(s0, 60) 162 162 | m, s = divmod(s0, 60)
163 163 | 163 163 |
164 164 | 164 164 |

View File

@ -241,7 +241,7 @@ redefined_loop_name.py:178:5: PLW2901 `for` loop variable `a.i` overwritten by a
180 | a.i = 2 # error 180 | a.i = 2 # error
| |
redefined_loop_name.py:180:5: PLW2901 `for` loop variable `a.i` overwritten by assignment target redefined_loop_name.py:180:5: PLW2901 `for` loop variable `a. i` overwritten by assignment target
| |
178 | a. i = 2 # error 178 | a. i = 2 # error
179 | for a. i in []: 179 | for a. i in []:

View File

@ -10,7 +10,7 @@ magic_value_comparison.py:59:22: PLR2004 Magic value used in comparison, conside
60 | pass 60 | pass
| |
magic_value_comparison.py:65:21: PLR2004 Magic value used in comparison, consider replacing 3.141592653589793 with a constant variable magic_value_comparison.py:65:21: PLR2004 Magic value used in comparison, consider replacing 3.141592653589793238 with a constant variable
| |
63 | pi_estimation = 3.14 63 | pi_estimation = 3.14
64 | 64 |

View File

@ -17,6 +17,7 @@ ruff_python_ast = { path = "../ruff_python_ast" }
ruff_python_literal = { path = "../ruff_python_literal" } ruff_python_literal = { path = "../ruff_python_literal" }
ruff_python_parser = { path = "../ruff_python_parser" } ruff_python_parser = { path = "../ruff_python_parser" }
ruff_source_file = { path = "../ruff_source_file" } ruff_source_file = { path = "../ruff_source_file" }
ruff_text_size = { path = "../ruff_text_size" }
once_cell = { workspace = true } once_cell = { workspace = true }

View File

@ -10,7 +10,8 @@ use ruff_python_ast::{
}; };
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};
use ruff_source_file::LineEnding; use ruff_source_file::{LineEnding, Locator};
use ruff_text_size::{Ranged, TextRange};
use super::stylist::{Indentation, Quote, Stylist}; use super::stylist::{Indentation, Quote, Stylist};
@ -61,6 +62,11 @@ mod precedence {
pub(crate) const MAX: u8 = 63; pub(crate) const MAX: u8 = 63;
} }
pub struct Verbatim<'a> {
locator: &'a Locator<'a>,
nodes: Vec<TextRange>,
}
pub struct Generator<'a> { pub struct Generator<'a> {
/// The indentation style to use. /// The indentation style to use.
indent: &'a Indentation, indent: &'a Indentation,
@ -72,6 +78,7 @@ pub struct Generator<'a> {
indent_depth: usize, indent_depth: usize,
num_newlines: usize, num_newlines: usize,
initial: bool, initial: bool,
locator: Option<&'a Locator<'a>>,
} }
impl<'a> From<&'a Stylist<'a>> for Generator<'a> { impl<'a> From<&'a Stylist<'a>> for Generator<'a> {
@ -84,6 +91,7 @@ impl<'a> From<&'a Stylist<'a>> for Generator<'a> {
indent_depth: 0, indent_depth: 0,
num_newlines: 0, num_newlines: 0,
initial: true, initial: true,
locator: None,
} }
} }
} }
@ -100,6 +108,15 @@ impl<'a> Generator<'a> {
indent_depth: 0, indent_depth: 0,
num_newlines: 0, num_newlines: 0,
initial: true, initial: true,
locator: None,
}
}
#[must_use]
pub fn with_locator(self, locator: &'a Locator<'a>) -> Self {
Self {
locator: Some(locator),
..self
} }
} }
@ -796,6 +813,14 @@ impl<'a> Generator<'a> {
ret ret
}}; }};
} }
if let Some(locator) = &self.locator {
if !ast.range().is_empty() {
self.p(locator.slice(ast));
return;
}
}
match ast { match ast {
Expr::BoolOp(ast::ExprBoolOp { Expr::BoolOp(ast::ExprBoolOp {
op, op,

2
foo.py Normal file
View File

@ -0,0 +1,2 @@
if x.startswith(("a", "b")) or re.match(r"a\.b", x):
pass