diff --git a/crates/ruff/tests/lint.rs b/crates/ruff/tests/lint.rs index ffba3a8e90..41cae9080e 100644 --- a/crates/ruff/tests/lint.rs +++ b/crates/ruff/tests/lint.rs @@ -5692,3 +5692,56 @@ class Foo: " ); } + +#[test_case::test_case("concise")] +#[test_case::test_case("full")] +#[test_case::test_case("json")] +#[test_case::test_case("json-lines")] +#[test_case::test_case("junit")] +#[test_case::test_case("grouped")] +#[test_case::test_case("github")] +#[test_case::test_case("gitlab")] +#[test_case::test_case("pylint")] +#[test_case::test_case("rdjson")] +#[test_case::test_case("azure")] +#[test_case::test_case("sarif")] +fn output_format(output_format: &str) -> Result<()> { + const CONTENT: &str = "\ +import os # F401 +x = y # F821 +match 42: # invalid-syntax + case _: ... +"; + + let tempdir = TempDir::new()?; + let input = tempdir.path().join("input.py"); + fs::write(&input, CONTENT)?; + + let snapshot = format!("output_format_{output_format}"); + + insta::with_settings!({ + filters => vec![ + (tempdir_filter(&tempdir).as_str(), "[TMP]/"), + (r#""[^"]+\\?/?input.py"#, r#""[TMP]/input.py"#), + ] + }, { + assert_cmd_snapshot!( + snapshot, + Command::new(get_cargo_bin(BIN_NAME)) + .args([ + "check", + "--no-cache", + "--output-format", + output_format, + "--select", + "F401,F821", + "--target-version", + "py39", + "input.py", + ]) + .current_dir(&tempdir), + ); + }); + + Ok(()) +} diff --git a/crates/ruff/tests/snapshots/lint__output_format_azure.snap b/crates/ruff/tests/snapshots/lint__output_format_azure.snap new file mode 100644 index 0000000000..36ba1f8790 --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__output_format_azure.snap @@ -0,0 +1,23 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - azure + - "--select" + - "F401,F821" + - "--target-version" + - py39 + - input.py +--- +success: false +exit_code: 1 +----- 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=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) + +----- stderr ----- diff --git a/crates/ruff/tests/snapshots/lint__output_format_concise.snap b/crates/ruff/tests/snapshots/lint__output_format_concise.snap new file mode 100644 index 0000000000..9b9e4fd0f3 --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__output_format_concise.snap @@ -0,0 +1,25 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - concise + - "--select" + - "F401,F821" + - "--target-version" + - py39 + - input.py +--- +success: false +exit_code: 1 +----- stdout ----- +input.py:1:8: F401 [*] `os` imported but unused +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) +Found 3 errors. +[*] 1 fixable with the `--fix` option. + +----- stderr ----- diff --git a/crates/ruff/tests/snapshots/lint__output_format_full.snap b/crates/ruff/tests/snapshots/lint__output_format_full.snap new file mode 100644 index 0000000000..d9527c596f --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__output_format_full.snap @@ -0,0 +1,49 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - full + - "--select" + - "F401,F821" + - "--target-version" + - py39 + - input.py +--- +success: false +exit_code: 1 +----- stdout ----- +input.py:1:8: F401 [*] `os` imported but unused + | +1 | import os # F401 + | ^^ F401 +2 | x = y # F821 +3 | match 42: # invalid-syntax + | + = help: Remove unused import: `os` + +input.py:2:5: F821 Undefined name `y` + | +1 | import os # F401 +2 | x = y # F821 + | ^ F821 +3 | match 42: # invalid-syntax +4 | case _: ... + | + +input.py:3:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) + | +1 | import os # F401 +2 | x = y # F821 +3 | match 42: # invalid-syntax + | ^^^^^ +4 | case _: ... + | + +Found 3 errors. +[*] 1 fixable with the `--fix` option. + +----- stderr ----- diff --git a/crates/ruff/tests/snapshots/lint__output_format_github.snap b/crates/ruff/tests/snapshots/lint__output_format_github.snap new file mode 100644 index 0000000000..3f38f23652 --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__output_format_github.snap @@ -0,0 +1,23 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - github + - "--select" + - "F401,F821" + - "--target-version" + - py39 + - input.py +--- +success: false +exit_code: 1 +----- 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 (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) + +----- stderr ----- diff --git a/crates/ruff/tests/snapshots/lint__output_format_gitlab.snap b/crates/ruff/tests/snapshots/lint__output_format_gitlab.snap new file mode 100644 index 0000000000..e25a54bfc3 --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__output_format_gitlab.snap @@ -0,0 +1,60 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - gitlab + - "--select" + - "F401,F821" + - "--target-version" + - py39 + - input.py +--- +success: false +exit_code: 1 +----- stdout ----- +[ + { + "check_name": "F401", + "description": "`os` imported but unused", + "fingerprint": "4dbad37161e65c72", + "location": { + "lines": { + "begin": 1, + "end": 1 + }, + "path": "input.py" + }, + "severity": "major" + }, + { + "check_name": "F821", + "description": "Undefined name `y`", + "fingerprint": "7af59862a085230", + "location": { + "lines": { + "begin": 2, + "end": 2 + }, + "path": "input.py" + }, + "severity": "major" + }, + { + "check_name": "syntax-error", + "description": "Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)", + "fingerprint": "e558cec859bb66e8", + "location": { + "lines": { + "begin": 3, + "end": 3 + }, + "path": "input.py" + }, + "severity": "major" + } +] +----- stderr ----- diff --git a/crates/ruff/tests/snapshots/lint__output_format_grouped.snap b/crates/ruff/tests/snapshots/lint__output_format_grouped.snap new file mode 100644 index 0000000000..378ffe9d37 --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__output_format_grouped.snap @@ -0,0 +1,27 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - grouped + - "--select" + - "F401,F821" + - "--target-version" + - py39 + - input.py +--- +success: false +exit_code: 1 +----- stdout ----- +input.py: + 1:8 F401 [*] `os` imported but unused + 2:5 F821 Undefined name `y` + 3:1 SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) + +Found 3 errors. +[*] 1 fixable with the `--fix` option. + +----- stderr ----- diff --git a/crates/ruff/tests/snapshots/lint__output_format_json-lines.snap b/crates/ruff/tests/snapshots/lint__output_format_json-lines.snap new file mode 100644 index 0000000000..efadfd1cbc --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__output_format_json-lines.snap @@ -0,0 +1,23 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - json-lines + - "--select" + - "F401,F821" + - "--target-version" + - py39 + - input.py +--- +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":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} + +----- stderr ----- diff --git a/crates/ruff/tests/snapshots/lint__output_format_json.snap b/crates/ruff/tests/snapshots/lint__output_format_json.snap new file mode 100644 index 0000000000..ca01d68600 --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__output_format_json.snap @@ -0,0 +1,88 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - json + - "--select" + - "F401,F821" + - "--target-version" + - py39 + - input.py +--- +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": 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 + } +] +----- stderr ----- diff --git a/crates/ruff/tests/snapshots/lint__output_format_junit.snap b/crates/ruff/tests/snapshots/lint__output_format_junit.snap new file mode 100644 index 0000000000..30b5739172 --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__output_format_junit.snap @@ -0,0 +1,34 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - junit + - "--select" + - "F401,F821" + - "--target-version" + - py39 + - input.py +--- +success: false +exit_code: 1 +----- stdout ----- + + + + + line 1, col 8, `os` imported but unused + + + line 2, col 5, Undefined name `y` + + + line 3, col 1, SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) + + + + +----- stderr ----- diff --git a/crates/ruff/tests/snapshots/lint__output_format_pylint.snap b/crates/ruff/tests/snapshots/lint__output_format_pylint.snap new file mode 100644 index 0000000000..8b30234a82 --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__output_format_pylint.snap @@ -0,0 +1,23 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - pylint + - "--select" + - "F401,F821" + - "--target-version" + - py39 + - input.py +--- +success: false +exit_code: 1 +----- stdout ----- +input.py:1: [F401] `os` imported but unused +input.py:2: [F821] Undefined name `y` +input.py:3: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) + +----- stderr ----- diff --git a/crates/ruff/tests/snapshots/lint__output_format_rdjson.snap b/crates/ruff/tests/snapshots/lint__output_format_rdjson.snap new file mode 100644 index 0000000000..65a188cbb7 --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__output_format_rdjson.snap @@ -0,0 +1,103 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - rdjson + - "--select" + - "F401,F821" + - "--target-version" + - py39 + - input.py +--- +success: false +exit_code: 1 +----- stdout ----- +{ + "diagnostics": [ + { + "code": { + "url": "https://docs.astral.sh/ruff/rules/unused-import", + "value": "F401" + }, + "location": { + "path": "[TMP]/input.py", + "range": { + "end": { + "column": 10, + "line": 1 + }, + "start": { + "column": 8, + "line": 1 + } + } + }, + "message": "`os` imported but unused", + "suggestions": [ + { + "range": { + "end": { + "column": 1, + "line": 2 + }, + "start": { + "column": 1, + "line": 1 + } + }, + "text": "" + } + ] + }, + { + "code": { + "url": "https://docs.astral.sh/ruff/rules/undefined-name", + "value": "F821" + }, + "location": { + "path": "[TMP]/input.py", + "range": { + "end": { + "column": 6, + "line": 2 + }, + "start": { + "column": 5, + "line": 2 + } + } + }, + "message": "Undefined name `y`" + }, + { + "code": { + "url": null, + "value": null + }, + "location": { + "path": "[TMP]/input.py", + "range": { + "end": { + "column": 6, + "line": 3 + }, + "start": { + "column": 1, + "line": 3 + } + } + }, + "message": "SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)" + } + ], + "severity": "warning", + "source": { + "name": "ruff", + "url": "https://docs.astral.sh/ruff" + } +} +----- stderr ----- diff --git a/crates/ruff/tests/snapshots/lint__output_format_sarif.snap b/crates/ruff/tests/snapshots/lint__output_format_sarif.snap new file mode 100644 index 0000000000..41c86c7a61 --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__output_format_sarif.snap @@ -0,0 +1,142 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - sarif + - "--select" + - "F401,F821" + - "--target-version" + - py39 + - input.py +--- +success: false +exit_code: 1 +----- stdout ----- +{ + "$schema": "https://json.schemastore.org/sarif-2.1.0.json", + "runs": [ + { + "results": [ + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "[TMP]/input.py" + }, + "region": { + "endColumn": 10, + "endLine": 1, + "startColumn": 8, + "startLine": 1 + } + } + } + ], + "message": { + "text": "`os` imported but unused" + }, + "ruleId": "F401" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "[TMP]/input.py" + }, + "region": { + "endColumn": 6, + "endLine": 2, + "startColumn": 5, + "startLine": 2 + } + } + } + ], + "message": { + "text": "Undefined name `y`" + }, + "ruleId": "F821" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "[TMP]/input.py" + }, + "region": { + "endColumn": 6, + "endLine": 3, + "startColumn": 1, + "startLine": 3 + } + } + } + ], + "message": { + "text": "SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)" + }, + "ruleId": null + } + ], + "tool": { + "driver": { + "informationUri": "https://github.com/astral-sh/ruff", + "name": "ruff", + "rules": [ + { + "fullDescription": { + "text": "## What it does\nChecks for unused imports.\n\n## Why is this bad?\nUnused imports add a performance overhead at runtime, and risk creating\nimport cycles. They also increase the cognitive load of reading the code.\n\nIf an import statement is used to check for the availability or existence\nof a module, consider using `importlib.util.find_spec` instead.\n\nIf an import statement is used to re-export a symbol as part of a module's\npublic interface, consider using a \"redundant\" import alias, which\ninstructs Ruff (and other tools) to respect the re-export, and avoid\nmarking it as unused, as in:\n\n```python\nfrom module import member as member\n```\n\nAlternatively, you can use `__all__` to declare a symbol as part of the module's\ninterface, as in:\n\n```python\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n```\n\n## Fix safety\n\nFixes to remove unused imports are safe, except in `__init__.py` files.\n\nApplying fixes to `__init__.py` files is currently in preview. The fix offered depends on the\ntype of the unused import. Ruff will suggest a safe fix to export first-party imports with\neither a redundant alias or, if already present in the file, an `__all__` entry. If multiple\n`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix\nto remove third-party and standard library imports -- the fix is unsafe because the module's\ninterface changes.\n\n## Example\n\n```python\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n```\n\nUse instead:\n\n```python\ndef area(radius):\n return 3.14 * radius**2\n```\n\nTo check the availability of a module, use `importlib.util.find_spec`:\n\n```python\nfrom importlib.util import find_spec\n\nif find_spec(\"numpy\") is not None:\n print(\"numpy is installed\")\nelse:\n print(\"numpy is not installed\")\n```\n\n## Preview\nWhen [preview](https://docs.astral.sh/ruff/preview/) is enabled,\nthe criterion for determining whether an import is first-party\nis stricter, which could affect the suggested fix. See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) for more details.\n\n## Options\n- `lint.ignore-init-module-imports`\n- `lint.pyflakes.allowed-unused-imports`\n\n## References\n- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)\n- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)\n- [Typing documentation: interface conventions](https://typing.python.org/en/latest/source/libraries.html#library-interface-public-and-private-symbols)\n" + }, + "help": { + "text": "`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability" + }, + "helpUri": "https://docs.astral.sh/ruff/rules/unused-import", + "id": "F401", + "properties": { + "id": "F401", + "kind": "Pyflakes", + "name": "unused-import", + "problem.severity": "error" + }, + "shortDescription": { + "text": "`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability" + } + }, + { + "fullDescription": { + "text": "## What it does\nChecks for uses of undefined names.\n\n## Why is this bad?\nAn undefined name is likely to raise `NameError` at runtime.\n\n## Example\n```python\ndef double():\n return n * 2 # raises `NameError` if `n` is undefined when `double` is called\n```\n\nUse instead:\n```python\ndef double(n):\n return n * 2\n```\n\n## Options\n- [`target-version`]: Can be used to configure which symbols Ruff will understand\n as being available in the `builtins` namespace.\n\n## References\n- [Python documentation: Naming and binding](https://docs.python.org/3/reference/executionmodel.html#naming-and-binding)\n" + }, + "help": { + "text": "Undefined name `{name}`. {tip}" + }, + "helpUri": "https://docs.astral.sh/ruff/rules/undefined-name", + "id": "F821", + "properties": { + "id": "F821", + "kind": "Pyflakes", + "name": "undefined-name", + "problem.severity": "error" + }, + "shortDescription": { + "text": "Undefined name `{name}`. {tip}" + } + } + ], + "version": "0.12.2" + } + } + } + ], + "version": "2.1.0" +} +----- stderr -----