Skip to content
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

Merged
merged 1 commit into from
Jun 26, 2024
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
Consider 2-character EOL before line continuation
  • Loading branch information
dhruvmanila committed Jun 26, 2024
commit 61d7288d8e97c36e4ee02753f9542e34f1641c64
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_3.py text eol=crlf
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples_crlf.py text eol=crlf
crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples_crlf.py.snap text eol=crlf

crates/ruff_python_parser/resources/invalid/re_lexing/line_continuation_windows_eol.py text eol=crlf
crates/ruff_python_parser/resources/invalid/re_lex_logical_token_windows_eol.py text eol=crlf
crates/ruff_python_parser/resources/invalid/re_lex_logical_token_mac_eol.py text eol=cr

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
call(a, b, # comment \

def bar():
pass
48 changes: 31 additions & 17 deletions crates/ruff_python_parser/src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member Author

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.

Comment on lines +1399 to +1408
Copy link
Member

Choose a reason for hiding this comment

The 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
}

Copy link
Member Author

Choose a reason for hiding this comment

The 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);
}
}

Expand Down
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
|
Loading