Skip to content

Commit b617d90

Browse files
authored
Update E999 to show all syntax errors (#11900)
## Summary This PR updates the linter to show all the parse errors as diagnostics instead of just the first one. Note that this doesn't affect the parse error displayed as error log message. This will be removed in a follow-up PR. ### Breaking? I don't think this is a breaking change even though this might give more diagnostics. The main reason is that this shouldn't affect any users because it'll only give additional diagnostics in the case of multiple syntax errors. ## Test Plan Add an integration test case which would raise more than one parse error.
1 parent cdc7c71 commit b617d90

File tree

4 files changed

+30
-12
lines changed

4 files changed

+30
-12
lines changed

crates/ruff/tests/integration_test.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -727,15 +727,32 @@ fn stdin_format_jupyter() {
727727
fn stdin_parse_error() {
728728
let mut cmd = RuffCheck::default().build();
729729
assert_cmd_snapshot!(cmd
730-
.pass_stdin("from foo import =\n"), @r###"
730+
.pass_stdin("from foo import\n"), @r###"
731731
success: false
732732
exit_code: 1
733733
----- stdout -----
734-
-:1:17: E999 SyntaxError: Expected an import name
734+
-:1:16: E999 SyntaxError: Expected one or more symbol names after import
735735
Found 1 error.
736736
737737
----- stderr -----
738-
error: Failed to parse at 1:17: Expected an import name
738+
error: Failed to parse at 1:16: Expected one or more symbol names after import
739+
"###);
740+
}
741+
742+
#[test]
743+
fn stdin_multiple_parse_error() {
744+
let mut cmd = RuffCheck::default().build();
745+
assert_cmd_snapshot!(cmd
746+
.pass_stdin("from foo import\nbar =\n"), @r###"
747+
success: false
748+
exit_code: 1
749+
----- stdout -----
750+
-:1:16: E999 SyntaxError: Expected one or more symbol names after import
751+
-:2:6: E999 SyntaxError: Expected an expression
752+
Found 2 errors.
753+
754+
----- stderr -----
755+
error: Failed to parse at 1:16: Expected one or more symbol names after import
739756
"###);
740757
}
741758

crates/ruff_linter/src/linter.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,15 +192,17 @@ pub fn check_path(
192192
doc_lines.extend(doc_lines_from_ast(parsed.suite(), locator));
193193
}
194194
}
195-
Err(parse_error) => {
195+
Err(parse_errors) => {
196196
// Always add a diagnostic for the syntax error, regardless of whether
197197
// `Rule::SyntaxError` is enabled. We avoid propagating the syntax error
198198
// if it's disabled via any of the usual mechanisms (e.g., `noqa`,
199199
// `per-file-ignores`), and the easiest way to detect that suppression is
200200
// to see if the diagnostic persists to the end of the function.
201-
pycodestyle::rules::syntax_error(&mut diagnostics, parse_error, locator);
201+
for parse_error in parse_errors {
202+
pycodestyle::rules::syntax_error(&mut diagnostics, parse_error, locator);
203+
}
202204
// TODO(dhruvmanila): Remove this clone
203-
error = Some(parse_error.clone());
205+
error = parse_errors.iter().next().cloned();
204206
}
205207
}
206208
}

crates/ruff_python_parser/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,11 +274,11 @@ impl<T> Parsed<T> {
274274

275275
/// Returns the [`Parsed`] output as a [`Result`], returning [`Ok`] if it has no syntax errors,
276276
/// or [`Err`] containing the first [`ParseError`] encountered.
277-
pub fn as_result(&self) -> Result<&Parsed<T>, &ParseError> {
278-
if let [error, ..] = self.errors() {
279-
Err(error)
280-
} else {
277+
pub fn as_result(&self) -> Result<&Parsed<T>, &[ParseError]> {
278+
if self.is_valid() {
281279
Ok(self)
280+
} else {
281+
Err(&self.errors)
282282
}
283283
}
284284

crates/ruff_python_parser/tests/fixtures.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,7 @@ fn test_invalid_syntax(input_path: &Path) {
126126
#[allow(clippy::print_stdout)]
127127
fn parser_quick_test() {
128128
let source = "\
129-
def foo()
130-
pass
129+
from foo import
131130
";
132131

133132
let parsed = parse_unchecked(source, Mode::Module);

0 commit comments

Comments
 (0)