-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consider 2-character EOL before line continuation #12035
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
call(a, b, # comment \ | ||
|
||
def bar(): | ||
pass |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1393,26 +1393,40 @@ impl<'src> Lexer<'src> { | |
while let Some(ch) = reverse_chars.next() { | ||
if is_python_whitespace(ch) { | ||
current_position -= ch.text_len(); | ||
} else if matches!(ch, '\n' | '\r') { | ||
current_position -= ch.text_len(); | ||
// Count the number of backslashes before the newline character. | ||
let mut backslash_count = 0; | ||
while reverse_chars.next_if_eq(&'\\').is_some() { | ||
backslash_count += 1; | ||
} | ||
if backslash_count == 0 { | ||
// No escapes: `\n` | ||
newline_position = Some(current_position); | ||
} else { | ||
if backslash_count % 2 == 0 { | ||
// Even number of backslashes i.e., all backslashes cancel each other out | ||
// which means the newline character is not being escaped. | ||
newline_position = Some(current_position); | ||
continue; | ||
} | ||
|
||
match ch { | ||
'\n' => { | ||
current_position -= ch.text_len(); | ||
if let Some(carriage_return) = reverse_chars.next_if_eq(&'\r') { | ||
current_position -= carriage_return.text_len(); | ||
} | ||
current_position -= TextSize::new('\\'.text_len().to_u32() * backslash_count); | ||
} | ||
'\r' => { | ||
current_position -= ch.text_len(); | ||
} | ||
Comment on lines
+1399
to
+1408
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can simplify this logic a bit. It doesn't seem to matter how many new line characters there are, we just want to skip all of them if matches!(c, '\n' | '\r') {
while Some(newline) = reverse_chars.next_if(|c| matches!(c, '\n' | '\r')) {
current_position -= newline.text_len();
}
// after any new line, handle line continuation
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this will work because we want the position of the last unescaped newline character. We would still need to keep track of that position, so we might as well be explicit about this. |
||
_ => break, | ||
} | ||
|
||
debug_assert!(matches!(ch, '\n' | '\r')); | ||
|
||
// Count the number of backslashes before the newline character. | ||
let mut backslash_count = 0; | ||
while reverse_chars.next_if_eq(&'\\').is_some() { | ||
backslash_count += 1; | ||
} | ||
|
||
if backslash_count == 0 { | ||
// No escapes: `\n` | ||
newline_position = Some(current_position); | ||
} else { | ||
break; | ||
if backslash_count % 2 == 0 { | ||
// Even number of backslashes i.e., all backslashes cancel each other out | ||
// which means the newline character is not being escaped. | ||
newline_position = Some(current_position); | ||
} | ||
current_position -= TextSize::new('\\'.text_len().to_u32() * backslash_count); | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
--- | ||
source: crates/ruff_python_parser/tests/fixtures.rs | ||
input_file: crates/ruff_python_parser/resources/invalid/re_lexing/line_continuation_windows_eol.py | ||
--- | ||
## AST | ||
|
||
``` | ||
Module( | ||
ModModule { | ||
range: 0..46, | ||
body: [ | ||
Expr( | ||
StmtExpr { | ||
range: 0..10, | ||
value: Call( | ||
ExprCall { | ||
range: 0..10, | ||
func: Name( | ||
ExprName { | ||
range: 0..4, | ||
id: "call", | ||
ctx: Load, | ||
}, | ||
), | ||
arguments: Arguments { | ||
range: 4..10, | ||
args: [ | ||
Name( | ||
ExprName { | ||
range: 5..6, | ||
id: "a", | ||
ctx: Load, | ||
}, | ||
), | ||
Name( | ||
ExprName { | ||
range: 8..9, | ||
id: "b", | ||
ctx: Load, | ||
}, | ||
), | ||
], | ||
keywords: [], | ||
}, | ||
}, | ||
), | ||
}, | ||
), | ||
FunctionDef( | ||
StmtFunctionDef { | ||
range: 26..46, | ||
is_async: false, | ||
decorator_list: [], | ||
name: Identifier { | ||
id: "bar", | ||
range: 30..33, | ||
}, | ||
type_params: None, | ||
parameters: Parameters { | ||
range: 33..35, | ||
posonlyargs: [], | ||
args: [], | ||
vararg: None, | ||
kwonlyargs: [], | ||
kwarg: None, | ||
}, | ||
returns: None, | ||
body: [ | ||
Pass( | ||
StmtPass { | ||
range: 42..46, | ||
}, | ||
), | ||
], | ||
}, | ||
), | ||
], | ||
}, | ||
) | ||
``` | ||
## Errors | ||
|
||
| | ||
1 | call(a, b, # comment \ | ||
2 | / | ||
3 | | def bar(): | ||
| |_^ Syntax Error: Expected ')', found newline | ||
4 | pass | ||
| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main change is to split the match with
\r
and\n
into it's own branch and then a common check for line continuation character.