mirror of https://github.com/astral-sh/ruff
[`pyupgrade`] Fix parsing named Unicode escape sequences (`UP032`) (#21901)
## Summary Fixes https://github.com/astral-sh/ruff/issues/19771 Fixes incorrect parsing of Unicode named escape sequences like `Hey \N{snowman}` in `FormatString`, which were being incorrectly split into separate literal and field parts instead of being treated as a single literal unit. ## Problem The `FormatString` parser incorrectly handles Unicode named escape sequences: - **Current**: `Hey \N{snowman}` is parsed into 2 parts `Literal("Hey \N")` & `Field("snowman")` - **Expected**: `Hey \N{snowman}` should be parsed into 1 part `Literal("Hey \N{snowman}")` This affects f-string conversion rules when fixing `UP032` that rely on proper format string parsing. ## Solution I modified `parse_literal` to detect and handle Unicode named escape sequences before parsing single characters: - Introduced a flag to track when a backslash is "available" to escape something. - When the flag is `true`, and the text starts with `N{`, try to parse the complete Unicode escape sequence as one unit, and set the flag to `false` after parsing successfully. - Set the flag to `false` when the backslash is already consumed. ## Manual Verification `"\N{angle}AOB = {angle}°".format(angle=180)` **Result** ```bash def foo(): - "\N{angle}AOB = {angle}°".format(angle=180) + f"\N{angle}AOB = {180}°" Would fix 1 error. ``` `"\N{snowman} {snowman}".format(snowman=1)` **Result** ```bash def foo(): - "\N{snowman} {snowman}".format(snowman=1) + f"\N{snowman} {1}" Would fix 1 error. ``` `"\\N{snowman} {snowman}".format(snowman=1)` **Result** ```bash def foo(): - "\\N{snowman} {snowman}".format(snowman=1) + f"\\N{1} {1}" Would fix 1 error. ``` ## Test Plan - Added test cases (happy case, invalid case, edge case) for `FormatString` when parsing Unicode escape sequence. - Updated snapshots.
This commit is contained in:
parent
ad3de4e488
commit
b0bc990cbf
|
|
@ -132,7 +132,6 @@ async def c():
|
||||||
# Non-errors
|
# Non-errors
|
||||||
###
|
###
|
||||||
|
|
||||||
# False-negative: RustPython doesn't parse the `\N{snowman}`.
|
|
||||||
"\N{snowman} {}".format(a)
|
"\N{snowman} {}".format(a)
|
||||||
|
|
||||||
"{".format(a)
|
"{".format(a)
|
||||||
|
|
@ -276,3 +275,6 @@ if __name__ == "__main__":
|
||||||
number = 0
|
number = 0
|
||||||
string = "{}".format(number := number + 1)
|
string = "{}".format(number := number + 1)
|
||||||
print(string)
|
print(string)
|
||||||
|
|
||||||
|
# Unicode escape
|
||||||
|
"\N{angle}AOB = {angle}°".format(angle=180)
|
||||||
|
|
|
||||||
|
|
@ -902,56 +902,76 @@ help: Convert to f-string
|
||||||
132 | # Non-errors
|
132 | # Non-errors
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:160:1
|
--> UP032_0.py:135:1
|
||||||
|
|
|
|
||||||
158 | r'"\N{snowman} {}".format(a)'
|
133 | ###
|
||||||
159 |
|
134 |
|
||||||
160 | / "123456789 {}".format(
|
135 | "\N{snowman} {}".format(a)
|
||||||
161 | | 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
162 | | )
|
136 |
|
||||||
| |_^
|
137 | "{".format(a)
|
||||||
163 |
|
|
||||||
164 | """
|
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
157 |
|
132 | # Non-errors
|
||||||
158 | r'"\N{snowman} {}".format(a)'
|
133 | ###
|
||||||
159 |
|
134 |
|
||||||
|
- "\N{snowman} {}".format(a)
|
||||||
|
135 + f"\N{snowman} {a}"
|
||||||
|
136 |
|
||||||
|
137 | "{".format(a)
|
||||||
|
138 |
|
||||||
|
|
||||||
|
UP032 [*] Use f-string instead of `format` call
|
||||||
|
--> UP032_0.py:159:1
|
||||||
|
|
|
||||||
|
157 | r'"\N{snowman} {}".format(a)'
|
||||||
|
158 |
|
||||||
|
159 | / "123456789 {}".format(
|
||||||
|
160 | | 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||||
|
161 | | )
|
||||||
|
| |_^
|
||||||
|
162 |
|
||||||
|
163 | """
|
||||||
|
|
|
||||||
|
help: Convert to f-string
|
||||||
|
156 |
|
||||||
|
157 | r'"\N{snowman} {}".format(a)'
|
||||||
|
158 |
|
||||||
- "123456789 {}".format(
|
- "123456789 {}".format(
|
||||||
- 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
- 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||||
- )
|
- )
|
||||||
160 + f"123456789 {11111111111111111111111111111111111111111111111111111111111111111111111111}"
|
159 + f"123456789 {11111111111111111111111111111111111111111111111111111111111111111111111111}"
|
||||||
161 |
|
160 |
|
||||||
162 | """
|
161 | """
|
||||||
163 | {}
|
162 | {}
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:164:1
|
--> UP032_0.py:163:1
|
||||||
|
|
|
|
||||||
162 | )
|
161 | )
|
||||||
163 |
|
162 |
|
||||||
164 | / """
|
163 | / """
|
||||||
|
164 | | {}
|
||||||
165 | | {}
|
165 | | {}
|
||||||
166 | | {}
|
166 | | {}
|
||||||
167 | | {}
|
167 | | """.format(
|
||||||
168 | | """.format(
|
168 | | 1,
|
||||||
169 | | 1,
|
169 | | 2,
|
||||||
170 | | 2,
|
170 | | 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||||
171 | | 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
171 | | )
|
||||||
172 | | )
|
|
||||||
| |_^
|
| |_^
|
||||||
173 |
|
172 |
|
||||||
174 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
173 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
161 | 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
160 | 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||||
162 | )
|
161 | )
|
||||||
163 |
|
162 |
|
||||||
164 + f"""
|
163 + f"""
|
||||||
165 + {1}
|
164 + {1}
|
||||||
166 + {2}
|
165 + {2}
|
||||||
167 + {111111111111111111111111111111111111111111111111111111111111111111111111111111111111111}
|
166 + {111111111111111111111111111111111111111111111111111111111111111111111111111111111111111}
|
||||||
168 | """
|
167 | """
|
||||||
- {}
|
- {}
|
||||||
- {}
|
- {}
|
||||||
- {}
|
- {}
|
||||||
|
|
@ -960,392 +980,408 @@ help: Convert to f-string
|
||||||
- 2,
|
- 2,
|
||||||
- 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
- 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||||
- )
|
- )
|
||||||
169 |
|
168 |
|
||||||
170 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
169 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
||||||
171 | """.format(
|
170 | """.format(
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:174:84
|
--> UP032_0.py:173:84
|
||||||
|
|
|
|
||||||
172 | )
|
171 | )
|
||||||
173 |
|
172 |
|
||||||
174 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
173 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
||||||
| ____________________________________________________________________________________^
|
| ____________________________________________________________________________________^
|
||||||
175 | | """.format(
|
174 | | """.format(
|
||||||
176 | | 111111
|
175 | | 111111
|
||||||
177 | | )
|
176 | | )
|
||||||
| |_^
|
| |_^
|
||||||
178 |
|
177 |
|
||||||
179 | "{}".format(
|
178 | "{}".format(
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
171 | 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
170 | 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||||
172 | )
|
171 | )
|
||||||
173 |
|
172 |
|
||||||
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
||||||
- """.format(
|
- """.format(
|
||||||
- 111111
|
- 111111
|
||||||
- )
|
- )
|
||||||
174 + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = f"""{111111}
|
173 + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = f"""{111111}
|
||||||
175 + """
|
174 + """
|
||||||
176 |
|
175 |
|
||||||
177 | "{}".format(
|
176 | "{}".format(
|
||||||
178 | [
|
177 | [
|
||||||
|
|
||||||
UP032 Use f-string instead of `format` call
|
UP032 Use f-string instead of `format` call
|
||||||
--> UP032_0.py:202:1
|
--> UP032_0.py:201:1
|
||||||
|
|
|
|
||||||
200 | "{}".format(**c)
|
199 | "{}".format(**c)
|
||||||
201 |
|
200 |
|
||||||
202 | / "{}".format(
|
201 | / "{}".format(
|
||||||
203 | | 1 # comment
|
202 | | 1 # comment
|
||||||
204 | | )
|
203 | | )
|
||||||
| |_^
|
| |_^
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:209:1
|
--> UP032_0.py:208:1
|
||||||
|
|
|
|
||||||
207 | # The fixed string will exceed the line length, but it's still smaller than the
|
206 | # The fixed string will exceed the line length, but it's still smaller than the
|
||||||
208 | # existing line length, so it's fine.
|
207 | # existing line length, so it's fine.
|
||||||
209 | "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
208 | "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
210 |
|
209 |
|
||||||
211 | # When fixing, trim the trailing empty string.
|
210 | # When fixing, trim the trailing empty string.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
206 |
|
205 |
|
||||||
207 | # The fixed string will exceed the line length, but it's still smaller than the
|
206 | # The fixed string will exceed the line length, but it's still smaller than the
|
||||||
208 | # existing line length, so it's fine.
|
207 | # existing line length, so it's fine.
|
||||||
- "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
- "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
||||||
209 + f"<Customer: {self.internal_ids}, {self.external_ids}, {self.properties}, {self.tags}, {self.others}>"
|
208 + f"<Customer: {self.internal_ids}, {self.external_ids}, {self.properties}, {self.tags}, {self.others}>"
|
||||||
210 |
|
209 |
|
||||||
211 | # When fixing, trim the trailing empty string.
|
210 | # When fixing, trim the trailing empty string.
|
||||||
212 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
211 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:212:18
|
--> UP032_0.py:211:18
|
||||||
|
|
|
|
||||||
211 | # When fixing, trim the trailing empty string.
|
210 | # When fixing, trim the trailing empty string.
|
||||||
212 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
211 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||||
| __________________^
|
| __________________^
|
||||||
213 | | "".format(new_dict, d))
|
212 | | "".format(new_dict, d))
|
||||||
| |_______________________________________^
|
| |_______________________________________^
|
||||||
214 |
|
|
||||||
215 | # When fixing, trim the trailing empty string.
|
|
||||||
|
|
|
||||||
help: Convert to f-string
|
|
||||||
209 | "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
|
||||||
210 |
|
|
||||||
211 | # When fixing, trim the trailing empty string.
|
|
||||||
- raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
|
||||||
- "".format(new_dict, d))
|
|
||||||
212 + raise ValueError(f"Conflicting configuration dicts: {new_dict!r} {d!r}")
|
|
||||||
213 |
|
213 |
|
||||||
214 | # When fixing, trim the trailing empty string.
|
214 | # When fixing, trim the trailing empty string.
|
||||||
215 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
|
||||||
--> UP032_0.py:216:18
|
|
||||||
|
|
|
||||||
215 | # When fixing, trim the trailing empty string.
|
|
||||||
216 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
|
||||||
| __________________^
|
|
||||||
217 | | .format(new_dict, d))
|
|
||||||
| |_____________________________________^
|
|
||||||
218 |
|
|
||||||
219 | raise ValueError(
|
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
213 | "".format(new_dict, d))
|
208 | "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
||||||
214 |
|
209 |
|
||||||
215 | # When fixing, trim the trailing empty string.
|
210 | # When fixing, trim the trailing empty string.
|
||||||
|
- raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||||
|
- "".format(new_dict, d))
|
||||||
|
211 + raise ValueError(f"Conflicting configuration dicts: {new_dict!r} {d!r}")
|
||||||
|
212 |
|
||||||
|
213 | # When fixing, trim the trailing empty string.
|
||||||
|
214 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||||
|
|
||||||
|
UP032 [*] Use f-string instead of `format` call
|
||||||
|
--> UP032_0.py:215:18
|
||||||
|
|
|
||||||
|
214 | # When fixing, trim the trailing empty string.
|
||||||
|
215 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||||
|
| __________________^
|
||||||
|
216 | | .format(new_dict, d))
|
||||||
|
| |_____________________________________^
|
||||||
|
217 |
|
||||||
|
218 | raise ValueError(
|
||||||
|
|
|
||||||
|
help: Convert to f-string
|
||||||
|
212 | "".format(new_dict, d))
|
||||||
|
213 |
|
||||||
|
214 | # When fixing, trim the trailing empty string.
|
||||||
- raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
- raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||||
- .format(new_dict, d))
|
- .format(new_dict, d))
|
||||||
216 + raise ValueError(f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
215 + raise ValueError(f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
||||||
217 + )
|
216 + )
|
||||||
218 |
|
217 |
|
||||||
219 | raise ValueError(
|
218 | raise ValueError(
|
||||||
220 | "Conflicting configuration dicts: {!r} {!r}"
|
219 | "Conflicting configuration dicts: {!r} {!r}"
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:220:5
|
--> UP032_0.py:219:5
|
||||||
|
|
|
|
||||||
219 | raise ValueError(
|
218 | raise ValueError(
|
||||||
220 | / "Conflicting configuration dicts: {!r} {!r}"
|
219 | / "Conflicting configuration dicts: {!r} {!r}"
|
||||||
221 | | "".format(new_dict, d)
|
220 | | "".format(new_dict, d)
|
||||||
| |__________________________^
|
| |__________________________^
|
||||||
222 | )
|
221 | )
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
217 | .format(new_dict, d))
|
216 | .format(new_dict, d))
|
||||||
218 |
|
217 |
|
||||||
219 | raise ValueError(
|
218 | raise ValueError(
|
||||||
- "Conflicting configuration dicts: {!r} {!r}"
|
- "Conflicting configuration dicts: {!r} {!r}"
|
||||||
- "".format(new_dict, d)
|
- "".format(new_dict, d)
|
||||||
220 + f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
219 + f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
||||||
|
220 | )
|
||||||
|
221 |
|
||||||
|
222 | raise ValueError(
|
||||||
|
|
||||||
|
UP032 [*] Use f-string instead of `format` call
|
||||||
|
--> UP032_0.py:224:5
|
||||||
|
|
|
||||||
|
223 | raise ValueError(
|
||||||
|
224 | / "Conflicting configuration dicts: {!r} {!r}"
|
||||||
|
225 | | "".format(new_dict, d)
|
||||||
|
| |__________________________^
|
||||||
|
226 |
|
||||||
|
227 | )
|
||||||
|
|
|
||||||
|
help: Convert to f-string
|
||||||
221 | )
|
221 | )
|
||||||
222 |
|
222 |
|
||||||
223 | raise ValueError(
|
223 | raise ValueError(
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
|
||||||
--> UP032_0.py:225:5
|
|
||||||
|
|
|
||||||
224 | raise ValueError(
|
|
||||||
225 | / "Conflicting configuration dicts: {!r} {!r}"
|
|
||||||
226 | | "".format(new_dict, d)
|
|
||||||
| |__________________________^
|
|
||||||
227 |
|
|
||||||
228 | )
|
|
||||||
|
|
|
||||||
help: Convert to f-string
|
|
||||||
222 | )
|
|
||||||
223 |
|
|
||||||
224 | raise ValueError(
|
|
||||||
- "Conflicting configuration dicts: {!r} {!r}"
|
- "Conflicting configuration dicts: {!r} {!r}"
|
||||||
- "".format(new_dict, d)
|
- "".format(new_dict, d)
|
||||||
225 + f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
224 + f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
||||||
226 |
|
225 |
|
||||||
227 | )
|
226 | )
|
||||||
228 |
|
227 |
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:231:1
|
--> UP032_0.py:230:1
|
||||||
|
|
|
|
||||||
230 | # The first string will be converted to an f-string and the curly braces in the second should be converted to be unescaped
|
229 | # The first string will be converted to an f-string and the curly braces in the second should be converted to be unescaped
|
||||||
231 | / (
|
230 | / (
|
||||||
232 | | "{}"
|
231 | | "{}"
|
||||||
233 | | "{{}}"
|
232 | | "{{}}"
|
||||||
234 | | ).format(a)
|
233 | | ).format(a)
|
||||||
| |___________^
|
| |___________^
|
||||||
235 |
|
234 |
|
||||||
236 | ("{}" "{{}}").format(a)
|
235 | ("{}" "{{}}").format(a)
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
229 |
|
228 |
|
||||||
230 | # The first string will be converted to an f-string and the curly braces in the second should be converted to be unescaped
|
229 | # The first string will be converted to an f-string and the curly braces in the second should be converted to be unescaped
|
||||||
231 | (
|
230 | (
|
||||||
232 + f"{a}"
|
231 + f"{a}"
|
||||||
233 | "{}"
|
232 | "{}"
|
||||||
- "{{}}"
|
- "{{}}"
|
||||||
- ).format(a)
|
- ).format(a)
|
||||||
234 + )
|
233 + )
|
||||||
235 |
|
234 |
|
||||||
236 | ("{}" "{{}}").format(a)
|
235 | ("{}" "{{}}").format(a)
|
||||||
237 |
|
236 |
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:236:1
|
--> UP032_0.py:235:1
|
||||||
|
|
|
|
||||||
234 | ).format(a)
|
233 | ).format(a)
|
||||||
235 |
|
234 |
|
||||||
236 | ("{}" "{{}}").format(a)
|
235 | ("{}" "{{}}").format(a)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
233 | "{{}}"
|
232 | "{{}}"
|
||||||
234 | ).format(a)
|
233 | ).format(a)
|
||||||
235 |
|
234 |
|
||||||
- ("{}" "{{}}").format(a)
|
- ("{}" "{{}}").format(a)
|
||||||
236 + (f"{a}" "{}")
|
235 + (f"{a}" "{}")
|
||||||
|
236 |
|
||||||
237 |
|
237 |
|
||||||
238 |
|
238 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
||||||
239 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:240:1
|
--> UP032_0.py:239:1
|
||||||
|
|
|
|
||||||
239 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
238 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
||||||
240 | / (
|
239 | / (
|
||||||
241 | | "{}"
|
240 | | "{}"
|
||||||
242 | | "{{{}}}"
|
241 | | "{{{}}}"
|
||||||
243 | | ).format(a, b)
|
242 | | ).format(a, b)
|
||||||
| |______________^
|
| |______________^
|
||||||
244 |
|
243 |
|
||||||
245 | ("{}" "{{{}}}").format(a, b)
|
244 | ("{}" "{{{}}}").format(a, b)
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
238 |
|
237 |
|
||||||
239 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
238 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
||||||
240 | (
|
239 | (
|
||||||
- "{}"
|
- "{}"
|
||||||
- "{{{}}}"
|
- "{{{}}}"
|
||||||
- ).format(a, b)
|
- ).format(a, b)
|
||||||
241 + f"{a}"
|
240 + f"{a}"
|
||||||
242 + f"{{{b}}}"
|
241 + f"{{{b}}}"
|
||||||
243 + )
|
242 + )
|
||||||
244 |
|
243 |
|
||||||
245 | ("{}" "{{{}}}").format(a, b)
|
244 | ("{}" "{{{}}}").format(a, b)
|
||||||
246 |
|
245 |
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:245:1
|
--> UP032_0.py:244:1
|
||||||
|
|
|
|
||||||
243 | ).format(a, b)
|
242 | ).format(a, b)
|
||||||
244 |
|
243 |
|
||||||
245 | ("{}" "{{{}}}").format(a, b)
|
244 | ("{}" "{{{}}}").format(a, b)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
246 |
|
245 |
|
||||||
247 | # The dictionary should be parenthesized.
|
246 | # The dictionary should be parenthesized.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
242 | "{{{}}}"
|
241 | "{{{}}}"
|
||||||
243 | ).format(a, b)
|
242 | ).format(a, b)
|
||||||
244 |
|
243 |
|
||||||
- ("{}" "{{{}}}").format(a, b)
|
- ("{}" "{{{}}}").format(a, b)
|
||||||
245 + (f"{a}" f"{{{b}}}")
|
244 + (f"{a}" f"{{{b}}}")
|
||||||
246 |
|
245 |
|
||||||
247 | # The dictionary should be parenthesized.
|
246 | # The dictionary should be parenthesized.
|
||||||
248 | "{}".format({0: 1}[0])
|
247 | "{}".format({0: 1}[0])
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:248:1
|
--> UP032_0.py:247:1
|
||||||
|
|
|
|
||||||
247 | # The dictionary should be parenthesized.
|
246 | # The dictionary should be parenthesized.
|
||||||
248 | "{}".format({0: 1}[0])
|
247 | "{}".format({0: 1}[0])
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
249 |
|
248 |
|
||||||
250 | # The dictionary should be parenthesized.
|
249 | # The dictionary should be parenthesized.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
245 | ("{}" "{{{}}}").format(a, b)
|
244 | ("{}" "{{{}}}").format(a, b)
|
||||||
246 |
|
245 |
|
||||||
247 | # The dictionary should be parenthesized.
|
246 | # The dictionary should be parenthesized.
|
||||||
- "{}".format({0: 1}[0])
|
- "{}".format({0: 1}[0])
|
||||||
248 + f"{({0: 1}[0])}"
|
247 + f"{({0: 1}[0])}"
|
||||||
249 |
|
248 |
|
||||||
250 | # The dictionary should be parenthesized.
|
249 | # The dictionary should be parenthesized.
|
||||||
251 | "{}".format({0: 1}.bar)
|
250 | "{}".format({0: 1}.bar)
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:251:1
|
--> UP032_0.py:250:1
|
||||||
|
|
|
|
||||||
250 | # The dictionary should be parenthesized.
|
249 | # The dictionary should be parenthesized.
|
||||||
251 | "{}".format({0: 1}.bar)
|
250 | "{}".format({0: 1}.bar)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
252 |
|
251 |
|
||||||
253 | # The dictionary should be parenthesized.
|
252 | # The dictionary should be parenthesized.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
248 | "{}".format({0: 1}[0])
|
247 | "{}".format({0: 1}[0])
|
||||||
249 |
|
248 |
|
||||||
250 | # The dictionary should be parenthesized.
|
249 | # The dictionary should be parenthesized.
|
||||||
- "{}".format({0: 1}.bar)
|
- "{}".format({0: 1}.bar)
|
||||||
251 + f"{({0: 1}.bar)}"
|
250 + f"{({0: 1}.bar)}"
|
||||||
252 |
|
251 |
|
||||||
253 | # The dictionary should be parenthesized.
|
252 | # The dictionary should be parenthesized.
|
||||||
254 | "{}".format({0: 1}())
|
253 | "{}".format({0: 1}())
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:254:1
|
--> UP032_0.py:253:1
|
||||||
|
|
|
|
||||||
253 | # The dictionary should be parenthesized.
|
252 | # The dictionary should be parenthesized.
|
||||||
254 | "{}".format({0: 1}())
|
253 | "{}".format({0: 1}())
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^
|
||||||
255 |
|
254 |
|
||||||
256 | # The string shouldn't be converted, since it would require repeating the function call.
|
255 | # The string shouldn't be converted, since it would require repeating the function call.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
251 | "{}".format({0: 1}.bar)
|
250 | "{}".format({0: 1}.bar)
|
||||||
252 |
|
251 |
|
||||||
253 | # The dictionary should be parenthesized.
|
252 | # The dictionary should be parenthesized.
|
||||||
- "{}".format({0: 1}())
|
- "{}".format({0: 1}())
|
||||||
254 + f"{({0: 1}())}"
|
253 + f"{({0: 1}())}"
|
||||||
255 |
|
254 |
|
||||||
256 | # The string shouldn't be converted, since it would require repeating the function call.
|
255 | # The string shouldn't be converted, since it would require repeating the function call.
|
||||||
257 | "{x} {x}".format(x=foo())
|
256 | "{x} {x}".format(x=foo())
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:261:1
|
--> UP032_0.py:260:1
|
||||||
|
|
|
|
||||||
260 | # The string _should_ be converted, since the function call is repeated in the arguments.
|
259 | # The string _should_ be converted, since the function call is repeated in the arguments.
|
||||||
261 | "{0} {1}".format(foo(), foo())
|
260 | "{0} {1}".format(foo(), foo())
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
262 |
|
261 |
|
||||||
263 | # The call should be removed, but the string itself should remain.
|
262 | # The call should be removed, but the string itself should remain.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
258 | "{0} {0}".format(foo())
|
257 | "{0} {0}".format(foo())
|
||||||
259 |
|
258 |
|
||||||
260 | # The string _should_ be converted, since the function call is repeated in the arguments.
|
259 | # The string _should_ be converted, since the function call is repeated in the arguments.
|
||||||
- "{0} {1}".format(foo(), foo())
|
- "{0} {1}".format(foo(), foo())
|
||||||
261 + f"{foo()} {foo()}"
|
260 + f"{foo()} {foo()}"
|
||||||
262 |
|
261 |
|
||||||
263 | # The call should be removed, but the string itself should remain.
|
262 | # The call should be removed, but the string itself should remain.
|
||||||
264 | ''.format(self.project)
|
263 | ''.format(self.project)
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:264:1
|
--> UP032_0.py:263:1
|
||||||
|
|
|
|
||||||
263 | # The call should be removed, but the string itself should remain.
|
262 | # The call should be removed, but the string itself should remain.
|
||||||
264 | ''.format(self.project)
|
263 | ''.format(self.project)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
265 |
|
264 |
|
||||||
266 | # The call should be removed, but the string itself should remain.
|
265 | # The call should be removed, but the string itself should remain.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
261 | "{0} {1}".format(foo(), foo())
|
260 | "{0} {1}".format(foo(), foo())
|
||||||
262 |
|
261 |
|
||||||
263 | # The call should be removed, but the string itself should remain.
|
262 | # The call should be removed, but the string itself should remain.
|
||||||
- ''.format(self.project)
|
- ''.format(self.project)
|
||||||
264 + ''
|
263 + ''
|
||||||
265 |
|
264 |
|
||||||
266 | # The call should be removed, but the string itself should remain.
|
265 | # The call should be removed, but the string itself should remain.
|
||||||
267 | "".format(self.project)
|
266 | "".format(self.project)
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:267:1
|
--> UP032_0.py:266:1
|
||||||
|
|
|
|
||||||
266 | # The call should be removed, but the string itself should remain.
|
265 | # The call should be removed, but the string itself should remain.
|
||||||
267 | "".format(self.project)
|
266 | "".format(self.project)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
268 |
|
267 |
|
||||||
269 | # Not a valid type annotation but this test shouldn't result in a panic.
|
268 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
264 | ''.format(self.project)
|
263 | ''.format(self.project)
|
||||||
265 |
|
264 |
|
||||||
266 | # The call should be removed, but the string itself should remain.
|
265 | # The call should be removed, but the string itself should remain.
|
||||||
- "".format(self.project)
|
- "".format(self.project)
|
||||||
267 + ""
|
266 + ""
|
||||||
268 |
|
267 |
|
||||||
269 | # Not a valid type annotation but this test shouldn't result in a panic.
|
268 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||||
270 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
269 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:271:5
|
--> UP032_0.py:270:5
|
||||||
|
|
|
|
||||||
269 | # Not a valid type annotation but this test shouldn't result in a panic.
|
268 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||||
270 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
269 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||||
271 | x: "'{} + {}'.format(x, y)"
|
270 | x: "'{} + {}'.format(x, y)"
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
272 |
|
271 |
|
||||||
273 | # Regression https://github.com/astral-sh/ruff/issues/21000
|
272 | # Regression https://github.com/astral-sh/ruff/issues/21000
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
268 |
|
267 |
|
||||||
269 | # Not a valid type annotation but this test shouldn't result in a panic.
|
268 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||||
270 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
269 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||||
- x: "'{} + {}'.format(x, y)"
|
- x: "'{} + {}'.format(x, y)"
|
||||||
271 + x: "f'{x} + {y}'"
|
270 + x: "f'{x} + {y}'"
|
||||||
272 |
|
271 |
|
||||||
273 | # Regression https://github.com/astral-sh/ruff/issues/21000
|
272 | # Regression https://github.com/astral-sh/ruff/issues/21000
|
||||||
274 | # Fix should parenthesize walrus
|
273 | # Fix should parenthesize walrus
|
||||||
|
|
||||||
UP032 [*] Use f-string instead of `format` call
|
UP032 [*] Use f-string instead of `format` call
|
||||||
--> UP032_0.py:277:14
|
--> UP032_0.py:276:14
|
||||||
|
|
|
|
||||||
275 | if __name__ == "__main__":
|
274 | if __name__ == "__main__":
|
||||||
276 | number = 0
|
275 | number = 0
|
||||||
277 | string = "{}".format(number := number + 1)
|
276 | string = "{}".format(number := number + 1)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
278 | print(string)
|
277 | print(string)
|
||||||
|
|
|
|
||||||
help: Convert to f-string
|
help: Convert to f-string
|
||||||
274 | # Fix should parenthesize walrus
|
273 | # Fix should parenthesize walrus
|
||||||
275 | if __name__ == "__main__":
|
274 | if __name__ == "__main__":
|
||||||
276 | number = 0
|
275 | number = 0
|
||||||
- string = "{}".format(number := number + 1)
|
- string = "{}".format(number := number + 1)
|
||||||
277 + string = f"{(number := number + 1)}"
|
276 + string = f"{(number := number + 1)}"
|
||||||
278 | print(string)
|
277 | print(string)
|
||||||
|
278 |
|
||||||
|
279 | # Unicode escape
|
||||||
|
|
||||||
|
UP032 [*] Use f-string instead of `format` call
|
||||||
|
--> UP032_0.py:280:1
|
||||||
|
|
|
||||||
|
279 | # Unicode escape
|
||||||
|
280 | "\N{angle}AOB = {angle}°".format(angle=180)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
help: Convert to f-string
|
||||||
|
277 | print(string)
|
||||||
|
278 |
|
||||||
|
279 | # Unicode escape
|
||||||
|
- "\N{angle}AOB = {angle}°".format(angle=180)
|
||||||
|
280 + f"\N{angle}AOB = {180}°"
|
||||||
|
|
|
||||||
|
|
@ -592,11 +592,23 @@ impl FormatString {
|
||||||
fn parse_literal(text: &str) -> Result<(FormatPart, &str), FormatParseError> {
|
fn parse_literal(text: &str) -> Result<(FormatPart, &str), FormatParseError> {
|
||||||
let mut cur_text = text;
|
let mut cur_text = text;
|
||||||
let mut result_string = String::new();
|
let mut result_string = String::new();
|
||||||
|
let mut pending_escape = false;
|
||||||
while !cur_text.is_empty() {
|
while !cur_text.is_empty() {
|
||||||
|
if pending_escape
|
||||||
|
&& let Some((unicode_string, remaining)) =
|
||||||
|
FormatString::parse_escaped_unicode_string(cur_text)
|
||||||
|
{
|
||||||
|
result_string.push_str(unicode_string);
|
||||||
|
cur_text = remaining;
|
||||||
|
pending_escape = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
match FormatString::parse_literal_single(cur_text) {
|
match FormatString::parse_literal_single(cur_text) {
|
||||||
Ok((next_char, remaining)) => {
|
Ok((next_char, remaining)) => {
|
||||||
result_string.push(next_char);
|
result_string.push(next_char);
|
||||||
cur_text = remaining;
|
cur_text = remaining;
|
||||||
|
pending_escape = next_char == '\\' && !pending_escape;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return if result_string.is_empty() {
|
return if result_string.is_empty() {
|
||||||
|
|
@ -678,6 +690,13 @@ impl FormatString {
|
||||||
}
|
}
|
||||||
Err(FormatParseError::UnmatchedBracket)
|
Err(FormatParseError::UnmatchedBracket)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_escaped_unicode_string(text: &str) -> Option<(&str, &str)> {
|
||||||
|
text.strip_prefix("N{")?.find('}').map(|idx| {
|
||||||
|
let end_idx = idx + 3; // 3 for "N{"
|
||||||
|
(&text[..end_idx], &text[end_idx..])
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FromTemplate<'a>: Sized {
|
pub trait FromTemplate<'a>: Sized {
|
||||||
|
|
@ -1020,4 +1039,48 @@ mod tests {
|
||||||
Err(FormatParseError::InvalidCharacterAfterRightBracket)
|
Err(FormatParseError::InvalidCharacterAfterRightBracket)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_format_unicode_escape() {
|
||||||
|
let expected = Ok(FormatString {
|
||||||
|
format_parts: vec![FormatPart::Literal("I am a \\N{snowman}".to_owned())],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(FormatString::from_str("I am a \\N{snowman}"), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_format_unicode_escape_with_field() {
|
||||||
|
let expected = Ok(FormatString {
|
||||||
|
format_parts: vec![
|
||||||
|
FormatPart::Literal("I am a \\N{snowman}".to_owned()),
|
||||||
|
FormatPart::Field {
|
||||||
|
field_name: "snowman".to_owned(),
|
||||||
|
conversion_spec: None,
|
||||||
|
format_spec: String::new(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
FormatString::from_str("I am a \\N{snowman}{snowman}"),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_format_multiple_escape_with_field() {
|
||||||
|
let expected = Ok(FormatString {
|
||||||
|
format_parts: vec![
|
||||||
|
FormatPart::Literal("I am a \\\\N".to_owned()),
|
||||||
|
FormatPart::Field {
|
||||||
|
field_name: "snowman".to_owned(),
|
||||||
|
conversion_spec: None,
|
||||||
|
format_spec: String::new(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(FormatString::from_str("I am a \\\\N{snowman}"), expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue