Allow hiding the diagnostic severity in ruff_db (#19644)

## Summary

This PR is a spin-off from https://github.com/astral-sh/ruff/pull/19415.
It enables replacing the severity and lint name in a ty-style
diagnostic:

```
error[unused-import]: `os` imported but unused
```

with the noqa code and optional fix availability icon for a Ruff
diagnostic:

```
F401 [*] `os` imported but unused
F821 Undefined name `a`
```

or nothing at all for a Ruff syntax error:

```
SyntaxError: Expected one or more symbol names after import
```

Ruff adds the `SyntaxError` prefix to these messages manually.

Initially (d912458), I just passed a `hide_severity` flag through a
bunch of calls to get it into `annotate-snippets`, but after looking at
it again today, I think reusing the `None` severity/level gave a nicer
result. As I note in a lengthy code comment, I think all of this code
should be temporary and reverted when Ruff gets real severities, so
hopefully it's okay if it feels a little hacky.

I think the main visible downside of this approach is that we can't
style the asterisk in the fix availabilty icon in cyan, as in Ruff's
current output. It's part of the message in this PR and any styling gets
overwritten in `annotate-snippets`.

<img width="400" height="342" alt="image"
src="https://github.com/user-attachments/assets/57542ec9-a81c-4a01-91c7-bd6d7ec99f99"
/>

Hmm, I guess reusing `Level::None` also means the `F401` isn't red
anymore. Maybe my initial approach was better after all. In any case,
the rest of the PR should be basically the same, it just depends how we
want to toggle the severity.

## Test Plan

New `ruff_db` tests. These snapshots should be compared to the two tests
just above them (`hide_severity_output` vs `output` and
`hide_severity_syntax_errors` against `syntax_errors`).
This commit is contained in:
Brent Westbrook 2025-08-05 09:56:18 -04:00 committed by GitHub
parent 94947cbf65
commit 78e5fe0a51
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
77 changed files with 448 additions and 223 deletions

View file

@ -798,7 +798,7 @@ fn stdin_parse_error() {
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
-:1:16: SyntaxError: Expected one or more symbol names after import -:1:16: invalid-syntax: Expected one or more symbol names after import
| |
1 | from foo import 1 | from foo import
| ^ | ^
@ -818,14 +818,14 @@ fn stdin_multiple_parse_error() {
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
-:1:16: SyntaxError: Expected one or more symbol names after import -:1:16: invalid-syntax: Expected one or more symbol names after import
| |
1 | from foo import 1 | from foo import
| ^ | ^
2 | bar = 2 | bar =
| |
-:2:6: SyntaxError: Expected an expression -:2:6: invalid-syntax: Expected an expression
| |
1 | from foo import 1 | from foo import
2 | bar = 2 | bar =
@ -847,7 +847,7 @@ fn parse_error_not_included() {
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
-:1:6: SyntaxError: Expected an expression -:1:6: invalid-syntax: Expected an expression
| |
1 | foo = 1 | foo =
| ^ | ^

View file

@ -5389,7 +5389,7 @@ fn walrus_before_py38() {
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
test.py:1:2: SyntaxError: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8) test.py:1:2: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
Found 1 error. Found 1 error.
----- stderr ----- ----- stderr -----
@ -5435,15 +5435,15 @@ match 2:
print("it's one") print("it's one")
"# "#
), ),
@r###" @r"
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
test.py:2:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) test.py:2:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
Found 1 error. Found 1 error.
----- stderr ----- ----- stderr -----
"### "
); );
// syntax error on 3.9 with preview // syntax error on 3.9 with preview
@ -5464,7 +5464,7 @@ match 2:
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
test.py:2:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) test.py:2:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
Found 1 error. Found 1 error.
----- stderr ----- ----- stderr -----
@ -5492,7 +5492,7 @@ fn cache_syntax_errors() -> Result<()> {
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
main.py:1:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) main.py:1:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
----- stderr ----- ----- stderr -----
" "
@ -5505,7 +5505,7 @@ fn cache_syntax_errors() -> Result<()> {
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
main.py:1:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) main.py:1:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
----- stderr ----- ----- stderr -----
" "
@ -5618,7 +5618,7 @@ fn semantic_syntax_errors() -> Result<()> {
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
main.py:1:3: SyntaxError: assignment expression cannot rebind comprehension variable main.py:1:3: invalid-syntax: assignment expression cannot rebind comprehension variable
main.py:1:20: F821 Undefined name `foo` main.py:1:20: F821 Undefined name `foo`
----- stderr ----- ----- stderr -----
@ -5632,7 +5632,7 @@ fn semantic_syntax_errors() -> Result<()> {
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
main.py:1:3: SyntaxError: assignment expression cannot rebind comprehension variable main.py:1:3: invalid-syntax: assignment expression cannot rebind comprehension variable
main.py:1:20: F821 Undefined name `foo` main.py:1:20: F821 Undefined name `foo`
----- stderr ----- ----- stderr -----
@ -5651,7 +5651,7 @@ fn semantic_syntax_errors() -> Result<()> {
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
-:1:3: SyntaxError: assignment expression cannot rebind comprehension variable -:1:3: invalid-syntax: assignment expression cannot rebind comprehension variable
Found 1 error. Found 1 error.
----- stderr ----- ----- stderr -----

View file

@ -18,6 +18,6 @@ exit_code: 1
----- stdout ----- ----- stdout -----
##vso[task.logissue type=error;sourcepath=[TMP]/input.py;linenumber=1;columnnumber=8;code=F401;]`os` imported but unused ##vso[task.logissue type=error;sourcepath=[TMP]/input.py;linenumber=1;columnnumber=8;code=F401;]`os` imported but unused
##vso[task.logissue type=error;sourcepath=[TMP]/input.py;linenumber=2;columnnumber=5;code=F821;]Undefined name `y` ##vso[task.logissue type=error;sourcepath=[TMP]/input.py;linenumber=2;columnnumber=5;code=F821;]Undefined name `y`
##vso[task.logissue type=error;sourcepath=[TMP]/input.py;linenumber=3;columnnumber=1;]SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) ##vso[task.logissue type=error;sourcepath=[TMP]/input.py;linenumber=3;columnnumber=1;code=invalid-syntax;]Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
----- stderr ----- ----- stderr -----

View file

@ -18,7 +18,7 @@ exit_code: 1
----- stdout ----- ----- stdout -----
input.py:1:8: F401 [*] `os` imported but unused input.py:1:8: F401 [*] `os` imported but unused
input.py:2:5: F821 Undefined name `y` input.py:2:5: F821 Undefined name `y`
input.py:3:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) input.py:3:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
Found 3 errors. Found 3 errors.
[*] 1 fixable with the `--fix` option. [*] 1 fixable with the `--fix` option.

View file

@ -34,7 +34,7 @@ input.py:2:5: F821 Undefined name `y`
4 | case _: ... 4 | case _: ...
| |
input.py:3:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) input.py:3:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
| |
1 | import os # F401 1 | import os # F401
2 | x = y # F821 2 | x = y # F821

View file

@ -18,6 +18,6 @@ exit_code: 1
----- stdout ----- ----- stdout -----
::error title=Ruff (F401),file=[TMP]/input.py,line=1,col=8,endLine=1,endColumn=10::input.py:1:8: F401 `os` imported but unused ::error title=Ruff (F401),file=[TMP]/input.py,line=1,col=8,endLine=1,endColumn=10::input.py:1:8: F401 `os` imported but unused
::error title=Ruff (F821),file=[TMP]/input.py,line=2,col=5,endLine=2,endColumn=6::input.py:2:5: F821 Undefined name `y` ::error title=Ruff (F821),file=[TMP]/input.py,line=2,col=5,endLine=2,endColumn=6::input.py:2:5: F821 Undefined name `y`
::error title=Ruff,file=[TMP]/input.py,line=3,col=1,endLine=3,endColumn=6::input.py:3:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) ::error title=Ruff (invalid-syntax),file=[TMP]/input.py,line=3,col=1,endLine=3,endColumn=6::input.py:3:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
----- stderr ----- ----- stderr -----

View file

@ -19,7 +19,7 @@ exit_code: 1
input.py: input.py:
1:8 F401 [*] `os` imported but unused 1:8 F401 [*] `os` imported but unused
2:5 F821 Undefined name `y` 2:5 F821 Undefined name `y`
3:1 SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) 3:1 invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
Found 3 errors. Found 3 errors.
[*] 1 fixable with the `--fix` option. [*] 1 fixable with the `--fix` option.

View file

@ -18,6 +18,6 @@ exit_code: 1
----- stdout ----- ----- stdout -----
{"cell":null,"code":"F401","end_location":{"column":10,"row":1},"filename":"[TMP]/input.py","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":1},"message":"`os` imported but unused","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/unused-import"} {"cell":null,"code":"F401","end_location":{"column":10,"row":1},"filename":"[TMP]/input.py","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":1},"message":"`os` imported but unused","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
{"cell":null,"code":"F821","end_location":{"column":6,"row":2},"filename":"[TMP]/input.py","fix":null,"location":{"column":5,"row":2},"message":"Undefined name `y`","noqa_row":2,"url":"https://docs.astral.sh/ruff/rules/undefined-name"} {"cell":null,"code":"F821","end_location":{"column":6,"row":2},"filename":"[TMP]/input.py","fix":null,"location":{"column":5,"row":2},"message":"Undefined name `y`","noqa_row":2,"url":"https://docs.astral.sh/ruff/rules/undefined-name"}
{"cell":null,"code":null,"end_location":{"column":6,"row":3},"filename":"[TMP]/input.py","fix":null,"location":{"column":1,"row":3},"message":"SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)","noqa_row":null,"url":null} {"cell":null,"code":"invalid-syntax","end_location":{"column":6,"row":3},"filename":"[TMP]/input.py","fix":null,"location":{"column":1,"row":3},"message":"Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)","noqa_row":null,"url":null}
----- stderr ----- ----- stderr -----

View file

@ -69,7 +69,7 @@ exit_code: 1
}, },
{ {
"cell": null, "cell": null,
"code": null, "code": "invalid-syntax",
"end_location": { "end_location": {
"column": 6, "column": 6,
"row": 3 "row": 3
@ -80,7 +80,7 @@ exit_code: 1
"column": 1, "column": 1,
"row": 3 "row": 3
}, },
"message": "SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)", "message": "Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)",
"noqa_row": null, "noqa_row": null,
"url": null "url": null
} }

View file

@ -26,7 +26,7 @@ exit_code: 1
<failure message="Undefined name `y`">line 2, col 5, Undefined name `y`</failure> <failure message="Undefined name `y`">line 2, col 5, Undefined name `y`</failure>
</testcase> </testcase>
<testcase name="org.ruff.invalid-syntax" classname="[TMP]/input" line="3" column="1"> <testcase name="org.ruff.invalid-syntax" classname="[TMP]/input" line="3" column="1">
<failure message="SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)">line 3, col 1, SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)</failure> <failure message="Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)">line 3, col 1, Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)</failure>
</testcase> </testcase>
</testsuite> </testsuite>
</testsuites> </testsuites>

View file

@ -18,6 +18,6 @@ exit_code: 1
----- stdout ----- ----- stdout -----
input.py:1: [F401] `os` imported but unused input.py:1: [F401] `os` imported but unused
input.py:2: [F821] Undefined name `y` input.py:2: [F821] Undefined name `y`
input.py:3: [invalid-syntax] SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) input.py:3: [invalid-syntax] Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
----- stderr ----- ----- stderr -----

View file

@ -90,7 +90,7 @@ exit_code: 1
} }
} }
}, },
"message": "SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)" "message": "Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)"
} }
], ],
"severity": "WARNING", "severity": "WARNING",

View file

@ -83,9 +83,9 @@ exit_code: 1
} }
], ],
"message": { "message": {
"text": "SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)" "text": "Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)"
}, },
"ruleId": null "ruleId": "invalid-syntax"
} }
], ],
"tool": { "tool": {

View file

@ -193,9 +193,14 @@ impl DisplaySet<'_> {
stylesheet: &Stylesheet, stylesheet: &Stylesheet,
buffer: &mut StyledBuffer, buffer: &mut StyledBuffer,
) -> fmt::Result { ) -> fmt::Result {
let hide_severity = annotation.annotation_type.is_none();
let color = get_annotation_style(&annotation.annotation_type, stylesheet); let color = get_annotation_style(&annotation.annotation_type, stylesheet);
let formatted_len = if let Some(id) = &annotation.id { let formatted_len = if let Some(id) = &annotation.id {
2 + id.len() + annotation_type_len(&annotation.annotation_type) if hide_severity {
id.len()
} else {
2 + id.len() + annotation_type_len(&annotation.annotation_type)
}
} else { } else {
annotation_type_len(&annotation.annotation_type) annotation_type_len(&annotation.annotation_type)
}; };
@ -209,18 +214,62 @@ impl DisplaySet<'_> {
if formatted_len == 0 { if formatted_len == 0 {
self.format_label(line_offset, &annotation.label, stylesheet, buffer) self.format_label(line_offset, &annotation.label, stylesheet, buffer)
} else { } else {
let id = match &annotation.id { // TODO(brent) All of this complicated checking of `hide_severity` should be reverted
Some(id) => format!("[{id}]"), // once we have real severities in Ruff. This code is trying to account for two
None => String::new(), // different cases:
}; //
buffer.append( // - main diagnostic message
line_offset, // - subdiagnostic message
&format!("{}{}", annotation_type_str(&annotation.annotation_type), id), //
*color, // In the first case, signaled by `hide_severity = true`, we want to print the ID (the
); // noqa code for a ruff lint diagnostic, e.g. `F401`, or `invalid-syntax` for a syntax
// error) without brackets. Instead, for subdiagnostics, we actually want to print the
// severity (usually `help`) regardless of the `hide_severity` setting. This is signaled
// by an ID of `None`.
//
// With real severities these should be reported more like in ty:
//
// ```
// error[F401]: `math` imported but unused
// error[invalid-syntax]: Cannot use `match` statement on Python 3.9...
// ```
//
// instead of the current versions intended to mimic the old Ruff output format:
//
// ```
// F401 `math` imported but unused
// invalid-syntax: Cannot use `match` statement on Python 3.9...
// ```
//
// Note that the `invalid-syntax` colon is added manually in `ruff_db`, not here. We
// could eventually add a colon to Ruff lint diagnostics (`F401:`) and then make the
// colon below unconditional again.
//
// This also applies to the hard-coded `stylesheet.error()` styling of the
// hidden-severity `id`. This should just be `*color` again later, but for now we don't
// want an unformatted `id`, which is what `get_annotation_style` returns for
// `DisplayAnnotationType::None`.
let annotation_type = annotation_type_str(&annotation.annotation_type);
if let Some(id) = annotation.id {
if hide_severity {
buffer.append(line_offset, &format!("{id} "), *stylesheet.error());
} else {
buffer.append(line_offset, &format!("{annotation_type}[{id}]"), *color);
}
} else {
buffer.append(line_offset, annotation_type, *color);
}
if annotation.is_fixable {
buffer.append(line_offset, "[", stylesheet.none);
buffer.append(line_offset, "*", stylesheet.help);
buffer.append(line_offset, "] ", stylesheet.none);
}
if !is_annotation_empty(annotation) { if !is_annotation_empty(annotation) {
buffer.append(line_offset, ": ", stylesheet.none); if annotation.id.is_none() || !hide_severity {
buffer.append(line_offset, ": ", stylesheet.none);
}
self.format_label(line_offset, &annotation.label, stylesheet, buffer)?; self.format_label(line_offset, &annotation.label, stylesheet, buffer)?;
} }
Ok(()) Ok(())
@ -768,6 +817,7 @@ pub(crate) struct Annotation<'a> {
pub(crate) annotation_type: DisplayAnnotationType, pub(crate) annotation_type: DisplayAnnotationType,
pub(crate) id: Option<&'a str>, pub(crate) id: Option<&'a str>,
pub(crate) label: Vec<DisplayTextFragment<'a>>, pub(crate) label: Vec<DisplayTextFragment<'a>>,
pub(crate) is_fixable: bool,
} }
/// A single line used in `DisplayList`. /// A single line used in `DisplayList`.
@ -920,6 +970,13 @@ pub(crate) enum DisplayAnnotationType {
Help, Help,
} }
impl DisplayAnnotationType {
#[inline]
const fn is_none(&self) -> bool {
matches!(self, Self::None)
}
}
impl From<snippet::Level> for DisplayAnnotationType { impl From<snippet::Level> for DisplayAnnotationType {
fn from(at: snippet::Level) -> Self { fn from(at: snippet::Level) -> Self {
match at { match at {
@ -1015,11 +1072,12 @@ fn format_message<'m>(
title, title,
footer, footer,
snippets, snippets,
is_fixable,
} = message; } = message;
let mut sets = vec![]; let mut sets = vec![];
let body = if !snippets.is_empty() || primary { let body = if !snippets.is_empty() || primary {
vec![format_title(level, id, title)] vec![format_title(level, id, title, is_fixable)]
} else { } else {
format_footer(level, id, title) format_footer(level, id, title)
}; };
@ -1060,12 +1118,18 @@ fn format_message<'m>(
sets sets
} }
fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> DisplayLine<'a> { fn format_title<'a>(
level: crate::Level,
id: Option<&'a str>,
label: &'a str,
is_fixable: bool,
) -> DisplayLine<'a> {
DisplayLine::Raw(DisplayRawLine::Annotation { DisplayLine::Raw(DisplayRawLine::Annotation {
annotation: Annotation { annotation: Annotation {
annotation_type: DisplayAnnotationType::from(level), annotation_type: DisplayAnnotationType::from(level),
id, id,
label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)), label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)),
is_fixable,
}, },
source_aligned: false, source_aligned: false,
continuation: false, continuation: false,
@ -1084,6 +1148,7 @@ fn format_footer<'a>(
annotation_type: DisplayAnnotationType::from(level), annotation_type: DisplayAnnotationType::from(level),
id, id,
label: format_label(Some(line), None), label: format_label(Some(line), None),
is_fixable: false,
}, },
source_aligned: true, source_aligned: true,
continuation: i != 0, continuation: i != 0,
@ -1472,6 +1537,7 @@ fn format_body<'m>(
annotation_type, annotation_type,
id: None, id: None,
label: format_label(annotation.label, None), label: format_label(annotation.label, None),
is_fixable: false,
}, },
range, range,
annotation_type: DisplayAnnotationType::from(annotation.level), annotation_type: DisplayAnnotationType::from(annotation.level),
@ -1511,6 +1577,7 @@ fn format_body<'m>(
annotation_type, annotation_type,
id: None, id: None,
label: vec![], label: vec![],
is_fixable: false,
}, },
range, range,
annotation_type: DisplayAnnotationType::from(annotation.level), annotation_type: DisplayAnnotationType::from(annotation.level),
@ -1580,6 +1647,7 @@ fn format_body<'m>(
annotation_type, annotation_type,
id: None, id: None,
label: format_label(annotation.label, None), label: format_label(annotation.label, None),
is_fixable: false,
}, },
range, range,
annotation_type: DisplayAnnotationType::from(annotation.level), annotation_type: DisplayAnnotationType::from(annotation.level),

View file

@ -22,6 +22,7 @@ pub struct Message<'a> {
pub(crate) title: &'a str, pub(crate) title: &'a str,
pub(crate) snippets: Vec<Snippet<'a>>, pub(crate) snippets: Vec<Snippet<'a>>,
pub(crate) footer: Vec<Message<'a>>, pub(crate) footer: Vec<Message<'a>>,
pub(crate) is_fixable: bool,
} }
impl<'a> Message<'a> { impl<'a> Message<'a> {
@ -49,6 +50,15 @@ impl<'a> Message<'a> {
self.footer.extend(footer); self.footer.extend(footer);
self self
} }
/// Whether or not the diagnostic for this message is fixable.
///
/// This is rendered as a `[*]` indicator after the `id` in an annotation header, if the
/// annotation also has `Level::None`.
pub fn is_fixable(mut self, yes: bool) -> Self {
self.is_fixable = yes;
self
}
} }
/// Structure containing the slice of text to be annotated and /// Structure containing the slice of text to be annotated and
@ -145,6 +155,7 @@ impl Level {
title, title,
snippets: vec![], snippets: vec![],
footer: vec![], footer: vec![],
is_fixable: false,
} }
} }

View file

@ -366,6 +366,16 @@ impl Diagnostic {
self.inner.secondary_code.as_ref() self.inner.secondary_code.as_ref()
} }
/// Returns the secondary code for the diagnostic if it exists, or the lint name otherwise.
///
/// This is a common pattern for Ruff diagnostics, which want to use the noqa code in general,
/// but fall back on the `invalid-syntax` identifier for syntax errors, which don't have
/// secondary codes.
pub fn secondary_code_or_id(&self) -> &str {
self.secondary_code()
.map_or_else(|| self.inner.id.as_str(), SecondaryCode::as_str)
}
/// Set the secondary code for this diagnostic. /// Set the secondary code for this diagnostic.
pub fn set_secondary_code(&mut self, code: SecondaryCode) { pub fn set_secondary_code(&mut self, code: SecondaryCode) {
Arc::make_mut(&mut self.inner).secondary_code = Some(code); Arc::make_mut(&mut self.inner).secondary_code = Some(code);

View file

@ -135,7 +135,7 @@ impl std::fmt::Display for DisplayDiagnostics<'_> {
.none(stylesheet.none); .none(stylesheet.none);
for diag in self.diagnostics { for diag in self.diagnostics {
let resolved = Resolved::new(self.resolver, diag); let resolved = Resolved::new(self.resolver, diag, self.config);
let renderable = resolved.to_renderable(self.config.context); let renderable = resolved.to_renderable(self.config.context);
for diag in renderable.diagnostics.iter() { for diag in renderable.diagnostics.iter() {
writeln!(f, "{}", renderer.render(diag.to_annotate()))?; writeln!(f, "{}", renderer.render(diag.to_annotate()))?;
@ -191,9 +191,13 @@ struct Resolved<'a> {
impl<'a> Resolved<'a> { impl<'a> Resolved<'a> {
/// Creates a new resolved set of diagnostics. /// Creates a new resolved set of diagnostics.
fn new(resolver: &'a dyn FileResolver, diag: &'a Diagnostic) -> Resolved<'a> { fn new(
resolver: &'a dyn FileResolver,
diag: &'a Diagnostic,
config: &DisplayDiagnosticConfig,
) -> Resolved<'a> {
let mut diagnostics = vec![]; let mut diagnostics = vec![];
diagnostics.push(ResolvedDiagnostic::from_diagnostic(resolver, diag)); diagnostics.push(ResolvedDiagnostic::from_diagnostic(resolver, config, diag));
for sub in &diag.inner.subs { for sub in &diag.inner.subs {
diagnostics.push(ResolvedDiagnostic::from_sub_diagnostic(resolver, sub)); diagnostics.push(ResolvedDiagnostic::from_sub_diagnostic(resolver, sub));
} }
@ -223,12 +227,14 @@ struct ResolvedDiagnostic<'a> {
id: Option<String>, id: Option<String>,
message: String, message: String,
annotations: Vec<ResolvedAnnotation<'a>>, annotations: Vec<ResolvedAnnotation<'a>>,
is_fixable: bool,
} }
impl<'a> ResolvedDiagnostic<'a> { impl<'a> ResolvedDiagnostic<'a> {
/// Resolve a single diagnostic. /// Resolve a single diagnostic.
fn from_diagnostic( fn from_diagnostic(
resolver: &'a dyn FileResolver, resolver: &'a dyn FileResolver,
config: &DisplayDiagnosticConfig,
diag: &'a Diagnostic, diag: &'a Diagnostic,
) -> ResolvedDiagnostic<'a> { ) -> ResolvedDiagnostic<'a> {
let annotations: Vec<_> = diag let annotations: Vec<_> = diag
@ -241,13 +247,35 @@ impl<'a> ResolvedDiagnostic<'a> {
ResolvedAnnotation::new(path, &diagnostic_source, ann) ResolvedAnnotation::new(path, &diagnostic_source, ann)
}) })
.collect(); .collect();
let id = Some(diag.inner.id.to_string());
let message = diag.inner.message.as_str().to_string(); let id = if config.hide_severity {
// Either the rule code alone (e.g. `F401`), or the lint id with a colon (e.g.
// `invalid-syntax:`). When Ruff gets real severities, we should put the colon back in
// `DisplaySet::format_annotation` for both cases, but this is a small hack to improve
// the formatting of syntax errors for now. This should also be kept consistent with the
// concise formatting.
Some(diag.secondary_code().map_or_else(
|| format!("{id}:", id = diag.inner.id),
|code| code.to_string(),
))
} else {
Some(diag.inner.id.to_string())
};
let level = if config.hide_severity {
AnnotateLevel::None
} else {
diag.inner.severity.to_annotate()
};
ResolvedDiagnostic { ResolvedDiagnostic {
level: diag.inner.severity.to_annotate(), level,
id, id,
message, message: diag.inner.message.as_str().to_string(),
annotations, annotations,
is_fixable: diag
.fix()
.is_some_and(|fix| fix.applies(config.fix_applicability)),
} }
} }
@ -271,6 +299,7 @@ impl<'a> ResolvedDiagnostic<'a> {
id: None, id: None,
message: diag.inner.message.as_str().to_string(), message: diag.inner.message.as_str().to_string(),
annotations, annotations,
is_fixable: false,
} }
} }
@ -338,6 +367,7 @@ impl<'a> ResolvedDiagnostic<'a> {
id: self.id.as_deref(), id: self.id.as_deref(),
message: &self.message, message: &self.message,
snippets_by_input, snippets_by_input,
is_fixable: self.is_fixable,
} }
} }
} }
@ -436,6 +466,10 @@ struct RenderableDiagnostic<'r> {
/// should be from the same file, and none of the snippets inside of a /// should be from the same file, and none of the snippets inside of a
/// collection should overlap with one another or be directly adjacent. /// collection should overlap with one another or be directly adjacent.
snippets_by_input: Vec<RenderableSnippets<'r>>, snippets_by_input: Vec<RenderableSnippets<'r>>,
/// Whether or not the diagnostic is fixable.
///
/// This is rendered as a `[*]` indicator after the diagnostic ID.
is_fixable: bool,
} }
impl RenderableDiagnostic<'_> { impl RenderableDiagnostic<'_> {
@ -448,7 +482,7 @@ impl RenderableDiagnostic<'_> {
.iter() .iter()
.map(|snippet| snippet.to_annotate(path)) .map(|snippet| snippet.to_annotate(path))
}); });
let mut message = self.level.title(self.message); let mut message = self.level.title(self.message).is_fixable(self.is_fixable);
if let Some(id) = self.id { if let Some(id) = self.id {
message = message.id(id); message = message.id(id);
} }
@ -2850,10 +2884,10 @@ if call(foo
env.format(format); env.format(format);
let diagnostics = vec![ let diagnostics = vec![
env.invalid_syntax("SyntaxError: Expected one or more symbol names after import") env.invalid_syntax("Expected one or more symbol names after import")
.primary("syntax_errors.py", "1:14", "1:15", "") .primary("syntax_errors.py", "1:14", "1:15", "")
.build(), .build(),
env.invalid_syntax("SyntaxError: Expected ')', found newline") env.invalid_syntax("Expected ')', found newline")
.primary("syntax_errors.py", "3:11", "3:12", "") .primary("syntax_errors.py", "3:11", "3:12", "")
.build(), .build(),
]; ];

View file

@ -50,10 +50,8 @@ impl AzureRenderer<'_> {
} }
writeln!( writeln!(
f, f,
"{code}]{body}", "code={code};]{body}",
code = diag code = diag.secondary_code_or_id(),
.secondary_code()
.map_or_else(String::new, |code| format!("code={code};")),
body = diag.body(), body = diag.body(),
)?; )?;
} }

View file

@ -69,6 +69,12 @@ impl<'a> ConciseRenderer<'a> {
"{code} ", "{code} ",
code = fmt_styled(code, stylesheet.secondary_code) code = fmt_styled(code, stylesheet.secondary_code)
)?; )?;
} else {
write!(
f,
"{id}: ",
id = fmt_styled(diag.inner.id.as_str(), stylesheet.secondary_code)
)?;
} }
if self.config.show_fix_status { if self.config.show_fix_status {
if let Some(fix) = diag.fix() { if let Some(fix) = diag.fix() {
@ -156,8 +162,8 @@ mod tests {
env.show_fix_status(true); env.show_fix_status(true);
env.fix_applicability(Applicability::DisplayOnly); env.fix_applicability(Applicability::DisplayOnly);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r" insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
syntax_errors.py:1:15: SyntaxError: Expected one or more symbol names after import syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
syntax_errors.py:3:12: SyntaxError: Expected ')', found newline syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
"); ");
} }
@ -165,8 +171,8 @@ mod tests {
fn syntax_errors() { fn syntax_errors() {
let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Concise); let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Concise);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r" insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
syntax_errors.py:1:15: error[invalid-syntax] SyntaxError: Expected one or more symbol names after import syntax_errors.py:1:15: error[invalid-syntax] Expected one or more symbol names after import
syntax_errors.py:3:12: error[invalid-syntax] SyntaxError: Expected ')', found newline syntax_errors.py:3:12: error[invalid-syntax] Expected ')', found newline
"); ");
} }

View file

@ -1,5 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use ruff_diagnostics::Applicability;
use crate::diagnostic::{ use crate::diagnostic::{
DiagnosticFormat, Severity, DiagnosticFormat, Severity,
render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics}, render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics},
@ -42,7 +44,7 @@ mod tests {
fn syntax_errors() { fn syntax_errors() {
let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Full); let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Full);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r" insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
error[invalid-syntax]: SyntaxError: Expected one or more symbol names after import error[invalid-syntax]: Expected one or more symbol names after import
--> syntax_errors.py:1:15 --> syntax_errors.py:1:15
| |
1 | from os import 1 | from os import
@ -51,7 +53,71 @@ mod tests {
3 | if call(foo 3 | if call(foo
| |
error[invalid-syntax]: SyntaxError: Expected ')', found newline error[invalid-syntax]: Expected ')', found newline
--> syntax_errors.py:3:12
|
1 | from os import
2 |
3 | if call(foo
| ^
4 | def bar():
5 | pass
|
");
}
#[test]
fn hide_severity_output() {
let (mut env, diagnostics) = create_diagnostics(DiagnosticFormat::Full);
env.hide_severity(true);
env.fix_applicability(Applicability::DisplayOnly);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r#"
F401 [*] `os` imported but unused
--> fib.py:1:8
|
1 | import os
| ^^
|
help: Remove unused import: `os`
F841 [*] Local variable `x` is assigned to but never used
--> fib.py:6:5
|
4 | def fibonacci(n):
5 | """Compute the nth number in the Fibonacci sequence."""
6 | x = 1
| ^
7 | if n == 0:
8 | return 0
|
help: Remove assignment to unused variable `x`
F821 Undefined name `a`
--> undef.py:1:4
|
1 | if a == 1: pass
| ^
|
"#);
}
#[test]
fn hide_severity_syntax_errors() {
let (mut env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Full);
env.hide_severity(true);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
invalid-syntax: Expected one or more symbol names after import
--> syntax_errors.py:1:15
|
1 | from os import
| ^
2 |
3 | if call(foo
|
invalid-syntax: Expected ')', found newline
--> syntax_errors.py:3:12 --> syntax_errors.py:3:12
| |
1 | from os import 1 | from os import

View file

@ -6,7 +6,7 @@ use ruff_notebook::NotebookIndex;
use ruff_source_file::{LineColumn, OneIndexed}; use ruff_source_file::{LineColumn, OneIndexed};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::diagnostic::{Diagnostic, DiagnosticSource, DisplayDiagnosticConfig, SecondaryCode}; use crate::diagnostic::{Diagnostic, DiagnosticSource, DisplayDiagnosticConfig};
use super::FileResolver; use super::FileResolver;
@ -99,7 +99,7 @@ pub(super) fn diagnostic_to_json<'a>(
// In preview, the locations and filename can be optional. // In preview, the locations and filename can be optional.
if config.preview { if config.preview {
JsonDiagnostic { JsonDiagnostic {
code: diagnostic.secondary_code(), code: diagnostic.secondary_code_or_id(),
url: diagnostic.to_ruff_url(), url: diagnostic.to_ruff_url(),
message: diagnostic.body(), message: diagnostic.body(),
fix, fix,
@ -111,7 +111,7 @@ pub(super) fn diagnostic_to_json<'a>(
} }
} else { } else {
JsonDiagnostic { JsonDiagnostic {
code: diagnostic.secondary_code(), code: diagnostic.secondary_code_or_id(),
url: diagnostic.to_ruff_url(), url: diagnostic.to_ruff_url(),
message: diagnostic.body(), message: diagnostic.body(),
fix, fix,
@ -221,7 +221,7 @@ impl Serialize for ExpandedEdits<'_> {
#[derive(Serialize)] #[derive(Serialize)]
pub(crate) struct JsonDiagnostic<'a> { pub(crate) struct JsonDiagnostic<'a> {
cell: Option<OneIndexed>, cell: Option<OneIndexed>,
code: Option<&'a SecondaryCode>, code: &'a str,
end_location: Option<JsonLocation>, end_location: Option<JsonLocation>,
filename: Option<&'a str>, filename: Option<&'a str>,
fix: Option<JsonFix<'a>>, fix: Option<JsonFix<'a>>,
@ -302,7 +302,7 @@ mod tests {
[ [
{ {
"cell": null, "cell": null,
"code": null, "code": "test-diagnostic",
"end_location": { "end_location": {
"column": 1, "column": 1,
"row": 1 "row": 1
@ -336,7 +336,7 @@ mod tests {
[ [
{ {
"cell": null, "cell": null,
"code": null, "code": "test-diagnostic",
"end_location": null, "end_location": null,
"filename": null, "filename": null,
"fix": null, "fix": null,

View file

@ -2,5 +2,5 @@
source: crates/ruff_db/src/diagnostic/render/azure.rs source: crates/ruff_db/src/diagnostic/render/azure.rs
expression: env.render_diagnostics(&diagnostics) expression: env.render_diagnostics(&diagnostics)
--- ---
##vso[task.logissue type=error;sourcepath=syntax_errors.py;linenumber=1;columnnumber=15;]SyntaxError: Expected one or more symbol names after import ##vso[task.logissue type=error;sourcepath=syntax_errors.py;linenumber=1;columnnumber=15;code=invalid-syntax;]Expected one or more symbol names after import
##vso[task.logissue type=error;sourcepath=syntax_errors.py;linenumber=3;columnnumber=12;]SyntaxError: Expected ')', found newline ##vso[task.logissue type=error;sourcepath=syntax_errors.py;linenumber=3;columnnumber=12;code=invalid-syntax;]Expected ')', found newline

View file

@ -5,7 +5,7 @@ expression: env.render_diagnostics(&diagnostics)
[ [
{ {
"cell": null, "cell": null,
"code": null, "code": "invalid-syntax",
"end_location": { "end_location": {
"column": 1, "column": 1,
"row": 2 "row": 2
@ -16,13 +16,13 @@ expression: env.render_diagnostics(&diagnostics)
"column": 15, "column": 15,
"row": 1 "row": 1
}, },
"message": "SyntaxError: Expected one or more symbol names after import", "message": "Expected one or more symbol names after import",
"noqa_row": null, "noqa_row": null,
"url": null "url": null
}, },
{ {
"cell": null, "cell": null,
"code": null, "code": "invalid-syntax",
"end_location": { "end_location": {
"column": 1, "column": 1,
"row": 4 "row": 4
@ -33,7 +33,7 @@ expression: env.render_diagnostics(&diagnostics)
"column": 12, "column": 12,
"row": 3 "row": 3
}, },
"message": "SyntaxError: Expected ')', found newline", "message": "Expected ')', found newline",
"noqa_row": null, "noqa_row": null,
"url": null "url": null
} }

View file

@ -2,5 +2,5 @@
source: crates/ruff_db/src/diagnostic/render/json_lines.rs source: crates/ruff_db/src/diagnostic/render/json_lines.rs
expression: env.render_diagnostics(&diagnostics) expression: env.render_diagnostics(&diagnostics)
--- ---
{"cell":null,"code":null,"end_location":{"column":1,"row":2},"filename":"syntax_errors.py","fix":null,"location":{"column":15,"row":1},"message":"SyntaxError: Expected one or more symbol names after import","noqa_row":null,"url":null} {"cell":null,"code":"invalid-syntax","end_location":{"column":1,"row":2},"filename":"syntax_errors.py","fix":null,"location":{"column":15,"row":1},"message":"Expected one or more symbol names after import","noqa_row":null,"url":null}
{"cell":null,"code":null,"end_location":{"column":1,"row":4},"filename":"syntax_errors.py","fix":null,"location":{"column":12,"row":3},"message":"SyntaxError: Expected ')', found newline","noqa_row":null,"url":null} {"cell":null,"code":"invalid-syntax","end_location":{"column":1,"row":4},"filename":"syntax_errors.py","fix":null,"location":{"column":12,"row":3},"message":"Expected ')', found newline","noqa_row":null,"url":null}

View file

@ -6,10 +6,10 @@ expression: env.render_diagnostics(&diagnostics)
<testsuites name="ruff" tests="2" failures="2" errors="0"> <testsuites name="ruff" tests="2" failures="2" errors="0">
<testsuite name="syntax_errors.py" tests="2" disabled="0" errors="0" failures="2" package="org.ruff"> <testsuite name="syntax_errors.py" tests="2" disabled="0" errors="0" failures="2" package="org.ruff">
<testcase name="org.ruff.invalid-syntax" classname="syntax_errors" line="1" column="15"> <testcase name="org.ruff.invalid-syntax" classname="syntax_errors" line="1" column="15">
<failure message="SyntaxError: Expected one or more symbol names after import">line 1, col 15, SyntaxError: Expected one or more symbol names after import</failure> <failure message="Expected one or more symbol names after import">line 1, col 15, Expected one or more symbol names after import</failure>
</testcase> </testcase>
<testcase name="org.ruff.invalid-syntax" classname="syntax_errors" line="3" column="12"> <testcase name="org.ruff.invalid-syntax" classname="syntax_errors" line="3" column="12">
<failure message="SyntaxError: Expected &apos;)&apos;, found newline">line 3, col 12, SyntaxError: Expected &apos;)&apos;, found newline</failure> <failure message="Expected &apos;)&apos;, found newline">line 3, col 12, Expected &apos;)&apos;, found newline</failure>
</testcase> </testcase>
</testsuite> </testsuite>
</testsuites> </testsuites>

View file

@ -2,5 +2,5 @@
source: crates/ruff_db/src/diagnostic/render/pylint.rs source: crates/ruff_db/src/diagnostic/render/pylint.rs
expression: env.render_diagnostics(&diagnostics) expression: env.render_diagnostics(&diagnostics)
--- ---
syntax_errors.py:1: [invalid-syntax] SyntaxError: Expected one or more symbol names after import syntax_errors.py:1: [invalid-syntax] Expected one or more symbol names after import
syntax_errors.py:3: [invalid-syntax] SyntaxError: Expected ')', found newline syntax_errors.py:3: [invalid-syntax] Expected ')', found newline

View file

@ -21,7 +21,7 @@ expression: env.render_diagnostics(&diagnostics)
} }
} }
}, },
"message": "SyntaxError: Expected one or more symbol names after import" "message": "Expected one or more symbol names after import"
}, },
{ {
"code": { "code": {
@ -40,7 +40,7 @@ expression: env.render_diagnostics(&diagnostics)
} }
} }
}, },
"message": "SyntaxError: Expected ')', found newline" "message": "Expected ')', found newline"
} }
], ],
"severity": "WARNING", "severity": "WARNING",

View file

@ -33,10 +33,8 @@ impl Emitter for GithubEmitter {
write!( write!(
writer, writer,
"::error title=Ruff{code},file={file},line={row},col={column},endLine={end_row},endColumn={end_column}::", "::error title=Ruff ({code}),file={file},line={row},col={column},endLine={end_row},endColumn={end_column}::",
code = diagnostic code = diagnostic.secondary_code_or_id(),
.secondary_code()
.map_or_else(String::new, |code| format!(" ({code})")),
file = filename, file = filename,
row = source_location.line, row = source_location.line,
column = source_location.column, column = source_location.column,
@ -54,6 +52,8 @@ impl Emitter for GithubEmitter {
if let Some(code) = diagnostic.secondary_code() { if let Some(code) = diagnostic.secondary_code() {
write!(writer, " {code}")?; write!(writer, " {code}")?;
} else {
write!(writer, " {id}:", id = diagnostic.id())?;
} }
writeln!(writer, " {}", diagnostic.body())?; writeln!(writer, " {}", diagnostic.body())?;

View file

@ -33,8 +33,7 @@ mod text;
/// Creates a `Diagnostic` from a syntax error, with the format expected by Ruff. /// Creates a `Diagnostic` from a syntax error, with the format expected by Ruff.
/// ///
/// This is almost identical to `ruff_db::diagnostic::create_syntax_error_diagnostic`, except the /// This is almost identical to `ruff_db::diagnostic::create_syntax_error_diagnostic`, except the
/// `message` is stored as the primary diagnostic message instead of on the primary annotation, and /// `message` is stored as the primary diagnostic message instead of on the primary annotation.
/// `SyntaxError: ` is prepended to the message.
/// ///
/// TODO(brent) These should be unified at some point, but we keep them separate for now to avoid a /// TODO(brent) These should be unified at some point, but we keep them separate for now to avoid a
/// ton of snapshot changes while combining ruff's diagnostic type with `Diagnostic`. /// ton of snapshot changes while combining ruff's diagnostic type with `Diagnostic`.
@ -43,11 +42,7 @@ pub fn create_syntax_error_diagnostic(
message: impl std::fmt::Display, message: impl std::fmt::Display,
range: impl Ranged, range: impl Ranged,
) -> Diagnostic { ) -> Diagnostic {
let mut diag = Diagnostic::new( let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, message);
DiagnosticId::InvalidSyntax,
Severity::Error,
format_args!("SyntaxError: {message}"),
);
let span = span.into().with_range(range.range()); let span = span.into().with_range(range.range());
diag.annotate(Annotation::primary(span)); diag.annotate(Annotation::primary(span));
diag diag

View file

@ -27,7 +27,10 @@ impl Emitter for SarifEmitter {
.map(SarifResult::from_message) .map(SarifResult::from_message)
.collect::<Result<Vec<_>>>()?; .collect::<Result<Vec<_>>>()?;
let unique_rules: HashSet<_> = results.iter().filter_map(|result| result.code).collect(); let unique_rules: HashSet<_> = results
.iter()
.filter_map(|result| result.code.as_secondary_code())
.collect();
let mut rules: Vec<SarifRule> = unique_rules.into_iter().map(SarifRule::from).collect(); let mut rules: Vec<SarifRule> = unique_rules.into_iter().map(SarifRule::from).collect();
rules.sort_by(|a, b| a.code.cmp(b.code)); rules.sort_by(|a, b| a.code.cmp(b.code));
@ -109,9 +112,40 @@ impl Serialize for SarifRule<'_> {
} }
} }
#[derive(Debug)]
enum RuleCode<'a> {
SecondaryCode(&'a SecondaryCode),
LintId(&'a str),
}
impl RuleCode<'_> {
fn as_secondary_code(&self) -> Option<&SecondaryCode> {
match self {
RuleCode::SecondaryCode(code) => Some(code),
RuleCode::LintId(_) => None,
}
}
fn as_str(&self) -> &str {
match self {
RuleCode::SecondaryCode(code) => code.as_str(),
RuleCode::LintId(id) => id,
}
}
}
impl<'a> From<&'a Diagnostic> for RuleCode<'a> {
fn from(code: &'a Diagnostic) -> Self {
match code.secondary_code() {
Some(diagnostic) => Self::SecondaryCode(diagnostic),
None => Self::LintId(code.id().as_str()),
}
}
}
#[derive(Debug)] #[derive(Debug)]
struct SarifResult<'a> { struct SarifResult<'a> {
code: Option<&'a SecondaryCode>, code: RuleCode<'a>,
level: String, level: String,
message: String, message: String,
uri: String, uri: String,
@ -128,7 +162,7 @@ impl<'a> SarifResult<'a> {
let end_location = message.expect_ruff_end_location(); let end_location = message.expect_ruff_end_location();
let path = normalize_path(&*message.expect_ruff_filename()); let path = normalize_path(&*message.expect_ruff_filename());
Ok(Self { Ok(Self {
code: message.secondary_code(), code: RuleCode::from(message),
level: "error".to_string(), level: "error".to_string(),
message: message.body().to_string(), message: message.body().to_string(),
uri: url::Url::from_file_path(&path) uri: url::Url::from_file_path(&path)
@ -148,7 +182,7 @@ impl<'a> SarifResult<'a> {
let end_location = message.expect_ruff_end_location(); let end_location = message.expect_ruff_end_location();
let path = normalize_path(&*message.expect_ruff_filename()); let path = normalize_path(&*message.expect_ruff_filename());
Ok(Self { Ok(Self {
code: message.secondary_code(), code: RuleCode::from(message),
level: "error".to_string(), level: "error".to_string(),
message: message.body().to_string(), message: message.body().to_string(),
uri: path.display().to_string(), uri: path.display().to_string(),
@ -183,7 +217,7 @@ impl Serialize for SarifResult<'_> {
} }
} }
}], }],
"ruleId": self.code, "ruleId": self.code.as_str(),
}) })
.serialize(serializer) .serialize(serializer)
} }

View file

@ -1,7 +1,6 @@
--- ---
source: crates/ruff_linter/src/message/github.rs source: crates/ruff_linter/src/message/github.rs
expression: content expression: content
snapshot_kind: text
--- ---
::error title=Ruff,file=syntax_errors.py,line=1,col=15,endLine=2,endColumn=1::syntax_errors.py:1:15: SyntaxError: Expected one or more symbol names after import ::error title=Ruff (invalid-syntax),file=syntax_errors.py,line=1,col=15,endLine=2,endColumn=1::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
::error title=Ruff,file=syntax_errors.py,line=3,col=12,endLine=4,endColumn=1::syntax_errors.py:3:12: SyntaxError: Expected ')', found newline ::error title=Ruff (invalid-syntax),file=syntax_errors.py,line=3,col=12,endLine=4,endColumn=1::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline

View file

@ -1,8 +1,7 @@
--- ---
source: crates/ruff_linter/src/message/grouped.rs source: crates/ruff_linter/src/message/grouped.rs
expression: content expression: content
snapshot_kind: text
--- ---
syntax_errors.py: syntax_errors.py:
1:15 SyntaxError: Expected one or more symbol names after import 1:15 invalid-syntax: Expected one or more symbol names after import
3:12 SyntaxError: Expected ')', found newline 3:12 invalid-syntax: Expected ')', found newline

View file

@ -2,7 +2,7 @@
source: crates/ruff_linter/src/message/text.rs source: crates/ruff_linter/src/message/text.rs
expression: content expression: content
--- ---
syntax_errors.py:1:15: SyntaxError: Expected one or more symbol names after import syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
| |
1 | from os import 1 | from os import
| ^ | ^
@ -11,7 +11,7 @@ syntax_errors.py:1:15: SyntaxError: Expected one or more symbol names after impo
4 | def bar(): 4 | def bar():
| |
syntax_errors.py:3:12: SyntaxError: Expected ')', found newline syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
| |
1 | from os import 1 | from os import
2 | 2 |

View file

@ -154,7 +154,12 @@ impl Display for RuleCodeAndBody<'_> {
body = self.message.body(), body = self.message.body(),
) )
} else { } else {
f.write_str(self.message.body()) write!(
f,
"{code}: {body}",
code = self.message.id().as_str().red().bold(),
body = self.message.body(),
)
} }
} }
} }

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
--- ---
COM81_syntax_error.py:3:5: SyntaxError: Starred expression cannot be used here COM81_syntax_error.py:3:5: invalid-syntax: Starred expression cannot be used here
| |
1 | # Check for `flake8-commas` violation for a file containing syntax errors. 1 | # Check for `flake8-commas` violation for a file containing syntax errors.
2 | ( 2 | (
@ -10,7 +10,7 @@ COM81_syntax_error.py:3:5: SyntaxError: Starred expression cannot be used here
4 | ) 4 | )
| |
COM81_syntax_error.py:6:9: SyntaxError: Type parameter list cannot be empty COM81_syntax_error.py:6:9: invalid-syntax: Type parameter list cannot be empty
| |
4 | ) 4 | )
5 | 5 |

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
--- ---
COM81_syntax_error.py:3:5: SyntaxError: Starred expression cannot be used here COM81_syntax_error.py:3:5: invalid-syntax: Starred expression cannot be used here
| |
1 | # Check for `flake8-commas` violation for a file containing syntax errors. 1 | # Check for `flake8-commas` violation for a file containing syntax errors.
2 | ( 2 | (
@ -10,7 +10,7 @@ COM81_syntax_error.py:3:5: SyntaxError: Starred expression cannot be used here
4 | ) 4 | )
| |
COM81_syntax_error.py:6:9: SyntaxError: Type parameter list cannot be empty COM81_syntax_error.py:6:9: invalid-syntax: Type parameter list cannot be empty
| |
4 | ) 4 | )
5 | 5 |

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/rules/flake8_implicit_str_concat/mod.rs source: crates/ruff_linter/src/rules/flake8_implicit_str_concat/mod.rs
--- ---
ISC_syntax_error.py:2:5: SyntaxError: missing closing quote in string literal ISC_syntax_error.py:2:5: invalid-syntax: missing closing quote in string literal
| |
1 | # The lexer doesn't emit a string token if it's unterminated 1 | # The lexer doesn't emit a string token if it's unterminated
2 | "a" "b 2 | "a" "b
@ -10,7 +10,7 @@ ISC_syntax_error.py:2:5: SyntaxError: missing closing quote in string literal
4 | "a" """b 4 | "a" """b
| |
ISC_syntax_error.py:2:7: SyntaxError: Expected a statement ISC_syntax_error.py:2:7: invalid-syntax: Expected a statement
| |
1 | # The lexer doesn't emit a string token if it's unterminated 1 | # The lexer doesn't emit a string token if it's unterminated
2 | "a" "b 2 | "a" "b
@ -31,7 +31,7 @@ ISC_syntax_error.py:3:1: ISC001 Implicitly concatenated string literals on one l
| |
= help: Combine string literals = help: Combine string literals
ISC_syntax_error.py:3:9: SyntaxError: missing closing quote in string literal ISC_syntax_error.py:3:9: invalid-syntax: missing closing quote in string literal
| |
1 | # The lexer doesn't emit a string token if it's unterminated 1 | # The lexer doesn't emit a string token if it's unterminated
2 | "a" "b 2 | "a" "b
@ -41,7 +41,7 @@ ISC_syntax_error.py:3:9: SyntaxError: missing closing quote in string literal
5 | c""" "d 5 | c""" "d
| |
ISC_syntax_error.py:3:11: SyntaxError: Expected a statement ISC_syntax_error.py:3:11: invalid-syntax: Expected a statement
| |
1 | # The lexer doesn't emit a string token if it's unterminated 1 | # The lexer doesn't emit a string token if it's unterminated
2 | "a" "b 2 | "a" "b
@ -63,7 +63,7 @@ ISC_syntax_error.py:4:1: ISC001 Implicitly concatenated string literals on one l
| |
= help: Combine string literals = help: Combine string literals
ISC_syntax_error.py:5:6: SyntaxError: missing closing quote in string literal ISC_syntax_error.py:5:6: invalid-syntax: missing closing quote in string literal
| |
3 | "a" "b" "c 3 | "a" "b" "c
4 | "a" """b 4 | "a" """b
@ -73,7 +73,7 @@ ISC_syntax_error.py:5:6: SyntaxError: missing closing quote in string literal
7 | # For f-strings, the `FStringRanges` won't contain the range for 7 | # For f-strings, the `FStringRanges` won't contain the range for
| |
ISC_syntax_error.py:5:8: SyntaxError: Expected a statement ISC_syntax_error.py:5:8: invalid-syntax: Expected a statement
| |
3 | "a" "b" "c 3 | "a" "b" "c
4 | "a" """b 4 | "a" """b
@ -84,7 +84,7 @@ ISC_syntax_error.py:5:8: SyntaxError: Expected a statement
8 | # unterminated f-strings. 8 | # unterminated f-strings.
| |
ISC_syntax_error.py:9:8: SyntaxError: f-string: unterminated string ISC_syntax_error.py:9:8: invalid-syntax: f-string: unterminated string
| |
7 | # For f-strings, the `FStringRanges` won't contain the range for 7 | # For f-strings, the `FStringRanges` won't contain the range for
8 | # unterminated f-strings. 8 | # unterminated f-strings.
@ -94,7 +94,7 @@ ISC_syntax_error.py:9:8: SyntaxError: f-string: unterminated string
11 | f"a" f"""b 11 | f"a" f"""b
| |
ISC_syntax_error.py:9:9: SyntaxError: Expected FStringEnd, found newline ISC_syntax_error.py:9:9: invalid-syntax: Expected FStringEnd, found newline
| |
7 | # For f-strings, the `FStringRanges` won't contain the range for 7 | # For f-strings, the `FStringRanges` won't contain the range for
8 | # unterminated f-strings. 8 | # unterminated f-strings.
@ -116,7 +116,7 @@ ISC_syntax_error.py:10:1: ISC001 Implicitly concatenated string literals on one
| |
= help: Combine string literals = help: Combine string literals
ISC_syntax_error.py:10:13: SyntaxError: f-string: unterminated string ISC_syntax_error.py:10:13: invalid-syntax: f-string: unterminated string
| |
8 | # unterminated f-strings. 8 | # unterminated f-strings.
9 | f"a" f"b 9 | f"a" f"b
@ -126,7 +126,7 @@ ISC_syntax_error.py:10:13: SyntaxError: f-string: unterminated string
12 | c""" f"d {e 12 | c""" f"d {e
| |
ISC_syntax_error.py:10:14: SyntaxError: Expected FStringEnd, found newline ISC_syntax_error.py:10:14: invalid-syntax: Expected FStringEnd, found newline
| |
8 | # unterminated f-strings. 8 | # unterminated f-strings.
9 | f"a" f"b 9 | f"a" f"b
@ -148,7 +148,7 @@ ISC_syntax_error.py:11:1: ISC001 Implicitly concatenated string literals on one
| |
= help: Combine string literals = help: Combine string literals
ISC_syntax_error.py:16:5: SyntaxError: missing closing quote in string literal ISC_syntax_error.py:16:5: invalid-syntax: missing closing quote in string literal
| |
14 | ( 14 | (
15 | "a" 15 | "a"
@ -158,7 +158,7 @@ ISC_syntax_error.py:16:5: SyntaxError: missing closing quote in string literal
18 | "d" 18 | "d"
| |
ISC_syntax_error.py:26:9: SyntaxError: f-string: unterminated triple-quoted string ISC_syntax_error.py:26:9: invalid-syntax: f-string: unterminated triple-quoted string
| |
24 | ( 24 | (
25 | """abc""" 25 | """abc"""
@ -170,14 +170,14 @@ ISC_syntax_error.py:26:9: SyntaxError: f-string: unterminated triple-quoted stri
| |__^ | |__^
| |
ISC_syntax_error.py:30:1: SyntaxError: unexpected EOF while parsing ISC_syntax_error.py:30:1: invalid-syntax: unexpected EOF while parsing
| |
28 | "i" "j" 28 | "i" "j"
29 | ) 29 | )
| ^ | ^
| |
ISC_syntax_error.py:30:1: SyntaxError: f-string: unterminated string ISC_syntax_error.py:30:1: invalid-syntax: f-string: unterminated string
| |
28 | "i" "j" 28 | "i" "j"
29 | ) 29 | )

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/rules/flake8_implicit_str_concat/mod.rs source: crates/ruff_linter/src/rules/flake8_implicit_str_concat/mod.rs
--- ---
ISC_syntax_error.py:2:5: SyntaxError: missing closing quote in string literal ISC_syntax_error.py:2:5: invalid-syntax: missing closing quote in string literal
| |
1 | # The lexer doesn't emit a string token if it's unterminated 1 | # The lexer doesn't emit a string token if it's unterminated
2 | "a" "b 2 | "a" "b
@ -10,7 +10,7 @@ ISC_syntax_error.py:2:5: SyntaxError: missing closing quote in string literal
4 | "a" """b 4 | "a" """b
| |
ISC_syntax_error.py:2:7: SyntaxError: Expected a statement ISC_syntax_error.py:2:7: invalid-syntax: Expected a statement
| |
1 | # The lexer doesn't emit a string token if it's unterminated 1 | # The lexer doesn't emit a string token if it's unterminated
2 | "a" "b 2 | "a" "b
@ -20,7 +20,7 @@ ISC_syntax_error.py:2:7: SyntaxError: Expected a statement
5 | c""" "d 5 | c""" "d
| |
ISC_syntax_error.py:3:9: SyntaxError: missing closing quote in string literal ISC_syntax_error.py:3:9: invalid-syntax: missing closing quote in string literal
| |
1 | # The lexer doesn't emit a string token if it's unterminated 1 | # The lexer doesn't emit a string token if it's unterminated
2 | "a" "b 2 | "a" "b
@ -30,7 +30,7 @@ ISC_syntax_error.py:3:9: SyntaxError: missing closing quote in string literal
5 | c""" "d 5 | c""" "d
| |
ISC_syntax_error.py:3:11: SyntaxError: Expected a statement ISC_syntax_error.py:3:11: invalid-syntax: Expected a statement
| |
1 | # The lexer doesn't emit a string token if it's unterminated 1 | # The lexer doesn't emit a string token if it's unterminated
2 | "a" "b 2 | "a" "b
@ -40,7 +40,7 @@ ISC_syntax_error.py:3:11: SyntaxError: Expected a statement
5 | c""" "d 5 | c""" "d
| |
ISC_syntax_error.py:5:6: SyntaxError: missing closing quote in string literal ISC_syntax_error.py:5:6: invalid-syntax: missing closing quote in string literal
| |
3 | "a" "b" "c 3 | "a" "b" "c
4 | "a" """b 4 | "a" """b
@ -50,7 +50,7 @@ ISC_syntax_error.py:5:6: SyntaxError: missing closing quote in string literal
7 | # For f-strings, the `FStringRanges` won't contain the range for 7 | # For f-strings, the `FStringRanges` won't contain the range for
| |
ISC_syntax_error.py:5:8: SyntaxError: Expected a statement ISC_syntax_error.py:5:8: invalid-syntax: Expected a statement
| |
3 | "a" "b" "c 3 | "a" "b" "c
4 | "a" """b 4 | "a" """b
@ -61,7 +61,7 @@ ISC_syntax_error.py:5:8: SyntaxError: Expected a statement
8 | # unterminated f-strings. 8 | # unterminated f-strings.
| |
ISC_syntax_error.py:9:8: SyntaxError: f-string: unterminated string ISC_syntax_error.py:9:8: invalid-syntax: f-string: unterminated string
| |
7 | # For f-strings, the `FStringRanges` won't contain the range for 7 | # For f-strings, the `FStringRanges` won't contain the range for
8 | # unterminated f-strings. 8 | # unterminated f-strings.
@ -71,7 +71,7 @@ ISC_syntax_error.py:9:8: SyntaxError: f-string: unterminated string
11 | f"a" f"""b 11 | f"a" f"""b
| |
ISC_syntax_error.py:9:9: SyntaxError: Expected FStringEnd, found newline ISC_syntax_error.py:9:9: invalid-syntax: Expected FStringEnd, found newline
| |
7 | # For f-strings, the `FStringRanges` won't contain the range for 7 | # For f-strings, the `FStringRanges` won't contain the range for
8 | # unterminated f-strings. 8 | # unterminated f-strings.
@ -82,7 +82,7 @@ ISC_syntax_error.py:9:9: SyntaxError: Expected FStringEnd, found newline
12 | c""" f"d {e 12 | c""" f"d {e
| |
ISC_syntax_error.py:10:13: SyntaxError: f-string: unterminated string ISC_syntax_error.py:10:13: invalid-syntax: f-string: unterminated string
| |
8 | # unterminated f-strings. 8 | # unterminated f-strings.
9 | f"a" f"b 9 | f"a" f"b
@ -92,7 +92,7 @@ ISC_syntax_error.py:10:13: SyntaxError: f-string: unterminated string
12 | c""" f"d {e 12 | c""" f"d {e
| |
ISC_syntax_error.py:10:14: SyntaxError: Expected FStringEnd, found newline ISC_syntax_error.py:10:14: invalid-syntax: Expected FStringEnd, found newline
| |
8 | # unterminated f-strings. 8 | # unterminated f-strings.
9 | f"a" f"b 9 | f"a" f"b
@ -102,7 +102,7 @@ ISC_syntax_error.py:10:14: SyntaxError: Expected FStringEnd, found newline
12 | c""" f"d {e 12 | c""" f"d {e
| |
ISC_syntax_error.py:16:5: SyntaxError: missing closing quote in string literal ISC_syntax_error.py:16:5: invalid-syntax: missing closing quote in string literal
| |
14 | ( 14 | (
15 | "a" 15 | "a"
@ -112,7 +112,7 @@ ISC_syntax_error.py:16:5: SyntaxError: missing closing quote in string literal
18 | "d" 18 | "d"
| |
ISC_syntax_error.py:26:9: SyntaxError: f-string: unterminated triple-quoted string ISC_syntax_error.py:26:9: invalid-syntax: f-string: unterminated triple-quoted string
| |
24 | ( 24 | (
25 | """abc""" 25 | """abc"""
@ -124,14 +124,14 @@ ISC_syntax_error.py:26:9: SyntaxError: f-string: unterminated triple-quoted stri
| |__^ | |__^
| |
ISC_syntax_error.py:30:1: SyntaxError: unexpected EOF while parsing ISC_syntax_error.py:30:1: invalid-syntax: unexpected EOF while parsing
| |
28 | "i" "j" 28 | "i" "j"
29 | ) 29 | )
| ^ | ^
| |
ISC_syntax_error.py:30:1: SyntaxError: f-string: unterminated string ISC_syntax_error.py:30:1: invalid-syntax: f-string: unterminated string
| |
28 | "i" "j" 28 | "i" "j"
29 | ) 29 | )

View file

@ -21,7 +21,7 @@ E11.py:6:1: E111 Indentation is not a multiple of 4
8 | if False: 8 | if False:
| |
E11.py:9:1: SyntaxError: Expected an indented block after `if` statement E11.py:9:1: invalid-syntax: Expected an indented block after `if` statement
| |
7 | #: E112 7 | #: E112
8 | if False: 8 | if False:
@ -31,7 +31,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
11 | print() 11 | print()
| |
E11.py:12:1: SyntaxError: Unexpected indentation E11.py:12:1: invalid-syntax: Unexpected indentation
| |
10 | #: E113 10 | #: E113
11 | print() 11 | print()
@ -41,7 +41,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation
14 | mimetype = 'application/x-directory' 14 | mimetype = 'application/x-directory'
| |
E11.py:14:1: SyntaxError: Expected a statement E11.py:14:1: invalid-syntax: Expected a statement
| |
12 | print() 12 | print()
13 | #: E114 E116 13 | #: E114 E116
@ -51,7 +51,7 @@ E11.py:14:1: SyntaxError: Expected a statement
16 | create_date = False 16 | create_date = False
| |
E11.py:45:1: SyntaxError: Expected an indented block after `if` statement E11.py:45:1: invalid-syntax: Expected an indented block after `if` statement
| |
43 | #: E112 43 | #: E112
44 | if False: # 44 | if False: #

View file

@ -11,7 +11,7 @@ E11.py:9:1: E112 Expected an indented block
11 | print() 11 | print()
| |
E11.py:9:1: SyntaxError: Expected an indented block after `if` statement E11.py:9:1: invalid-syntax: Expected an indented block after `if` statement
| |
7 | #: E112 7 | #: E112
8 | if False: 8 | if False:
@ -21,7 +21,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
11 | print() 11 | print()
| |
E11.py:12:1: SyntaxError: Unexpected indentation E11.py:12:1: invalid-syntax: Unexpected indentation
| |
10 | #: E113 10 | #: E113
11 | print() 11 | print()
@ -31,7 +31,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation
14 | mimetype = 'application/x-directory' 14 | mimetype = 'application/x-directory'
| |
E11.py:14:1: SyntaxError: Expected a statement E11.py:14:1: invalid-syntax: Expected a statement
| |
12 | print() 12 | print()
13 | #: E114 E116 13 | #: E114 E116
@ -51,7 +51,7 @@ E11.py:45:1: E112 Expected an indented block
47 | if False: 47 | if False:
| |
E11.py:45:1: SyntaxError: Expected an indented block after `if` statement E11.py:45:1: invalid-syntax: Expected an indented block after `if` statement
| |
43 | #: E112 43 | #: E112
44 | if False: # 44 | if False: #

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
--- ---
E11.py:9:1: SyntaxError: Expected an indented block after `if` statement E11.py:9:1: invalid-syntax: Expected an indented block after `if` statement
| |
7 | #: E112 7 | #: E112
8 | if False: 8 | if False:
@ -21,7 +21,7 @@ E11.py:12:1: E113 Unexpected indentation
14 | mimetype = 'application/x-directory' 14 | mimetype = 'application/x-directory'
| |
E11.py:12:1: SyntaxError: Unexpected indentation E11.py:12:1: invalid-syntax: Unexpected indentation
| |
10 | #: E113 10 | #: E113
11 | print() 11 | print()
@ -31,7 +31,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation
14 | mimetype = 'application/x-directory' 14 | mimetype = 'application/x-directory'
| |
E11.py:14:1: SyntaxError: Expected a statement E11.py:14:1: invalid-syntax: Expected a statement
| |
12 | print() 12 | print()
13 | #: E114 E116 13 | #: E114 E116
@ -41,7 +41,7 @@ E11.py:14:1: SyntaxError: Expected a statement
16 | create_date = False 16 | create_date = False
| |
E11.py:45:1: SyntaxError: Expected an indented block after `if` statement E11.py:45:1: invalid-syntax: Expected an indented block after `if` statement
| |
43 | #: E112 43 | #: E112
44 | if False: # 44 | if False: #

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
--- ---
E11.py:9:1: SyntaxError: Expected an indented block after `if` statement E11.py:9:1: invalid-syntax: Expected an indented block after `if` statement
| |
7 | #: E112 7 | #: E112
8 | if False: 8 | if False:
@ -11,7 +11,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
11 | print() 11 | print()
| |
E11.py:12:1: SyntaxError: Unexpected indentation E11.py:12:1: invalid-syntax: Unexpected indentation
| |
10 | #: E113 10 | #: E113
11 | print() 11 | print()
@ -21,7 +21,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation
14 | mimetype = 'application/x-directory' 14 | mimetype = 'application/x-directory'
| |
E11.py:14:1: SyntaxError: Expected a statement E11.py:14:1: invalid-syntax: Expected a statement
| |
12 | print() 12 | print()
13 | #: E114 E116 13 | #: E114 E116
@ -41,7 +41,7 @@ E11.py:15:1: E114 Indentation is not a multiple of 4 (comment)
17 | #: E116 E116 E116 17 | #: E116 E116 E116
| |
E11.py:45:1: SyntaxError: Expected an indented block after `if` statement E11.py:45:1: invalid-syntax: Expected an indented block after `if` statement
| |
43 | #: E112 43 | #: E112
44 | if False: # 44 | if False: #

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
--- ---
E11.py:9:1: SyntaxError: Expected an indented block after `if` statement E11.py:9:1: invalid-syntax: Expected an indented block after `if` statement
| |
7 | #: E112 7 | #: E112
8 | if False: 8 | if False:
@ -11,7 +11,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
11 | print() 11 | print()
| |
E11.py:12:1: SyntaxError: Unexpected indentation E11.py:12:1: invalid-syntax: Unexpected indentation
| |
10 | #: E113 10 | #: E113
11 | print() 11 | print()
@ -21,7 +21,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation
14 | mimetype = 'application/x-directory' 14 | mimetype = 'application/x-directory'
| |
E11.py:14:1: SyntaxError: Expected a statement E11.py:14:1: invalid-syntax: Expected a statement
| |
12 | print() 12 | print()
13 | #: E114 E116 13 | #: E114 E116
@ -91,7 +91,7 @@ E11.py:35:1: E115 Expected an indented block (comment)
37 | #: E117 37 | #: E117
| |
E11.py:45:1: SyntaxError: Expected an indented block after `if` statement E11.py:45:1: invalid-syntax: Expected an indented block after `if` statement
| |
43 | #: E112 43 | #: E112
44 | if False: # 44 | if False: #

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
--- ---
E11.py:9:1: SyntaxError: Expected an indented block after `if` statement E11.py:9:1: invalid-syntax: Expected an indented block after `if` statement
| |
7 | #: E112 7 | #: E112
8 | if False: 8 | if False:
@ -11,7 +11,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
11 | print() 11 | print()
| |
E11.py:12:1: SyntaxError: Unexpected indentation E11.py:12:1: invalid-syntax: Unexpected indentation
| |
10 | #: E113 10 | #: E113
11 | print() 11 | print()
@ -21,7 +21,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation
14 | mimetype = 'application/x-directory' 14 | mimetype = 'application/x-directory'
| |
E11.py:14:1: SyntaxError: Expected a statement E11.py:14:1: invalid-syntax: Expected a statement
| |
12 | print() 12 | print()
13 | #: E114 E116 13 | #: E114 E116
@ -71,7 +71,7 @@ E11.py:26:1: E116 Unexpected indentation (comment)
28 | def start(self): 28 | def start(self):
| |
E11.py:45:1: SyntaxError: Expected an indented block after `if` statement E11.py:45:1: invalid-syntax: Expected an indented block after `if` statement
| |
43 | #: E112 43 | #: E112
44 | if False: # 44 | if False: #

View file

@ -11,7 +11,7 @@ E11.py:6:1: E117 Over-indented
8 | if False: 8 | if False:
| |
E11.py:9:1: SyntaxError: Expected an indented block after `if` statement E11.py:9:1: invalid-syntax: Expected an indented block after `if` statement
| |
7 | #: E112 7 | #: E112
8 | if False: 8 | if False:
@ -21,7 +21,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
11 | print() 11 | print()
| |
E11.py:12:1: SyntaxError: Unexpected indentation E11.py:12:1: invalid-syntax: Unexpected indentation
| |
10 | #: E113 10 | #: E113
11 | print() 11 | print()
@ -31,7 +31,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation
14 | mimetype = 'application/x-directory' 14 | mimetype = 'application/x-directory'
| |
E11.py:14:1: SyntaxError: Expected a statement E11.py:14:1: invalid-syntax: Expected a statement
| |
12 | print() 12 | print()
13 | #: E114 E116 13 | #: E114 E116
@ -61,7 +61,7 @@ E11.py:42:1: E117 Over-indented
44 | if False: # 44 | if False: #
| |
E11.py:45:1: SyntaxError: Expected an indented block after `if` statement E11.py:45:1: invalid-syntax: Expected an indented block after `if` statement
| |
43 | #: E112 43 | #: E112
44 | if False: # 44 | if False: #

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
--- ---
E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '(' E30_syntax_error.py:4:15: invalid-syntax: Expected ']', found '('
| |
2 | # parenthesis. 2 | # parenthesis.
3 | 3 |
@ -10,7 +10,7 @@ E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '('
5 | pass 5 | pass
| |
E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline E30_syntax_error.py:13:18: invalid-syntax: Expected ')', found newline
| |
12 | class Foo: 12 | class Foo:
13 | def __init__( 13 | def __init__(
@ -30,7 +30,7 @@ E30_syntax_error.py:15:5: E301 Expected 1 blank line, found 0
| |
= help: Add missing blank line = help: Add missing blank line
E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline E30_syntax_error.py:18:11: invalid-syntax: Expected ')', found newline
| |
16 | pass 16 | pass
17 | 17 |
@ -41,7 +41,7 @@ E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline
21 | def top( 21 | def top(
| |
E30_syntax_error.py:21:9: SyntaxError: Expected ')', found newline E30_syntax_error.py:21:9: invalid-syntax: Expected ')', found newline
| |
21 | def top( 21 | def top(
| ^ | ^

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
--- ---
E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '(' E30_syntax_error.py:4:15: invalid-syntax: Expected ']', found '('
| |
2 | # parenthesis. 2 | # parenthesis.
3 | 3 |
@ -20,7 +20,7 @@ E30_syntax_error.py:7:1: E302 Expected 2 blank lines, found 1
| |
= help: Add missing blank line(s) = help: Add missing blank line(s)
E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline E30_syntax_error.py:13:18: invalid-syntax: Expected ')', found newline
| |
12 | class Foo: 12 | class Foo:
13 | def __init__( 13 | def __init__(
@ -30,7 +30,7 @@ E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline
16 | pass 16 | pass
| |
E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline E30_syntax_error.py:18:11: invalid-syntax: Expected ')', found newline
| |
16 | pass 16 | pass
17 | 17 |
@ -41,7 +41,7 @@ E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline
21 | def top( 21 | def top(
| |
E30_syntax_error.py:21:9: SyntaxError: Expected ')', found newline E30_syntax_error.py:21:9: invalid-syntax: Expected ')', found newline
| |
21 | def top( 21 | def top(
| ^ | ^

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
--- ---
E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '(' E30_syntax_error.py:4:15: invalid-syntax: Expected ']', found '('
| |
2 | # parenthesis. 2 | # parenthesis.
3 | 3 |
@ -19,7 +19,7 @@ E30_syntax_error.py:12:1: E303 Too many blank lines (3)
| |
= help: Remove extraneous blank line(s) = help: Remove extraneous blank line(s)
E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline E30_syntax_error.py:13:18: invalid-syntax: Expected ')', found newline
| |
12 | class Foo: 12 | class Foo:
13 | def __init__( 13 | def __init__(
@ -29,7 +29,7 @@ E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline
16 | pass 16 | pass
| |
E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline E30_syntax_error.py:18:11: invalid-syntax: Expected ')', found newline
| |
16 | pass 16 | pass
17 | 17 |
@ -40,7 +40,7 @@ E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline
21 | def top( 21 | def top(
| |
E30_syntax_error.py:21:9: SyntaxError: Expected ')', found newline E30_syntax_error.py:21:9: invalid-syntax: Expected ')', found newline
| |
21 | def top( 21 | def top(
| ^ | ^

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
--- ---
E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '(' E30_syntax_error.py:4:15: invalid-syntax: Expected ']', found '('
| |
2 | # parenthesis. 2 | # parenthesis.
3 | 3 |
@ -10,7 +10,7 @@ E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '('
5 | pass 5 | pass
| |
E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline E30_syntax_error.py:13:18: invalid-syntax: Expected ')', found newline
| |
12 | class Foo: 12 | class Foo:
13 | def __init__( 13 | def __init__(
@ -29,7 +29,7 @@ E30_syntax_error.py:18:1: E305 Expected 2 blank lines after class or function de
| |
= help: Add missing blank line(s) = help: Add missing blank line(s)
E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline E30_syntax_error.py:18:11: invalid-syntax: Expected ')', found newline
| |
16 | pass 16 | pass
17 | 17 |
@ -40,7 +40,7 @@ E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline
21 | def top( 21 | def top(
| |
E30_syntax_error.py:21:9: SyntaxError: Expected ')', found newline E30_syntax_error.py:21:9: invalid-syntax: Expected ')', found newline
| |
21 | def top( 21 | def top(
| ^ | ^

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
--- ---
E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '(' E30_syntax_error.py:4:15: invalid-syntax: Expected ']', found '('
| |
2 | # parenthesis. 2 | # parenthesis.
3 | 3 |
@ -10,7 +10,7 @@ E30_syntax_error.py:4:15: SyntaxError: Expected ']', found '('
5 | pass 5 | pass
| |
E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline E30_syntax_error.py:13:18: invalid-syntax: Expected ')', found newline
| |
12 | class Foo: 12 | class Foo:
13 | def __init__( 13 | def __init__(
@ -20,7 +20,7 @@ E30_syntax_error.py:13:18: SyntaxError: Expected ')', found newline
16 | pass 16 | pass
| |
E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline E30_syntax_error.py:18:11: invalid-syntax: Expected ')', found newline
| |
16 | pass 16 | pass
17 | 17 |
@ -31,7 +31,7 @@ E30_syntax_error.py:18:11: SyntaxError: Expected ')', found newline
21 | def top( 21 | def top(
| |
E30_syntax_error.py:21:9: SyntaxError: Expected ')', found newline E30_syntax_error.py:21:9: invalid-syntax: Expected ')', found newline
| |
21 | def top( 21 | def top(
| ^ | ^

View file

@ -8,14 +8,14 @@ W19.py:1:1: W191 Indentation contains tabs
2 | multiline string with tab in it''' 2 | multiline string with tab in it'''
| |
W19.py:1:1: SyntaxError: Unexpected indentation W19.py:1:1: invalid-syntax: Unexpected indentation
| |
1 | '''File starts with a tab 1 | '''File starts with a tab
| ^^^^ | ^^^^
2 | multiline string with tab in it''' 2 | multiline string with tab in it'''
| |
W19.py:5:1: SyntaxError: Expected a statement W19.py:5:1: invalid-syntax: Expected a statement
| |
4 | #: W191 4 | #: W191
5 | if False: 5 | if False:

View file

@ -1,8 +1,7 @@
--- ---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
snapshot_kind: text
--- ---
E2_syntax_error.py:1:10: SyntaxError: Expected an expression E2_syntax_error.py:1:10: invalid-syntax: Expected an expression
| |
1 | a = (1 or) 1 | a = (1 or)
| ^ | ^

View file

@ -11,7 +11,7 @@ invalid_characters_syntax_error.py:5:6: PLE2510 Invalid unescaped character back
| |
= help: Replace with escape sequence = help: Replace with escape sequence
invalid_characters_syntax_error.py:7:5: SyntaxError: missing closing quote in string literal invalid_characters_syntax_error.py:7:5: invalid-syntax: missing closing quote in string literal
| |
5 | b = '␈' 5 | b = '␈'
6 | # Unterminated string 6 | # Unterminated string
@ -21,7 +21,7 @@ invalid_characters_syntax_error.py:7:5: SyntaxError: missing closing quote in st
9 | # Unterminated f-string 9 | # Unterminated f-string
| |
invalid_characters_syntax_error.py:7:7: SyntaxError: Expected a statement invalid_characters_syntax_error.py:7:7: invalid-syntax: Expected a statement
| |
5 | b = '␈' 5 | b = '␈'
6 | # Unterminated string 6 | # Unterminated string
@ -43,7 +43,7 @@ invalid_characters_syntax_error.py:8:6: PLE2510 Invalid unescaped character back
| |
= help: Replace with escape sequence = help: Replace with escape sequence
invalid_characters_syntax_error.py:10:7: SyntaxError: f-string: unterminated string invalid_characters_syntax_error.py:10:7: invalid-syntax: f-string: unterminated string
| |
8 | b = '␈' 8 | b = '␈'
9 | # Unterminated f-string 9 | # Unterminated f-string
@ -53,7 +53,7 @@ invalid_characters_syntax_error.py:10:7: SyntaxError: f-string: unterminated str
12 | # Implicitly concatenated 12 | # Implicitly concatenated
| |
invalid_characters_syntax_error.py:10:8: SyntaxError: Expected FStringEnd, found newline invalid_characters_syntax_error.py:10:8: invalid-syntax: Expected FStringEnd, found newline
| |
8 | b = '␈' 8 | b = '␈'
9 | # Unterminated f-string 9 | # Unterminated f-string
@ -93,7 +93,7 @@ invalid_characters_syntax_error.py:13:11: PLE2510 Invalid unescaped character ba
| |
= help: Replace with escape sequence = help: Replace with escape sequence
invalid_characters_syntax_error.py:13:14: SyntaxError: missing closing quote in string literal invalid_characters_syntax_error.py:13:14: invalid-syntax: missing closing quote in string literal
| |
11 | b = f'␈' 11 | b = f'␈'
12 | # Implicitly concatenated 12 | # Implicitly concatenated
@ -101,7 +101,7 @@ invalid_characters_syntax_error.py:13:14: SyntaxError: missing closing quote in
| ^^ | ^^
| |
invalid_characters_syntax_error.py:13:16: SyntaxError: Expected a statement invalid_characters_syntax_error.py:13:16: invalid-syntax: Expected a statement
| |
11 | b = f'␈' 11 | b = f'␈'
12 | # Implicitly concatenated 12 | # Implicitly concatenated

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
resources/test/fixtures/syntax_errors/async_comprehension.ipynb:3:5: SyntaxError: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11) resources/test/fixtures/syntax_errors/async_comprehension.ipynb:3:5: invalid-syntax: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
| |
1 | async def elements(n): yield n 1 | async def elements(n): yield n
2 | [x async for x in elements(5)] # okay, async at top level 2 | [x async for x in elements(5)] # okay, async at top level

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:1:27: SyntaxError: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11) <filename>:1:27: invalid-syntax: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
| |
1 | async def f(): return [[x async for x in foo(n)] for n in range(3)] 1 | async def f(): return [[x async for x in foo(n)] for n in range(3)]
| ^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:3:21: SyntaxError: attribute name `x` repeated in class pattern <filename>:3:21: invalid-syntax: attribute name `x` repeated in class pattern
| |
2 | match x: 2 | match x:
3 | case Point(x=1, x=2): 3 | case Point(x=1, x=2):

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:3:21: SyntaxError: mapping pattern checks duplicate key `'key'` <filename>:3:21: invalid-syntax: mapping pattern checks duplicate key `'key'`
| |
2 | match x: 2 | match x:
3 | case {'key': 1, 'key': 2}: 3 | case {'key': 1, 'key': 2}:

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:1:12: SyntaxError: duplicate type parameter <filename>:1:12: invalid-syntax: duplicate type parameter
| |
1 | class C[T, T]: pass 1 | class C[T, T]: pass
| ^ | ^

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:2:22: SyntaxError: named expression cannot be used within a generic definition <filename>:2:22: invalid-syntax: named expression cannot be used within a generic definition
| |
2 | def f[T](x: int) -> (y := 3): return x 2 | def f[T](x: int) -> (y := 3): return x
| ^^^^^^ | ^^^^^^

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:2:13: SyntaxError: yield expression cannot be used within a generic definition <filename>:2:13: invalid-syntax: yield expression cannot be used within a generic definition
| |
2 | class C[T]((yield from [object])): 2 | class C[T]((yield from [object])):
| ^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:2:11: SyntaxError: yield expression cannot be used within a type alias <filename>:2:11: invalid-syntax: yield expression cannot be used within a type alias
| |
2 | type Y = (yield 1) 2 | type Y = (yield 1)
| ^^^^^^^ | ^^^^^^^

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:2:12: SyntaxError: yield expression cannot be used within a TypeVar bound <filename>:2:12: invalid-syntax: yield expression cannot be used within a TypeVar bound
| |
2 | type X[T: (yield 1)] = int 2 | type X[T: (yield 1)] = int
| ^^^^^^^ | ^^^^^^^

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:3:12: SyntaxError: Starred expression cannot be used here <filename>:3:12: invalid-syntax: Starred expression cannot be used here
| |
2 | def func(): 2 | def func():
3 | return *x 3 | return *x

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:2:5: SyntaxError: Starred expression cannot be used here <filename>:2:5: invalid-syntax: Starred expression cannot be used here
| |
2 | for *x in range(10): 2 | for *x in range(10):
| ^^ | ^^

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:3:11: SyntaxError: Starred expression cannot be used here <filename>:3:11: invalid-syntax: Starred expression cannot be used here
| |
2 | def func(): 2 | def func():
3 | yield *x 3 | yield *x

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:3:10: SyntaxError: name capture `irrefutable` makes remaining patterns unreachable <filename>:3:10: invalid-syntax: name capture `irrefutable` makes remaining patterns unreachable
| |
2 | match value: 2 | match value:
3 | case irrefutable: 3 | case irrefutable:

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:3:10: SyntaxError: wildcard makes remaining patterns unreachable <filename>:3:10: invalid-syntax: wildcard makes remaining patterns unreachable
| |
2 | match value: 2 | match value:
3 | case _: 3 | case _:

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:3:14: SyntaxError: multiple assignments to name `a` in pattern <filename>:3:14: invalid-syntax: multiple assignments to name `a` in pattern
| |
2 | match x: 2 | match x:
3 | case [a, a]: 3 | case [a, a]:

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:1:2: SyntaxError: assignment expression cannot rebind comprehension variable <filename>:1:2: invalid-syntax: assignment expression cannot rebind comprehension variable
| |
1 | [x:= 2 for x in range(2)] 1 | [x:= 2 for x in range(2)]
| ^ | ^

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:1:1: SyntaxError: starred assignment target must be in a list or tuple <filename>:1:1: invalid-syntax: starred assignment target must be in a list or tuple
| |
1 | *a = [1, 2, 3, 4] 1 | *a = [1, 2, 3, 4]
| ^^ | ^^

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:2:1: SyntaxError: cannot assign to `__debug__` <filename>:2:1: invalid-syntax: cannot assign to `__debug__`
| |
2 | __debug__ = False 2 | __debug__ = False
| ^^^^^^^^^ | ^^^^^^^^^

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:2:15: SyntaxError: cannot assign to `__debug__` <filename>:2:15: invalid-syntax: cannot assign to `__debug__`
| |
2 | class Generic[__debug__]: 2 | class Generic[__debug__]:
| ^^^^^^^^^ | ^^^^^^^^^

View file

@ -1,7 +1,7 @@
--- ---
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
<filename>:2:13: SyntaxError: cannot assign to `__debug__` <filename>:2:13: invalid-syntax: cannot assign to `__debug__`
| |
2 | def process(__debug__): 2 | def process(__debug__):
| ^^^^^^^^^ | ^^^^^^^^^

View file

@ -57,7 +57,7 @@ export interface Diagnostic {
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
pub struct ExpandedMessage { pub struct ExpandedMessage {
pub code: Option<String>, pub code: String,
pub message: String, pub message: String,
pub start_location: Location, pub start_location: Location,
pub end_location: Location, pub end_location: Location,
@ -229,7 +229,7 @@ impl Workspace {
let messages: Vec<ExpandedMessage> = diagnostics let messages: Vec<ExpandedMessage> = diagnostics
.into_iter() .into_iter()
.map(|msg| ExpandedMessage { .map(|msg| ExpandedMessage {
code: msg.secondary_code().map(ToString::to_string), code: msg.secondary_code_or_id().to_string(),
message: msg.body().to_string(), message: msg.body().to_string(),
start_location: source_code.line_column(msg.expect_range().start()).into(), start_location: source_code.line_column(msg.expect_range().start()).into(),
end_location: source_code.line_column(msg.expect_range().end()).into(), end_location: source_code.line_column(msg.expect_range().end()).into(),

View file

@ -27,7 +27,7 @@ fn empty_config() {
"if (1, 2):\n pass", "if (1, 2):\n pass",
r#"{}"#, r#"{}"#,
[ExpandedMessage { [ExpandedMessage {
code: Some(Rule::IfTuple.noqa_code().to_string()), code: Rule::IfTuple.noqa_code().to_string(),
message: "If test is a tuple, which is always `True`".to_string(), message: "If test is a tuple, which is always `True`".to_string(),
start_location: Location { start_location: Location {
row: OneIndexed::from_zero_indexed(0), row: OneIndexed::from_zero_indexed(0),
@ -50,8 +50,8 @@ fn syntax_error() {
"x =\ny = 1\n", "x =\ny = 1\n",
r#"{}"#, r#"{}"#,
[ExpandedMessage { [ExpandedMessage {
code: None, code: "invalid-syntax".to_string(),
message: "SyntaxError: Expected an expression".to_string(), message: "Expected an expression".to_string(),
start_location: Location { start_location: Location {
row: OneIndexed::from_zero_indexed(0), row: OneIndexed::from_zero_indexed(0),
column: OneIndexed::from_zero_indexed(3) column: OneIndexed::from_zero_indexed(3)
@ -73,8 +73,9 @@ fn unsupported_syntax_error() {
"match 2:\n case 1: ...", "match 2:\n case 1: ...",
r#"{"target-version": "py39"}"#, r#"{"target-version": "py39"}"#,
[ExpandedMessage { [ExpandedMessage {
code: None, code: "invalid-syntax".to_string(),
message: "SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)".to_string(), message: "Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)"
.to_string(),
start_location: Location { start_location: Location {
row: OneIndexed::from_zero_indexed(0), row: OneIndexed::from_zero_indexed(0),
column: OneIndexed::from_zero_indexed(0) column: OneIndexed::from_zero_indexed(0)

View file

@ -45,7 +45,7 @@ CHECK_DIFF_LINE_RE = re.compile(
) )
CHECK_DIAGNOSTIC_LINE_RE = re.compile( CHECK_DIAGNOSTIC_LINE_RE = re.compile(
r"^(?P<diff>[+-])? ?(?P<location>.*): (?P<code>[A-Z]{1,4}[0-9]{3,4}|SyntaxError:)(?P<fixable> \[\*\])? (?P<message>.*)" r"^(?P<diff>[+-])? ?(?P<location>.*): (?P<code>[A-Z]{1,4}[0-9]{3,4}|[a-z\-]+:)(?P<fixable> \[\*\])? (?P<message>.*)"
) )
CHECK_VIOLATION_FIX_INDICATOR = " [*]" CHECK_VIOLATION_FIX_INDICATOR = " [*]"