diff --git a/crates/ruff/src/lib.rs b/crates/ruff/src/lib.rs index c56514f869dca..9d25a0967494a 100644 --- a/crates/ruff/src/lib.rs +++ b/crates/ruff/src/lib.rs @@ -359,10 +359,9 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result Result<()> { if matches!(self.log_level, LogLevel::Silent) { return Ok(()); @@ -235,12 +236,12 @@ impl Printer { let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes); let config = DisplayDiagnosticConfig::new("ruff") - .preview(preview) - .hide_severity(true) + .preview(preview.is_enabled()) + .hide_severity(!is_warning_severity_enabled(preview)) .color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize()) .with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref())) .with_fix_applicability(self.unsafe_fixes.required_applicability()) - .show_fix_diff(preview); + .show_fix_diff(preview.is_enabled()); render_diagnostics(writer, self.format, config, &context, &diagnostics.inner)?; @@ -382,7 +383,7 @@ impl Printer { &self, writer: &mut dyn Write, diagnostics: &Diagnostics, - preview: bool, + preview: PreviewMode, ) -> Result<()> { if matches!(self.log_level, LogLevel::Silent) { return Ok(()); @@ -409,12 +410,12 @@ impl Printer { let context = EmitterContext::new(&diagnostics.notebook_indexes); let config = DisplayDiagnosticConfig::new("ruff") - .preview(preview) - .hide_severity(true) + .preview(preview.is_enabled()) + .hide_severity(!is_warning_severity_enabled(preview)) .color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize()) .with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref())) .with_fix_applicability(self.unsafe_fixes.required_applicability()) - .show_fix_diff(preview); + .show_fix_diff(preview.is_enabled()); render_diagnostics(writer, self.format, config, &context, &diagnostics.inner)?; } writer.flush()?; diff --git a/crates/ruff/tests/cli/lint.rs b/crates/ruff/tests/cli/lint.rs index 5408798cc8acc..ab6f50f239744 100644 --- a/crates/ruff/tests/cli/lint.rs +++ b/crates/ruff/tests/cli/lint.rs @@ -2727,15 +2727,15 @@ fn nested_implicit_namespace_package() -> Result<()> { .arg("--select") .arg("INP") .arg("--preview") - , @" + , @r###" success: false exit_code: 1 ----- stdout ----- - foo/bar/baz/__init__.py:1:1: INP001 File `foo/bar/baz/__init__.py` declares a package, but is nested under an implicit namespace package. Add an `__init__.py` to `foo/bar`. + foo/bar/baz/__init__.py:1:1: error[INP001] File `foo/bar/baz/__init__.py` declares a package, but is nested under an implicit namespace package. Add an `__init__.py` to `foo/bar`. Found 1 error. ----- stderr ----- - "); + "###); Ok(()) } @@ -3112,7 +3112,7 @@ class Foo[_T, __T]: pass "# ), - @" + @r###" success: false exit_code: 1 ----- stdout ----- @@ -3121,9 +3121,9 @@ class Foo[_T, __T]: pass ----- stderr ----- - test.py:2:14: UP049 Generic class uses private type parameters + test.py:2:14: error[UP049] Generic class uses private type parameters Found 2 errors (1 fixed, 1 remaining). - " + "### ); } @@ -3263,16 +3263,16 @@ T = TypeVar("T") class A(Generic[T]): var: T "#), - @" + @r###" success: false exit_code: 1 ----- stdout ----- - test.py:6:9: UP046 Generic class `A` uses `Generic` subclass instead of type parameters + test.py:6:9: error[UP046] Generic class `A` uses `Generic` subclass instead of type parameters Found 1 error. No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option). ----- stderr ----- - " + "### ); // with per-file-target-version, there should be no errors because the new generic syntax is @@ -3405,15 +3405,15 @@ match 2: print("it's one") "# ), - @" + @r###" success: false exit_code: 1 ----- stdout ----- - test.py:2:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) + test.py:2:1: error[invalid-syntax] Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) Found 1 error. ----- stderr ----- - " + "### ); } @@ -3431,27 +3431,27 @@ fn cache_syntax_errors() -> Result<()> { assert_cmd_snapshot!( cmd, - @" + @r###" success: false exit_code: 1 ----- stdout ----- - main.py:1:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) + main.py:1:1: error[invalid-syntax] Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) ----- stderr ----- - " + "### ); // this should *not* be cached, like normal parse errors assert_cmd_snapshot!( cmd, - @" + @r###" success: false exit_code: 1 ----- stdout ----- - main.py:1:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) + main.py:1:1: error[invalid-syntax] Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) ----- stderr ----- - " + "### ); Ok(()) @@ -3552,29 +3552,29 @@ fn semantic_syntax_errors() -> Result<()> { assert_cmd_snapshot!( cmd, - @" + @r###" success: false exit_code: 1 ----- stdout ----- - main.py:1:3: invalid-syntax: assignment expression cannot rebind comprehension variable - main.py:1:20: F821 Undefined name `foo` + main.py:1:3: error[invalid-syntax] assignment expression cannot rebind comprehension variable + main.py:1:20: error[F821] Undefined name `foo` ----- stderr ----- - " + "### ); // this should *not* be cached, like normal parse errors assert_cmd_snapshot!( cmd, - @" + @r###" success: false exit_code: 1 ----- stdout ----- - main.py:1:3: invalid-syntax: assignment expression cannot rebind comprehension variable - main.py:1:20: F821 Undefined name `foo` + main.py:1:3: error[invalid-syntax] assignment expression cannot rebind comprehension variable + main.py:1:20: error[F821] Undefined name `foo` ----- stderr ----- - " + "### ); // ensure semantic errors are caught even without AST-based rules selected @@ -3584,15 +3584,15 @@ fn semantic_syntax_errors() -> Result<()> { .arg("--preview") .arg("-") .pass_stdin(contents), - @" + @r###" success: false exit_code: 1 ----- stdout ----- - -:1:3: invalid-syntax: assignment expression cannot rebind comprehension variable + -:1:3: error[invalid-syntax] assignment expression cannot rebind comprehension variable Found 1 error. ----- stderr ----- - " + "### ); Ok(()) @@ -3741,11 +3741,11 @@ fn show_fixes_in_full_output_with_preview_enabled() { .arg("--preview") .arg("-") .pass_stdin("import math"), - @" + @r###" success: false exit_code: 1 ----- stdout ----- - F401 [*] `math` imported but unused + error[F401][*]: `math` imported but unused --> -:1:8 | 1 | import math @@ -3758,7 +3758,7 @@ fn show_fixes_in_full_output_with_preview_enabled() { [*] 1 fixable with the `--fix` option. ----- stderr ----- - ", + "###, ); } @@ -3772,17 +3772,17 @@ fn rule_panic_mixed_results_concise() -> Result<()> { fixture.check_command() .args(["--select", "RUF9", "--preview"]) .args(["normal.py", "panic.py"]), - @" + @r###" success: false exit_code: 2 ----- stdout ----- - normal.py:1:1: RUF900 Hey this is a stable test rule. - normal.py:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix. - normal.py:1:1: RUF902 Hey this is a stable test rule with an unsafe fix. - normal.py:1:1: RUF903 Hey this is a stable test rule with a display only fix. - normal.py:1:1: RUF911 Hey this is a preview test rule. - normal.py:1:1: RUF950 Hey this is a test rule that was redirected from another. - panic.py: panic: Panicked at when checking `[TMP]/panic.py`: `This is a fake panic for testing.` + normal.py:1:1: error[RUF900] Hey this is a stable test rule. + normal.py:1:1: error[RUF901] [*] Hey this is a stable test rule with a safe fix. + normal.py:1:1: error[RUF902] Hey this is a stable test rule with an unsafe fix. + normal.py:1:1: error[RUF903] Hey this is a stable test rule with a display only fix. + normal.py:1:1: error[RUF911] Hey this is a preview test rule. + normal.py:1:1: error[RUF950] Hey this is a test rule that was redirected from another. + panic.py: fatal[panic] Panicked at when checking `[TMP]/panic.py`: `This is a fake panic for testing.` Found 7 errors. [*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option). @@ -3792,7 +3792,7 @@ fn rule_panic_mixed_results_concise() -> Result<()> { https://github.com/astral-sh/ruff/issues/new?title=%5BLinter%20panic%5D ...with the relevant file contents, the `pyproject.toml` settings, and the stack trace above, we'd be very appreciative! - "); + "###); Ok(()) } @@ -3807,31 +3807,31 @@ fn rule_panic_mixed_results_full() -> Result<()> { fixture.command() .args(["check", "--select", "RUF9", "--preview", "--output-format=full", "--no-cache"]) .args(["normal.py", "panic.py"]), - @" + @r###" success: false exit_code: 2 ----- stdout ----- - RUF900 Hey this is a stable test rule. + error[RUF900]: Hey this is a stable test rule. --> normal.py:1:1 - RUF901 [*] Hey this is a stable test rule with a safe fix. + error[RUF901][*]: Hey this is a stable test rule with a safe fix. --> normal.py:1:1 1 + # fix from stable-test-rule-safe-fix 2 | import os - RUF902 Hey this is a stable test rule with an unsafe fix. + error[RUF902]: Hey this is a stable test rule with an unsafe fix. --> normal.py:1:1 - RUF903 Hey this is a stable test rule with a display only fix. + error[RUF903]: Hey this is a stable test rule with a display only fix. --> normal.py:1:1 - RUF911 Hey this is a preview test rule. + error[RUF911]: Hey this is a preview test rule. --> normal.py:1:1 - RUF950 Hey this is a test rule that was redirected from another. + error[RUF950]: Hey this is a test rule that was redirected from another. --> normal.py:1:1 - panic: Panicked at when checking `[TMP]/panic.py`: `This is a fake panic for testing.` + error[panic]: Panicked at when checking `[TMP]/panic.py`: `This is a fake panic for testing.` --> panic.py:1:1 info: This indicates a bug in Ruff. info: If you could open an issue at https://github.com/astral-sh/ruff/issues/new?title=%5Bpanic%5D, we'd be very appreciative! @@ -3846,7 +3846,7 @@ fn rule_panic_mixed_results_full() -> Result<()> { https://github.com/astral-sh/ruff/issues/new?title=%5BLinter%20panic%5D ...with the relevant file contents, the `pyproject.toml` settings, and the stack trace above, we'd be very appreciative! - "); + "###); Ok(()) } @@ -3996,19 +3996,19 @@ fn supported_file_extensions_preview_enabled() -> Result<()> { fixture.check_command() .args(["--select", "F401", "--preview"]) .arg("src"), - @" + @r###" success: false exit_code: 1 ----- stdout ----- - src/thing.ipynb:cell 1:1:8: F401 [*] `os` imported but unused - src/thing.py:1:8: F401 [*] `os` imported but unused - src/thing.pyi:1:8: F401 [*] `os` imported but unused - src/thing.pyw:1:8: F401 [*] `os` imported but unused + src/thing.ipynb:cell 1:1:8: error[F401] [*] `os` imported but unused + src/thing.py:1:8: error[F401] [*] `os` imported but unused + src/thing.pyi:1:8: error[F401] [*] `os` imported but unused + src/thing.pyw:1:8: error[F401] [*] `os` imported but unused Found 4 errors. [*] 4 fixable with the `--fix` option. ----- stderr ----- - "); + "###); Ok(()) } diff --git a/crates/ruff/tests/cli/snapshots/cli__format__output_format_json-lines.snap b/crates/ruff/tests/cli/snapshots/cli__format__output_format_json-lines.snap index 10c4622041994..85b62ddd08682 100644 --- a/crates/ruff/tests/cli/snapshots/cli__format__output_format_json-lines.snap +++ b/crates/ruff/tests/cli/snapshots/cli__format__output_format_json-lines.snap @@ -14,6 +14,6 @@ info: success: false exit_code: 1 ----- stdout ----- -{"cell":null,"code":"unformatted","end_location":{"column":1,"row":2},"filename":"[TMP]/input.py","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":null},"location":{"column":1,"row":1},"message":"File would be reformatted","noqa_row":null,"url":null} +{"cell":null,"code":"unformatted","end_location":{"column":1,"row":2},"filename":"[TMP]/input.py","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":null},"location":{"column":1,"row":1},"message":"File would be reformatted","noqa_row":null,"severity":"error","url":null} ----- stderr ----- diff --git a/crates/ruff/tests/cli/snapshots/cli__format__output_format_json.snap b/crates/ruff/tests/cli/snapshots/cli__format__output_format_json.snap index e1058c41b1d67..dc120e7d20421 100644 --- a/crates/ruff/tests/cli/snapshots/cli__format__output_format_json.snap +++ b/crates/ruff/tests/cli/snapshots/cli__format__output_format_json.snap @@ -46,6 +46,7 @@ exit_code: 1 }, "message": "File would be reformatted", "noqa_row": null, + "severity": "error", "url": null } ] diff --git a/crates/ruff/tests/cli/snapshots/cli__lint__output_format_json-lines.snap b/crates/ruff/tests/cli/snapshots/cli__lint__output_format_json-lines.snap index a163a41776ff3..8f5d1e0f677e1 100644 --- a/crates/ruff/tests/cli/snapshots/cli__lint__output_format_json-lines.snap +++ b/crates/ruff/tests/cli/snapshots/cli__lint__output_format_json-lines.snap @@ -16,8 +16,8 @@ info: success: false exit_code: 1 ----- 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":"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":"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} +{"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,"severity":"error","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,"severity":"error","url":"https://docs.astral.sh/ruff/rules/undefined-name"} +{"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,"severity":"error","url":null} ----- stderr ----- diff --git a/crates/ruff/tests/cli/snapshots/cli__lint__output_format_json.snap b/crates/ruff/tests/cli/snapshots/cli__lint__output_format_json.snap index 908ff2c268feb..28c571f6aa027 100644 --- a/crates/ruff/tests/cli/snapshots/cli__lint__output_format_json.snap +++ b/crates/ruff/tests/cli/snapshots/cli__lint__output_format_json.snap @@ -48,6 +48,7 @@ exit_code: 1 }, "message": "`os` imported but unused", "noqa_row": 1, + "severity": "error", "url": "https://docs.astral.sh/ruff/rules/unused-import" }, { @@ -65,6 +66,7 @@ exit_code: 1 }, "message": "Undefined name `y`", "noqa_row": 2, + "severity": "error", "url": "https://docs.astral.sh/ruff/rules/undefined-name" }, { @@ -82,6 +84,7 @@ exit_code: 1 }, "message": "Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)", "noqa_row": null, + "severity": "error", "url": null } ] diff --git a/crates/ruff/tests/integration_test.rs b/crates/ruff/tests/integration_test.rs index 1f3320811e6bc..08ccae93de823 100644 --- a/crates/ruff/tests/integration_test.rs +++ b/crates/ruff/tests/integration_test.rs @@ -916,11 +916,11 @@ fn full_output_preview() { .args(["--preview", "--select=E741"]) .build(); assert_cmd_snapshot!(cmd - .pass_stdin("l = 1"), @" + .pass_stdin("l = 1"), @r###" success: false exit_code: 1 ----- stdout ----- - E741 Ambiguous variable name: `l` + error[E741]: Ambiguous variable name: `l` --> -:1:1 | 1 | l = 1 @@ -930,7 +930,7 @@ fn full_output_preview() { Found 1 error. ----- stderr ----- - "); + "###); } #[test] @@ -945,11 +945,11 @@ preview = true ", )?; let mut cmd = RuffCheck::default().config(&pyproject_toml).build(); - assert_cmd_snapshot!(cmd.arg("--select=E741").pass_stdin("l = 1"), @" + assert_cmd_snapshot!(cmd.arg("--select=E741").pass_stdin("l = 1"), @r###" success: false exit_code: 1 ----- stdout ----- - E741 Ambiguous variable name: `l` + error[E741]: Ambiguous variable name: `l` --> -:1:1 | 1 | l = 1 @@ -959,7 +959,7 @@ preview = true Found 1 error. ----- stderr ----- - "); + "###); Ok(()) } @@ -1254,21 +1254,21 @@ fn preview_enabled_prefix() { let mut cmd = RuffCheck::default() .args(["--select", "RUF9", "--output-format=concise", "--preview"]) .build(); - assert_cmd_snapshot!(cmd, @" + assert_cmd_snapshot!(cmd, @r###" success: false exit_code: 1 ----- stdout ----- - -:1:1: RUF900 Hey this is a stable test rule. - -:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix. - -:1:1: RUF902 Hey this is a stable test rule with an unsafe fix. - -:1:1: RUF903 Hey this is a stable test rule with a display only fix. - -:1:1: RUF911 Hey this is a preview test rule. - -:1:1: RUF950 Hey this is a test rule that was redirected from another. + -:1:1: error[RUF900] Hey this is a stable test rule. + -:1:1: error[RUF901] [*] Hey this is a stable test rule with a safe fix. + -:1:1: error[RUF902] Hey this is a stable test rule with an unsafe fix. + -:1:1: error[RUF903] Hey this is a stable test rule with a display only fix. + -:1:1: error[RUF911] Hey this is a preview test rule. + -:1:1: error[RUF950] Hey this is a test rule that was redirected from another. Found 6 errors. [*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option). ----- stderr ----- - "); + "###); } #[test] @@ -1276,25 +1276,25 @@ fn preview_enabled_all() { let mut cmd = RuffCheck::default() .args(["--select", "ALL", "--output-format=concise", "--preview"]) .build(); - assert_cmd_snapshot!(cmd, @" + assert_cmd_snapshot!(cmd, @r###" success: false exit_code: 1 ----- stdout ----- - -:1:1: D100 Missing docstring in public module - -:1:1: CPY001 Missing copyright notice at top of file - -:1:1: RUF900 Hey this is a stable test rule. - -:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix. - -:1:1: RUF902 Hey this is a stable test rule with an unsafe fix. - -:1:1: RUF903 Hey this is a stable test rule with a display only fix. - -:1:1: RUF911 Hey this is a preview test rule. - -:1:1: RUF950 Hey this is a test rule that was redirected from another. + -:1:1: error[D100] Missing docstring in public module + -:1:1: error[CPY001] Missing copyright notice at top of file + -:1:1: error[RUF900] Hey this is a stable test rule. + -:1:1: error[RUF901] [*] Hey this is a stable test rule with a safe fix. + -:1:1: error[RUF902] Hey this is a stable test rule with an unsafe fix. + -:1:1: error[RUF903] Hey this is a stable test rule with a display only fix. + -:1:1: error[RUF911] Hey this is a preview test rule. + -:1:1: error[RUF950] Hey this is a test rule that was redirected from another. Found 8 errors. [*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option). ----- stderr ----- warning: `incorrect-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `incorrect-blank-line-before-class`. warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`. - "); + "###); } #[test] @@ -1303,15 +1303,15 @@ fn preview_enabled_direct() { let mut cmd = RuffCheck::default() .args(["--select", "RUF911", "--output-format=concise", "--preview"]) .build(); - assert_cmd_snapshot!(cmd, @" + assert_cmd_snapshot!(cmd, @r###" success: false exit_code: 1 ----- stdout ----- - -:1:1: RUF911 Hey this is a preview test rule. + -:1:1: error[RUF911] Hey this is a preview test rule. Found 1 error. ----- stderr ----- - "); + "###); } #[test] @@ -1417,21 +1417,21 @@ fn preview_enabled_group_ignore() { "--output-format=concise", ]) .build(); - assert_cmd_snapshot!(cmd, @" + assert_cmd_snapshot!(cmd, @r###" success: false exit_code: 1 ----- stdout ----- - -:1:1: RUF900 Hey this is a stable test rule. - -:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix. - -:1:1: RUF902 Hey this is a stable test rule with an unsafe fix. - -:1:1: RUF903 Hey this is a stable test rule with a display only fix. - -:1:1: RUF911 Hey this is a preview test rule. - -:1:1: RUF950 Hey this is a test rule that was redirected from another. + -:1:1: error[RUF900] Hey this is a stable test rule. + -:1:1: error[RUF901] [*] Hey this is a stable test rule with a safe fix. + -:1:1: error[RUF902] Hey this is a stable test rule with an unsafe fix. + -:1:1: error[RUF903] Hey this is a stable test rule with a display only fix. + -:1:1: error[RUF911] Hey this is a preview test rule. + -:1:1: error[RUF950] Hey this is a test rule that was redirected from another. Found 6 errors. [*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option). ----- stderr ----- - "); + "###); } #[test] @@ -2397,11 +2397,11 @@ select = ["RUF017"] let mut cmd = RuffCheck::default().config(&ruff_toml).build(); assert_cmd_snapshot!(cmd .pass_stdin("x = [1, 2, 3]\ny = [4, 5, 6]\nsum([x, y], [])"), - @" + @r###" success: false exit_code: 1 ----- stdout ----- - RUF017 Avoid quadratic list summation + error[RUF017]: Avoid quadratic list summation --> -:3:1 | 1 | x = [1, 2, 3] @@ -2415,7 +2415,7 @@ select = ["RUF017"] No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option). ----- stderr ----- - "); + "###); Ok(()) } @@ -2438,11 +2438,11 @@ unfixable = ["RUF"] let mut cmd = RuffCheck::default().config(&ruff_toml).build(); assert_cmd_snapshot!(cmd .pass_stdin("x = [1, 2, 3]\ny = [4, 5, 6]\nsum([x, y], [])"), - @" + @r###" success: false exit_code: 1 ----- stdout ----- - RUF017 Avoid quadratic list summation + error[RUF017]: Avoid quadratic list summation --> -:3:1 | 1 | x = [1, 2, 3] @@ -2455,7 +2455,7 @@ unfixable = ["RUF"] Found 1 error. ----- stderr ----- - "); + "###); Ok(()) } diff --git a/crates/ruff/tests/snapshots/integration_test__stdin_json.snap b/crates/ruff/tests/snapshots/integration_test__stdin_json.snap index 6a3137aa753bf..415eb72756b9d 100644 --- a/crates/ruff/tests/snapshots/integration_test__stdin_json.snap +++ b/crates/ruff/tests/snapshots/integration_test__stdin_json.snap @@ -48,6 +48,7 @@ exit_code: 1 }, "message": "`os` imported but unused", "noqa_row": 1, + "severity": "error", "url": "https://docs.astral.sh/ruff/rules/unused-import" } ] diff --git a/crates/ruff_db/src/diagnostic/mod.rs b/crates/ruff_db/src/diagnostic/mod.rs index 6f14e1aebb324..9dcac74ecc6a4 100644 --- a/crates/ruff_db/src/diagnostic/mod.rs +++ b/crates/ruff_db/src/diagnostic/mod.rs @@ -5,6 +5,8 @@ use ruff_source_file::{LineColumn, SourceCode, SourceFile}; use ruff_annotate_snippets::Level as AnnotateLevel; use ruff_text_size::{Ranged, TextRange, TextSize}; +#[cfg(feature = "serde")] +use serde::Serialize; pub use self::render::{ DisplayDiagnostic, DisplayDiagnostics, DummyFileResolver, FileResolver, Input, @@ -1277,6 +1279,8 @@ impl From for Span { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, get_size2::GetSize)] +#[cfg_attr(feature = "serde", derive(Serialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] pub enum Severity { Info, Warning, diff --git a/crates/ruff_db/src/diagnostic/render.rs b/crates/ruff_db/src/diagnostic/render.rs index e41763e466b7a..68a6673094781 100644 --- a/crates/ruff_db/src/diagnostic/render.rs +++ b/crates/ruff_db/src/diagnostic/render.rs @@ -248,7 +248,7 @@ impl<'a> ResolvedDiagnostic<'a> { |code| code.to_string(), ) } else { - diag.inner.id.to_string() + diag.secondary_code_or_id().to_string() }; let level = if config.hide_severity { diff --git a/crates/ruff_db/src/diagnostic/render/concise.rs b/crates/ruff_db/src/diagnostic/render/concise.rs index 97ae8e0ec1084..518b38a095398 100644 --- a/crates/ruff_db/src/diagnostic/render/concise.rs +++ b/crates/ruff_db/src/diagnostic/render/concise.rs @@ -91,12 +91,6 @@ impl<'a> ConciseRenderer<'a> { ) )?; } - if self.config.show_fix_status { - // Do not display an indicator for inapplicable fixes - if diag.has_applicable_fix(self.config) { - write!(f, "[{fix}] ", fix = fmt_styled("*", stylesheet.separator))?; - } - } } else { let (severity, severity_style) = match diag.severity() { Severity::Info => ("info", stylesheet.info), @@ -109,11 +103,21 @@ impl<'a> ConciseRenderer<'a> { "{severity}[{id}] ", severity = fmt_styled(severity, severity_style), id = fmt_styled( - fmt_with_hyperlink(&diag.id(), diag.documentation_url(), &stylesheet), + fmt_with_hyperlink( + &diag.secondary_code_or_id(), + diag.documentation_url(), + &stylesheet + ), stylesheet.emphasis ) )?; } + if self.config.show_fix_status { + // Do not display an indicator for inapplicable fixes + if diag.has_applicable_fix(self.config) { + write!(f, "[{fix}] ", fix = fmt_styled("*", stylesheet.separator))?; + } + } writeln!(f, "{message}", message = diag.concise_message())?; } @@ -137,12 +141,12 @@ mod tests { #[test] fn output() { let (env, diagnostics) = create_diagnostics(DiagnosticFormat::Concise); - insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @" - fib.py:1:8: error[unused-import] `os` imported but unused - fib.py:6:5: error[unused-variable] Local variable `x` is assigned to but never used - undef.py:1:4: error[undefined-name] Undefined name `a` - fib.py:12:16: error[undefined-name] Undefined name `fibonaccii` - "); + insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r###" + fib.py:1:8: error[F401] `os` imported but unused + fib.py:6:5: error[F841] Local variable `x` is assigned to but never used + undef.py:1:4: error[F821] Undefined name `a` + fib.py:12:16: error[F821] Undefined name `fibonaccii` + "###); } #[test] @@ -198,11 +202,11 @@ mod tests { #[test] fn notebook_output() { let (env, diagnostics) = create_notebook_diagnostics(DiagnosticFormat::Concise); - insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @" - notebook.ipynb:cell 1:2:8: error[unused-import] `os` imported but unused - notebook.ipynb:cell 2:2:8: error[unused-import] `math` imported but unused - notebook.ipynb:cell 3:4:5: error[unused-variable] Local variable `x` is assigned to but never used - "); + insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r###" + notebook.ipynb:cell 1:2:8: error[F401] `os` imported but unused + notebook.ipynb:cell 2:2:8: error[F401] `math` imported but unused + notebook.ipynb:cell 3:4:5: error[F841] Local variable `x` is assigned to but never used + "###); } #[test] diff --git a/crates/ruff_db/src/diagnostic/render/full.rs b/crates/ruff_db/src/diagnostic/render/full.rs index ebe2de20a24fb..0d6b2cc6399cc 100644 --- a/crates/ruff_db/src/diagnostic/render/full.rs +++ b/crates/ruff_db/src/diagnostic/render/full.rs @@ -311,8 +311,8 @@ mod tests { #[test] fn output() { let (env, diagnostics) = create_diagnostics(DiagnosticFormat::Full); - insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r#" - error[unused-import]: `os` imported but unused + insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r###" + error[F401]: `os` imported but unused --> fib.py:1:8 | 1 | import os @@ -320,7 +320,7 @@ mod tests { | help: Remove unused import: `os` - error[unused-variable]: Local variable `x` is assigned to but never used + error[F841]: Local variable `x` is assigned to but never used --> fib.py:6:5 | 4 | def fibonacci(n): @@ -332,14 +332,14 @@ mod tests { | help: Remove assignment to unused variable `x` - error[undefined-name]: Undefined name `a` + error[F821]: Undefined name `a` --> undef.py:1:4 | 1 | if a == 1: pass | ^ | - error[undefined-name]: Undefined name `fibonaccii` + error[F821]: Undefined name `fibonaccii` --> fib.py:12:16 | 10 | return 1 @@ -357,7 +357,7 @@ mod tests { 6 | x = 1 7 | if n == 0: | - "#); + "###); } #[test] @@ -630,8 +630,8 @@ print() fn notebook_output() { let (mut env, diagnostics) = create_notebook_diagnostics(DiagnosticFormat::Full); env.show_fix_status(true); - insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r" - error[unused-import][*]: `os` imported but unused + insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r###" + error[F401][*]: `os` imported but unused --> notebook.ipynb:cell 1:2:8 | 1 | # cell 1 @@ -640,7 +640,7 @@ print() | help: Remove unused import: `os` - error[unused-import][*]: `math` imported but unused + error[F401][*]: `math` imported but unused --> notebook.ipynb:cell 2:2:8 | 1 | # cell 2 @@ -651,7 +651,7 @@ print() | help: Remove unused import: `math` - error[unused-variable]: Local variable `x` is assigned to but never used + error[F841]: Local variable `x` is assigned to but never used --> notebook.ipynb:cell 3:4:5 | 2 | def foo(): @@ -660,7 +660,7 @@ print() | ^ | help: Remove assignment to unused variable `x` - "); + "###); } /// Check notebook handling for multiple annotations in a single diagnostic that span cells. diff --git a/crates/ruff_db/src/diagnostic/render/json.rs b/crates/ruff_db/src/diagnostic/render/json.rs index 944ce49b3a1ba..93cb0e0347b56 100644 --- a/crates/ruff_db/src/diagnostic/render/json.rs +++ b/crates/ruff_db/src/diagnostic/render/json.rs @@ -6,7 +6,9 @@ use ruff_notebook::NotebookIndex; use ruff_source_file::{LineColumn, OneIndexed}; use ruff_text_size::Ranged; -use crate::diagnostic::{ConciseMessage, Diagnostic, DiagnosticSource, DisplayDiagnosticConfig}; +use crate::diagnostic::{ + ConciseMessage, Diagnostic, DiagnosticSource, DisplayDiagnosticConfig, Severity, +}; use super::FileResolver; @@ -96,10 +98,12 @@ 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 + // and the severity is displayed. if config.preview { JsonDiagnostic { code: diagnostic.secondary_code_or_id(), + severity: diagnostic.severity(), url: diagnostic.documentation_url(), message: diagnostic.concise_message(), fix, @@ -112,6 +116,7 @@ pub(super) fn diagnostic_to_json<'a>( } else { JsonDiagnostic { code: diagnostic.secondary_code_or_id(), + severity: Severity::Error, url: diagnostic.documentation_url(), message: diagnostic.concise_message(), fix, @@ -222,6 +227,7 @@ impl Serialize for ExpandedEdits<'_> { pub(crate) struct JsonDiagnostic<'a> { cell: Option, code: &'a str, + severity: Severity, end_location: Option, filename: Option<&'a str>, fix: Option>, @@ -301,7 +307,7 @@ mod tests { insta::assert_snapshot!( env.render(&diag), - @r#" + @r###" [ { "cell": null, @@ -318,10 +324,11 @@ mod tests { }, "message": "main diagnostic message", "noqa_row": null, + "severity": "error", "url": "https://docs.astral.sh/ruff/rules/test-diagnostic" } ] - "#, + "###, ); } @@ -338,7 +345,7 @@ mod tests { insta::assert_snapshot!( env.render(&diag), - @r#" + @r###" [ { "cell": null, @@ -349,10 +356,11 @@ mod tests { "location": null, "message": "main diagnostic message", "noqa_row": null, + "severity": "error", "url": "https://docs.astral.sh/ruff/rules/test-diagnostic" } ] - "#, + "###, ); } } diff --git a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__full__tests__notebook_output_with_diff.snap b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__full__tests__notebook_output_with_diff.snap index f35f559a544e6..9b246561711bc 100644 --- a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__full__tests__notebook_output_with_diff.snap +++ b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__full__tests__notebook_output_with_diff.snap @@ -2,7 +2,7 @@ source: crates/ruff_db/src/diagnostic/render/full.rs expression: env.render_diagnostics(&diagnostics) --- -error[unused-import][*]: `os` imported but unused +error[F401][*]: `os` imported but unused --> notebook.ipynb:cell 1:2:8 | 1 | # cell 1 @@ -14,7 +14,7 @@ help: Remove unused import: `os` 1 | # cell 1 - import os -error[unused-import][*]: `math` imported but unused +error[F401][*]: `math` imported but unused --> notebook.ipynb:cell 2:2:8 | 1 | # cell 2 @@ -30,7 +30,7 @@ help: Remove unused import: `math` 2 | 3 | print('hello world') -error[unused-variable][*]: Local variable `x` is assigned to but never used +error[F841][*]: Local variable `x` is assigned to but never used --> notebook.ipynb:cell 3:4:5 | 2 | def foo(): diff --git a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__full__tests__notebook_output_with_diff_spanning_cells.snap b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__full__tests__notebook_output_with_diff_spanning_cells.snap index 63a920eff65ce..5f1d1e315bfdd 100644 --- a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__full__tests__notebook_output_with_diff_spanning_cells.snap +++ b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__full__tests__notebook_output_with_diff_spanning_cells.snap @@ -2,7 +2,7 @@ source: crates/ruff_db/src/diagnostic/render/full.rs expression: env.render(&diagnostic) --- -error[unused-import][*]: `os` imported but unused +error[F401][*]: `os` imported but unused --> notebook.ipynb:cell 1:2:8 | 1 | # cell 1 diff --git a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json__tests__notebook_output.snap b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json__tests__notebook_output.snap index e8622f7bc96e4..ec9bb5e9d874f 100644 --- a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json__tests__notebook_output.snap +++ b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json__tests__notebook_output.snap @@ -34,6 +34,7 @@ expression: env.render_diagnostics(&diagnostics) }, "message": "`os` imported but unused", "noqa_row": 2, + "severity": "error", "url": "https://docs.astral.sh/ruff/rules/unused-import" }, { @@ -67,6 +68,7 @@ expression: env.render_diagnostics(&diagnostics) }, "message": "`math` imported but unused", "noqa_row": 2, + "severity": "error", "url": "https://docs.astral.sh/ruff/rules/unused-import" }, { @@ -100,6 +102,7 @@ expression: env.render_diagnostics(&diagnostics) }, "message": "Local variable `x` is assigned to but never used", "noqa_row": 4, + "severity": "error", "url": "https://docs.astral.sh/ruff/rules/unused-variable" } ] diff --git a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json__tests__output.snap b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json__tests__output.snap index 79fea8b21074f..46e669a1f56be 100644 --- a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json__tests__output.snap +++ b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json__tests__output.snap @@ -34,6 +34,7 @@ expression: env.render_diagnostics(&diagnostics) }, "message": "`os` imported but unused", "noqa_row": 1, + "severity": "error", "url": "https://docs.astral.sh/ruff/rules/unused-import" }, { @@ -67,6 +68,7 @@ expression: env.render_diagnostics(&diagnostics) }, "message": "Local variable `x` is assigned to but never used", "noqa_row": 6, + "severity": "error", "url": "https://docs.astral.sh/ruff/rules/unused-variable" }, { @@ -84,6 +86,7 @@ expression: env.render_diagnostics(&diagnostics) }, "message": "Undefined name `a`", "noqa_row": 1, + "severity": "error", "url": "https://docs.astral.sh/ruff/rules/undefined-name" }, { @@ -101,6 +104,7 @@ expression: env.render_diagnostics(&diagnostics) }, "message": "Undefined name `fibonaccii`", "noqa_row": 1, + "severity": "error", "url": "https://docs.astral.sh/ruff/rules/undefined-name" } ] diff --git a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json__tests__syntax_errors.snap b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json__tests__syntax_errors.snap index 52b7f91c192d9..8b6434916f4c0 100644 --- a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json__tests__syntax_errors.snap +++ b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json__tests__syntax_errors.snap @@ -18,6 +18,7 @@ expression: env.render_diagnostics(&diagnostics) }, "message": "Expected one or more symbol names after import", "noqa_row": null, + "severity": "error", "url": null }, { @@ -35,6 +36,7 @@ expression: env.render_diagnostics(&diagnostics) }, "message": "Expected ')', found newline", "noqa_row": null, + "severity": "error", "url": null } ] diff --git a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json_lines__tests__notebook_output.snap b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json_lines__tests__notebook_output.snap index 87666b075c33c..8323a2299b2c5 100644 --- a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json_lines__tests__notebook_output.snap +++ b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json_lines__tests__notebook_output.snap @@ -2,6 +2,6 @@ source: crates/ruff_db/src/diagnostic/render/json_lines.rs expression: env.render_diagnostics(&diagnostics) --- -{"cell":1,"code":"F401","end_location":{"column":10,"row":2},"filename":"/notebook.ipynb","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":10,"row":2},"location":{"column":1,"row":2}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":2},"message":"`os` imported but unused","noqa_row":2,"url":"https://docs.astral.sh/ruff/rules/unused-import"} -{"cell":2,"code":"F401","end_location":{"column":12,"row":2},"filename":"/notebook.ipynb","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":1,"row":3},"location":{"column":1,"row":2}}],"message":"Remove unused import: `math`"},"location":{"column":8,"row":2},"message":"`math` imported but unused","noqa_row":2,"url":"https://docs.astral.sh/ruff/rules/unused-import"} -{"cell":3,"code":"F841","end_location":{"column":6,"row":4},"filename":"/notebook.ipynb","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":1,"row":5},"location":{"column":1,"row":4}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":4},"message":"Local variable `x` is assigned to but never used","noqa_row":4,"url":"https://docs.astral.sh/ruff/rules/unused-variable"} +{"cell":1,"code":"F401","end_location":{"column":10,"row":2},"filename":"/notebook.ipynb","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":10,"row":2},"location":{"column":1,"row":2}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":2},"message":"`os` imported but unused","noqa_row":2,"severity":"error","url":"https://docs.astral.sh/ruff/rules/unused-import"} +{"cell":2,"code":"F401","end_location":{"column":12,"row":2},"filename":"/notebook.ipynb","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":1,"row":3},"location":{"column":1,"row":2}}],"message":"Remove unused import: `math`"},"location":{"column":8,"row":2},"message":"`math` imported but unused","noqa_row":2,"severity":"error","url":"https://docs.astral.sh/ruff/rules/unused-import"} +{"cell":3,"code":"F841","end_location":{"column":6,"row":4},"filename":"/notebook.ipynb","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":1,"row":5},"location":{"column":1,"row":4}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":4},"message":"Local variable `x` is assigned to but never used","noqa_row":4,"severity":"error","url":"https://docs.astral.sh/ruff/rules/unused-variable"} diff --git a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json_lines__tests__output.snap b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json_lines__tests__output.snap index 5623ed9ec25a3..d28cd846a6c68 100644 --- a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json_lines__tests__output.snap +++ b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json_lines__tests__output.snap @@ -2,7 +2,7 @@ source: crates/ruff_db/src/diagnostic/render/json_lines.rs expression: env.render_diagnostics(&diagnostics) --- -{"cell":null,"code":"F401","end_location":{"column":10,"row":1},"filename":"/fib.py","fix":{"applicability":"unsafe","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":"F841","end_location":{"column":6,"row":6},"filename":"/fib.py","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":10,"row":6},"location":{"column":5,"row":6}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":6},"message":"Local variable `x` is assigned to but never used","noqa_row":6,"url":"https://docs.astral.sh/ruff/rules/unused-variable"} -{"cell":null,"code":"F821","end_location":{"column":5,"row":1},"filename":"/undef.py","fix":null,"location":{"column":4,"row":1},"message":"Undefined name `a`","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/undefined-name"} -{"cell":null,"code":"F821","end_location":{"column":26,"row":12},"filename":"/fib.py","fix":null,"location":{"column":16,"row":12},"message":"Undefined name `fibonaccii`","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/undefined-name"} +{"cell":null,"code":"F401","end_location":{"column":10,"row":1},"filename":"/fib.py","fix":{"applicability":"unsafe","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,"severity":"error","url":"https://docs.astral.sh/ruff/rules/unused-import"} +{"cell":null,"code":"F841","end_location":{"column":6,"row":6},"filename":"/fib.py","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":10,"row":6},"location":{"column":5,"row":6}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":6},"message":"Local variable `x` is assigned to but never used","noqa_row":6,"severity":"error","url":"https://docs.astral.sh/ruff/rules/unused-variable"} +{"cell":null,"code":"F821","end_location":{"column":5,"row":1},"filename":"/undef.py","fix":null,"location":{"column":4,"row":1},"message":"Undefined name `a`","noqa_row":1,"severity":"error","url":"https://docs.astral.sh/ruff/rules/undefined-name"} +{"cell":null,"code":"F821","end_location":{"column":26,"row":12},"filename":"/fib.py","fix":null,"location":{"column":16,"row":12},"message":"Undefined name `fibonaccii`","noqa_row":1,"severity":"error","url":"https://docs.astral.sh/ruff/rules/undefined-name"} diff --git a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json_lines__tests__syntax_errors.snap b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json_lines__tests__syntax_errors.snap index 498a876d9e1ff..f7e5f9f7f8fb9 100644 --- a/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json_lines__tests__syntax_errors.snap +++ b/crates/ruff_db/src/diagnostic/render/snapshots/ruff_db__diagnostic__render__json_lines__tests__syntax_errors.snap @@ -2,5 +2,5 @@ source: crates/ruff_db/src/diagnostic/render/json_lines.rs expression: env.render_diagnostics(&diagnostics) --- -{"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":"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} +{"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,"severity":"error","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,"severity":"error","url":null} diff --git a/crates/ruff_linter/src/message/sarif.rs b/crates/ruff_linter/src/message/sarif.rs index 2ce9003689e5a..cc669055e6cd8 100644 --- a/crates/ruff_linter/src/message/sarif.rs +++ b/crates/ruff_linter/src/message/sarif.rs @@ -6,7 +6,7 @@ use anyhow::Result; use log::warn; use serde::{Serialize, Serializer}; -use ruff_db::diagnostic::{Diagnostic, SecondaryCode}; +use ruff_db::diagnostic::{Diagnostic, SecondaryCode, Severity}; use ruff_source_file::{OneIndexed, SourceFile}; use ruff_text_size::{Ranged, TextRange}; @@ -34,13 +34,28 @@ impl Emitter for SarifEmitter { .map(SarifResult::from_message) .collect::>>()?; - let unique_rules: HashSet<_> = results + let unique_rules_and_severities: HashSet<_> = results .iter() - .filter_map(|result| result.rule_id.as_secondary_code()) + // Here we implicitly assume that, for a fixed rule, all diagnostics + // have the same severity. If we ever introduce a way for users + // to locally change the severity of a diagnostic we will have to + // refactor in such a way that we know the _configured_ severity at + // this point (rather than just the severities of each diagnostic). + .filter_map(|result| { + let code = result.rule_id.as_secondary_code()?; + Some((code, result.level)) + }) .collect(); - let mut rules: Vec = unique_rules.into_iter().map(SarifRule::from).collect(); - rules.sort_by(|a, b| a.id.cmp(b.id)); - + let mut rules: Vec = unique_rules_and_severities + .into_iter() + .map(SarifRule::from) + .collect(); + rules.sort_by(|a, b| { + a.properties + .problem_severity + .cmp(&b.properties.problem_severity) + .then_with(|| a.id.cmp(b.id)) + }); let output = SarifOutput { schema: "https://json.schemastore.org/sarif-2.1.0.json", version: "2.1.0", @@ -102,8 +117,9 @@ struct SarifRule<'a> { short_description: SarifMessage<'a>, } -impl<'a> From<&'a SecondaryCode> for SarifRule<'a> { - fn from(code: &'a SecondaryCode) -> Self { +impl<'a> From<(&'a SecondaryCode, SarifLevel)> for SarifRule<'a> { + fn from(code_and_level: (&'a SecondaryCode, SarifLevel)) -> Self { + let (code, level) = code_and_level; // This is a manual re-implementation of Rule::from_code, but we also want the Linter. This // avoids calling Linter::parse_code twice. let (linter, suffix) = Linter::parse_code(code).unwrap(); @@ -127,7 +143,7 @@ impl<'a> From<&'a SecondaryCode> for SarifRule<'a> { id: code, kind: linter.name(), name: rule.into(), - problem_severity: "error", + problem_severity: level, }, } } @@ -182,7 +198,7 @@ impl<'a> From<&'a Diagnostic> for RuleCode<'a> { struct SarifResult<'a> { #[serde(skip_serializing_if = "Vec::is_empty")] fixes: Vec, - level: String, + level: SarifLevel, locations: Vec, message: SarifMessage<'a>, rule_id: RuleCode<'a>, @@ -262,7 +278,25 @@ struct SarifProperties<'a> { kind: &'a str, name: &'a str, #[serde(rename = "problem.severity")] - problem_severity: &'static str, + problem_severity: SarifLevel, +} + +#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize)] +#[serde(rename_all = "camelCase")] +enum SarifLevel { + Note, + Warning, + Error, +} + +impl From for SarifLevel { + fn from(value: Severity) -> Self { + match value { + Severity::Info => SarifLevel::Note, + Severity::Warning => SarifLevel::Warning, + Severity::Error | Severity::Fatal => SarifLevel::Error, + } + } } impl<'a> SarifResult<'a> { @@ -355,7 +389,7 @@ impl<'a> SarifResult<'a> { Ok(Self { rule_id: RuleCode::from(diagnostic), - level: "error".to_string(), + level: SarifLevel::from(diagnostic.severity()), message: SarifMessage { text: diagnostic.concise_message().to_str(), }, diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index f0be5463ca1ff..e3d2e43601e88 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -319,3 +319,8 @@ pub(crate) const fn is_incorrect_dict_iterator_comprehension_enabled( pub(crate) const fn is_up006_future_annotations_fix_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() } + +// https://github.com/astral-sh/ruff/pull/23845 +pub const fn is_warning_severity_enabled(preview: PreviewMode) -> bool { + preview.is_enabled() +} diff --git a/crates/ruff_server/src/lint.rs b/crates/ruff_server/src/lint.rs index b930b6517f6ed..01dda9f9a27df 100644 --- a/crates/ruff_server/src/lint.rs +++ b/crates/ruff_server/src/lint.rs @@ -19,7 +19,7 @@ use ruff_linter::{ linter::check_path, package::PackageRoot, packaging::detect_package_root, - settings::flags, + settings::{flags, types::PreviewMode}, source_kind::SourceKind, suppression::Suppressions, }; @@ -181,6 +181,7 @@ pub(crate) fn check( &source_kind, locator.to_index(), encoding, + settings.linter.preview, )) } }); @@ -242,6 +243,7 @@ fn to_lsp_diagnostic( source_kind: &SourceKind, index: &LineIndex, encoding: PositionEncoding, + preview: PreviewMode, ) -> (usize, lsp_types::Diagnostic) { let diagnostic_range = diagnostic.range().unwrap_or_default(); let name = diagnostic.name(); @@ -292,7 +294,9 @@ fn to_lsp_diagnostic( range = diagnostic_range.to_range(source_kind.source_code(), index, encoding); } - let (severity, code) = if let Some(code) = code { + let (severity, code) = if let Some(code) = code + && preview.is_disabled() + { (severity(code), code.to_string()) } else { ( @@ -302,7 +306,7 @@ fn to_lsp_diagnostic( ruff_db::diagnostic::Severity::Error => lsp_types::DiagnosticSeverity::ERROR, ruff_db::diagnostic::Severity::Fatal => lsp_types::DiagnosticSeverity::ERROR, }, - diagnostic.id().to_string(), + diagnostic.secondary_code_or_id().to_string(), ) }; diff --git a/python/ruff-ecosystem/ruff_ecosystem/check.py b/python/ruff-ecosystem/ruff_ecosystem/check.py index b1d2af54a8b3b..ceea0e693d1c1 100644 --- a/python/ruff-ecosystem/ruff_ecosystem/check.py +++ b/python/ruff-ecosystem/ruff_ecosystem/check.py @@ -45,8 +45,11 @@ r"^(?P
[+-]) (?P(?P[^:]+):(?P\d+):\d+:) (?P.*)$",
 )
 
+# A little permissive - it allows mismatched brackets around
+# the severity. But should be good enough while we support both
+# the old and new rendering with severities.
 CHECK_DIAGNOSTIC_LINE_RE = re.compile(
-    r"^(?P[+-])? ?(?P.*): (?P[A-Z]{1,4}[0-9]{3,4}|[a-z\-]+:)(?P \[\*\])? (?P.*)"
+    r"^(?P[+-])? ?(?P.*): (?:(?P[a-z]+)\[)?(?P[A-Z]{1,4}[0-9]{3,4}|[a-z\-]+:)\]?(?P \[\*\])? (?P.*)"
 )
 
 PANIC_DIAGNOSTIC_LINE_RE = re.compile(r"^[^:]+: panic: Panicked at ")