Skip to content

Commit 4fc7dd3

Browse files
authored
Improved error recovery for unclosed strings (including f- and t-strings) (#20848)
1 parent a93618e commit 4fc7dd3

151 files changed

Lines changed: 1369 additions & 565 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC_syntax_error.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# The lexer doesn't emit a string token if it's unterminated
1+
# The lexer emits a string token if it's unterminated
22
"a" "b
33
"a" "b" "c
44
"a" """b
55
c""" "d
66

7-
# For f-strings, the `FStringRanges` won't contain the range for
7+
# This is also true for
88
# unterminated f-strings.
99
f"a" f"b
1010
f"a" f"b" f"c

crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC_syntax_error.py.snap

Lines changed: 36 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
11
---
22
source: crates/ruff_linter/src/rules/flake8_implicit_str_concat/mod.rs
33
---
4-
invalid-syntax: missing closing quote in string literal
5-
--> ISC_syntax_error.py:2:5
4+
ISC001 Implicitly concatenated string literals on one line
5+
--> ISC_syntax_error.py:2:1
66
|
7-
1 | # The lexer doesn't emit a string token if it's unterminated
7+
1 | # The lexer emits a string token if it's unterminated
88
2 | "a" "b
9-
| ^^
9+
| ^^^^^^
1010
3 | "a" "b" "c
1111
4 | "a" """b
1212
|
13+
help: Combine string literals
1314

14-
invalid-syntax: Expected a statement
15-
--> ISC_syntax_error.py:2:7
15+
invalid-syntax: missing closing quote in string literal
16+
--> ISC_syntax_error.py:2:5
1617
|
17-
1 | # The lexer doesn't emit a string token if it's unterminated
18+
1 | # The lexer emits a string token if it's unterminated
1819
2 | "a" "b
19-
| ^
20+
| ^^
2021
3 | "a" "b" "c
2122
4 | "a" """b
2223
|
2324

2425
ISC001 Implicitly concatenated string literals on one line
2526
--> ISC_syntax_error.py:3:1
2627
|
27-
1 | # The lexer doesn't emit a string token if it's unterminated
28+
1 | # The lexer emits a string token if it's unterminated
2829
2 | "a" "b
2930
3 | "a" "b" "c
3031
| ^^^^^^^
@@ -33,24 +34,25 @@ ISC001 Implicitly concatenated string literals on one line
3334
|
3435
help: Combine string literals
3536

36-
invalid-syntax: missing closing quote in string literal
37-
--> ISC_syntax_error.py:3:9
37+
ISC001 Implicitly concatenated string literals on one line
38+
--> ISC_syntax_error.py:3:5
3839
|
39-
1 | # The lexer doesn't emit a string token if it's unterminated
40+
1 | # The lexer emits a string token if it's unterminated
4041
2 | "a" "b
4142
3 | "a" "b" "c
42-
| ^^
43+
| ^^^^^^
4344
4 | "a" """b
4445
5 | c""" "d
4546
|
47+
help: Combine string literals
4648

47-
invalid-syntax: Expected a statement
48-
--> ISC_syntax_error.py:3:11
49+
invalid-syntax: missing closing quote in string literal
50+
--> ISC_syntax_error.py:3:9
4951
|
50-
1 | # The lexer doesn't emit a string token if it's unterminated
52+
1 | # The lexer emits a string token if it's unterminated
5153
2 | "a" "b
5254
3 | "a" "b" "c
53-
| ^
55+
| ^^
5456
4 | "a" """b
5557
5 | c""" "d
5658
|
@@ -64,36 +66,39 @@ ISC001 Implicitly concatenated string literals on one line
6466
5 | | c""" "d
6567
| |____^
6668
6 |
67-
7 | # For f-strings, the `FStringRanges` won't contain the range for
69+
7 | # This is also true for
6870
|
6971
help: Combine string literals
7072

71-
invalid-syntax: missing closing quote in string literal
72-
--> ISC_syntax_error.py:5:6
73+
ISC001 Implicitly concatenated string literals on one line
74+
--> ISC_syntax_error.py:4:5
7375
|
74-
3 | "a" "b" "c
75-
4 | "a" """b
76-
5 | c""" "d
77-
| ^^
76+
2 | "a" "b
77+
3 | "a" "b" "c
78+
4 | "a" """b
79+
| _____^
80+
5 | | c""" "d
81+
| |_______^
7882
6 |
79-
7 | # For f-strings, the `FStringRanges` won't contain the range for
83+
7 | # This is also true for
8084
|
85+
help: Combine string literals
8186

82-
invalid-syntax: Expected a statement
83-
--> ISC_syntax_error.py:5:8
87+
invalid-syntax: missing closing quote in string literal
88+
--> ISC_syntax_error.py:5:6
8489
|
8590
3 | "a" "b" "c
8691
4 | "a" """b
8792
5 | c""" "d
88-
| ^
93+
| ^^
8994
6 |
90-
7 | # For f-strings, the `FStringRanges` won't contain the range for
95+
7 | # This is also true for
9196
|
9297

9398
invalid-syntax: f-string: unterminated string
9499
--> ISC_syntax_error.py:9:8
95100
|
96-
7 | # For f-strings, the `FStringRanges` won't contain the range for
101+
7 | # This is also true for
97102
8 | # unterminated f-strings.
98103
9 | f"a" f"b
99104
| ^
@@ -104,7 +109,7 @@ invalid-syntax: f-string: unterminated string
104109
invalid-syntax: Expected FStringEnd, found newline
105110
--> ISC_syntax_error.py:9:9
106111
|
107-
7 | # For f-strings, the `FStringRanges` won't contain the range for
112+
7 | # This is also true for
108113
8 | # unterminated f-strings.
109114
9 | f"a" f"b
110115
| ^
@@ -183,14 +188,6 @@ invalid-syntax: f-string: unterminated triple-quoted string
183188
| |__^
184189
|
185190

186-
invalid-syntax: unexpected EOF while parsing
187-
--> ISC_syntax_error.py:30:1
188-
|
189-
28 | "i" "j"
190-
29 | )
191-
| ^
192-
|
193-
194191
invalid-syntax: f-string: unterminated string
195192
--> ISC_syntax_error.py:30:1
196193
|

crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC002_ISC_syntax_error.py.snap

Lines changed: 5 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,24 @@ source: crates/ruff_linter/src/rules/flake8_implicit_str_concat/mod.rs
44
invalid-syntax: missing closing quote in string literal
55
--> ISC_syntax_error.py:2:5
66
|
7-
1 | # The lexer doesn't emit a string token if it's unterminated
7+
1 | # The lexer emits a string token if it's unterminated
88
2 | "a" "b
99
| ^^
1010
3 | "a" "b" "c
1111
4 | "a" """b
1212
|
1313

14-
invalid-syntax: Expected a statement
15-
--> ISC_syntax_error.py:2:7
16-
|
17-
1 | # The lexer doesn't emit a string token if it's unterminated
18-
2 | "a" "b
19-
| ^
20-
3 | "a" "b" "c
21-
4 | "a" """b
22-
|
23-
2414
invalid-syntax: missing closing quote in string literal
2515
--> ISC_syntax_error.py:3:9
2616
|
27-
1 | # The lexer doesn't emit a string token if it's unterminated
17+
1 | # The lexer emits a string token if it's unterminated
2818
2 | "a" "b
2919
3 | "a" "b" "c
3020
| ^^
3121
4 | "a" """b
3222
5 | c""" "d
3323
|
3424

35-
invalid-syntax: Expected a statement
36-
--> ISC_syntax_error.py:3:11
37-
|
38-
1 | # The lexer doesn't emit a string token if it's unterminated
39-
2 | "a" "b
40-
3 | "a" "b" "c
41-
| ^
42-
4 | "a" """b
43-
5 | c""" "d
44-
|
45-
4625
invalid-syntax: missing closing quote in string literal
4726
--> ISC_syntax_error.py:5:6
4827
|
@@ -51,24 +30,13 @@ invalid-syntax: missing closing quote in string literal
5130
5 | c""" "d
5231
| ^^
5332
6 |
54-
7 | # For f-strings, the `FStringRanges` won't contain the range for
55-
|
56-
57-
invalid-syntax: Expected a statement
58-
--> ISC_syntax_error.py:5:8
59-
|
60-
3 | "a" "b" "c
61-
4 | "a" """b
62-
5 | c""" "d
63-
| ^
64-
6 |
65-
7 | # For f-strings, the `FStringRanges` won't contain the range for
33+
7 | # This is also true for
6634
|
6735

6836
invalid-syntax: f-string: unterminated string
6937
--> ISC_syntax_error.py:9:8
7038
|
71-
7 | # For f-strings, the `FStringRanges` won't contain the range for
39+
7 | # This is also true for
7240
8 | # unterminated f-strings.
7341
9 | f"a" f"b
7442
| ^
@@ -79,7 +47,7 @@ invalid-syntax: f-string: unterminated string
7947
invalid-syntax: Expected FStringEnd, found newline
8048
--> ISC_syntax_error.py:9:9
8149
|
82-
7 | # For f-strings, the `FStringRanges` won't contain the range for
50+
7 | # This is also true for
8351
8 | # unterminated f-strings.
8452
9 | f"a" f"b
8553
| ^
@@ -133,14 +101,6 @@ invalid-syntax: f-string: unterminated triple-quoted string
133101
| |__^
134102
|
135103

136-
invalid-syntax: unexpected EOF while parsing
137-
--> ISC_syntax_error.py:30:1
138-
|
139-
28 | "i" "j"
140-
29 | )
141-
| ^
142-
|
143-
144104
invalid-syntax: f-string: unterminated string
145105
--> ISC_syntax_error.py:30:1
146106
|

crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2510_invalid_characters_syntax_error.py.snap

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,17 @@ invalid-syntax: missing closing quote in string literal
2323
9 | # Unterminated f-string
2424
|
2525

26-
invalid-syntax: Expected a statement
27-
--> invalid_characters_syntax_error.py:7:7
26+
PLE2510 Invalid unescaped character backspace, use "\b" instead
27+
--> invalid_characters_syntax_error.py:7:6
2828
|
2929
5 | b = ''
3030
6 | # Unterminated string
3131
7 | b = '
32-
| ^
32+
| ^
3333
8 | b = ''
3434
9 | # Unterminated f-string
3535
|
36+
help: Replace with escape sequence
3637

3738
PLE2510 Invalid unescaped character backspace, use "\b" instead
3839
--> invalid_characters_syntax_error.py:8:6
@@ -46,6 +47,18 @@ PLE2510 Invalid unescaped character backspace, use "\b" instead
4647
|
4748
help: Replace with escape sequence
4849

50+
PLE2510 Invalid unescaped character backspace, use "\b" instead
51+
--> invalid_characters_syntax_error.py:10:7
52+
|
53+
8 | b = ''
54+
9 | # Unterminated f-string
55+
10 | b = f'
56+
| ^
57+
11 | b = f''
58+
12 | # Implicitly concatenated
59+
|
60+
help: Replace with escape sequence
61+
4962
invalid-syntax: f-string: unterminated string
5063
--> invalid_characters_syntax_error.py:10:7
5164
|
@@ -109,11 +122,12 @@ invalid-syntax: missing closing quote in string literal
109122
| ^^
110123
|
111124

112-
invalid-syntax: Expected a statement
113-
--> invalid_characters_syntax_error.py:13:16
125+
PLE2510 Invalid unescaped character backspace, use "\b" instead
126+
--> invalid_characters_syntax_error.py:13:15
114127
|
115128
11 | b = f''
116129
12 | # Implicitly concatenated
117130
13 | b = '' f'' '
118-
| ^
131+
| ^
119132
|
133+
help: Replace with escape sequence

crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
66
use ruff_macros::{ViolationMetadata, derive_message_formats};
77
use ruff_python_ast::helpers::any_over_expr;
88
use ruff_python_ast::str::{leading_quote, trailing_quote};
9-
use ruff_python_ast::{self as ast, Expr, Keyword};
9+
use ruff_python_ast::{self as ast, Expr, Keyword, StringFlags};
1010
use ruff_python_literal::format::{
1111
FieldName, FieldNamePart, FieldType, FormatPart, FormatString, FromTemplate,
1212
};
@@ -430,7 +430,7 @@ pub(crate) fn f_strings(checker: &Checker, call: &ast::ExprCall, summary: &Forma
430430
// dot is the start of an attribute access.
431431
break token.start();
432432
}
433-
TokenKind::String => {
433+
TokenKind::String if !token.unwrap_string_flags().is_unclosed() => {
434434
match FStringConversion::try_convert(token.range(), &mut summary, checker.locator())
435435
{
436436
// If the format string contains side effects that would need to be repeated,

0 commit comments

Comments
 (0)