Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
if True:
1
\
2
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
if True:
\
1
\
2
else:\
3
97 changes: 96 additions & 1 deletion crates/ruff_python_parser/src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,35 @@ impl<'src> Lexer<'src> {
self.token_range(),
)));
}
indentation = Indentation::root();
// test_ok backslash_continuation_indentation
// if True:
// \
// 1
// \
// 2
// else:\
// 3

// test_err backslash_continuation_indentation_error
// if True:
// 1
// \
// 2

// > Indentation cannot be split over multiple physical lines using backslashes;
// > the whitespace up to the first backslash determines the indentation.
// >
// > https://docs.python.org/3/reference/lexical_analysis.html#indentation
//
// Skip whitespace after the continuation-line without accumulating it into
// `indentation`. However, if the backslash is at column 0 (no prior
// indentation), let the loop continue so the next line's whitespace is
// accumulated normally.
//
// See also: https://github.com/python/cpython/issues/90249
if indentation != Indentation::root() {
self.cursor.eat_while(is_python_whitespace);
}
}
// Form feed
'\x0C' => {
Expand Down Expand Up @@ -3061,4 +3089,71 @@ t"{(lambda x:{x})}"
UnterminatedTripleQuotedString
);
}

#[test]
fn backslash_continuation_indentation() {
// The first `\` has 4 spaces before it which matches the indentation level at that point,
// so the whitespace before `2` is irrelevant and shouldn't produce an indentation error.
// Similarly, the second `\` is also at the same indentation level, so the `3` line is also
// valid.
let source = r"if True:
1
\
2
\
3
else:
pass
"
.to_string();
assert_snapshot!(lex_source(&source));
}

#[test]
fn backslash_continuation_at_root() {
// But, it's a different when the backslash character itself is at the root indentation
// level. Then, the whitespaces following it determines the indentation level of the next
// line, so `1` is indented with 4 spaces and `2` is indented with 8 spaces, and `3` is
// indented with 4 spaces, all of which are valid.
let source = r"if True:
\
1
if True:
\
2
else:\
3
"
.to_string();
assert_snapshot!(lex_source(&source));
}

#[test]
fn multiple_backslash_continuation() {
// It's only the first backslash character that determines the indentation level of the next
// line, so all the lines after the first `\` are indented with 4 spaces, and the remaining
// backslashes are just ignored and don't affect the indentation level.
let source = r"if True:
1
\
\
\
\
2
"
.to_string();
assert_snapshot!(lex_source(&source));
}

#[test]
fn backslash_continuation_mismatch_indentation() {
// Indentation doesn't match any previous indentation level
let source = r"if True:
1
\
2
"
.to_string();
assert_snapshot!(lex_invalid(&source, Mode::Module));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(&source)
---
## Tokens
```
[
(
If,
0..2,
),
(
True,
3..7,
),
(
Colon,
7..8,
),
(
Newline,
8..9,
),
(
Indent,
9..15,
),
(
Int(
1,
),
15..16,
),
(
Newline,
16..17,
),
(
If,
21..23,
),
(
True,
24..28,
),
(
Colon,
28..29,
),
(
Newline,
29..30,
),
(
Indent,
30..40,
),
(
Int(
2,
),
40..41,
),
(
Newline,
41..42,
),
(
Dedent,
42..42,
),
(
Dedent,
42..42,
),
(
Else,
42..46,
),
(
Colon,
46..47,
),
(
Int(
3,
),
53..54,
),
(
Newline,
54..55,
),
]
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: lex_source(&source)
---
## Tokens
```
[
(
If,
0..2,
),
(
True,
3..7,
),
(
Colon,
7..8,
),
(
Newline,
8..9,
),
(
Indent,
9..13,
),
(
Int(
1,
),
13..14,
),
(
Newline,
14..15,
),
(
Int(
2,
),
29..30,
),
(
Newline,
30..31,
),
(
Int(
3,
),
37..38,
),
(
Newline,
38..39,
),
(
Dedent,
39..39,
),
(
Else,
39..43,
),
(
Colon,
43..44,
),
(
Newline,
44..45,
),
(
Indent,
45..49,
),
(
Pass,
49..53,
),
(
Newline,
53..54,
),
(
Dedent,
54..54,
),
]
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
source: crates/ruff_python_parser/src/lexer.rs
expression: "lex_invalid(&source, Mode::Module)"
---
## Tokens
```
[
(
If,
0..2,
),
(
True,
3..7,
),
(
Colon,
7..8,
),
(
Newline,
8..9,
),
(
Indent,
9..13,
),
(
Int(
1,
),
13..14,
),
(
Newline,
14..15,
),
(
Unknown,
15..23,
),
(
Int(
2,
),
23..24,
),
(
Newline,
24..25,
),
]
```
## Errors
```
[
LexicalError {
error: IndentationError,
location: 15..23,
},
]
```
Loading