aspect-build / aspect-build/rules_lint
[Bug]: ruff doesn't report any error on `--output_groups=rules_lint_machine`
- Dominant language
- Starlark
- Stars
- 154
- Forks
- 125
- Avg merge
- 3d 21h
- Merged PRs (30d)
- 20
Description
### What happened?
The latest version of `rules_lint` doesn't report any linting error with ruff on `--output_groups=rules_lint_machine`.
### Version
Development (host) and target OS/architectures: linux x86_64
Output of `bazel --version`:
```
% bazel --version
aspect 2025.51.5-80ea9104
```
Version of the Aspect rules, or other relevant rules from your
`WORKSPACE` or `MODULE.bazel` file:
```
bazel_dep(name = "aspect_rules_lint", version = "2.0.0-beta.0")
```
Language(s) and/or frameworks involved:
python / ruff
### How to reproduce
```shell
# repro.py
import json
import abc
def main() -> None:
pass
# BUILD
load("@aspect_rules_py//py:defs.bzl", "py_binary")
py_binary(
name = "repro",
srcs = ["repro.py"],
)
Reproduction
% bazel lint //:repro --fixes=false --aspect:interactive=false
INFO: Invocation ID: f7731e21-299d-4279-afb3-2b5e883a8fd9
WARNING: Build option --@@aspect_rules_lint+//lint:fix has changed, discarding analysis cache (this can be expensive, see http
s://bazel.build/advanced/performance/iteration-speed).
INFO: Analyzed target //:repro (0 packages loaded, 24635 targets configured).
INFO: Found 1 target...
Aspect //bazel/tools:aspects.bzl%ruff of //:repro up-to-date:
bazel-bin/repro.AspectRulesLintRuff.out
bazel-bin/repro.AspectRulesLintRuff.out.exit_code
INFO: Elapsed time: 2.161s, Critical Path: 0.02s
INFO: 2 processes: 5 action cache hit, 1 internal, 1 linux-sandbox.
INFO: Build completed successfully, 2 total actions
INFO: Build Event Protocol files produced successfully.
Lint results for //:repro:
I001 [*] Import block is un-sorted or un-formatted
--> repro.py:1:1
|
1 | / import json
2 | | import abc
| |__________^
3 |
4 | def main() -> None:
|
help: Organize imports
F401 [*] `json` imported but unused
--> repro.py:1:8
|
1 | import json
| ^^^^
2 | import abc
|
help: Remove unused import: `json`
F401 [*] `abc` imported but unused
--> repro.py:2:8
|
1 | import json
2 | import abc
| ^^^
3 |
4 | def main() -> None:
|
help: Remove unused import: `abc`
Found 3 errors.
[*] 3 fixable with the `--fix` option.
However,
% bazel lint //:repro --fixes=false --aspect:interactive=false --machine
INFO: Invocation ID: 3b6728ca-106c-4bd8-87be-fc77a48b783c
INFO: Analyzed target //:repro (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Aspect //bazel/tools:aspects.bzl%ruff of //:repro up-to-date:
bazel-bin/repro.AspectRulesLintRuff.report
bazel-bin/repro.AspectRulesLintRuff.report.exit_code
INFO: Elapsed time: 0.627s, Critical Path: 0.11s
INFO: 3 processes: 938 action cache hit, 1 internal, 2 linux-sandbox.
INFO: Build completed successfully, 3 total actions
INFO: Build Event Protocol files produced successfully.
Lint results for //:repro:
{
"$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json",
"runs": [
{
"tool": {
"driver": {
"name": "Ruff"
}
}
}
],
"version": "2.1.0"
}
Now, hacking
--- a/lint/ruff.bzl
+++ b/lint/ruff.bzl
@@ -101,6 +101,7 @@ def ruff_action(ctx, executable, srcs, config, stdout, exit_code = None, env = {
args = ctx.actions.args()
args.add("check")
args.add("--force-exclude")
+ args.add("--output-format sarif")
args.add_all(srcs)
if exit_code:
in `rules_lint` does produce the expected sarif output,
Lint results for //:repro:
{
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
"runs": [
{
"results": [
{
"fixes": [
{
"artifactChanges": [
{
"artifactLocation": {
"uri": "file:///home/mathiaslaurin/.cache/bazel/_bazel_mathiaslaurin/f934e6242b74fe50c3a53c5460a4f89a/sandbox/linux-sandbox/4452/execroot/_main/repro.py"
},
"replacements": [
{
"deletedRegion": {
"endColumn": 1,
"endLine": 4,
"startColumn": 1,
"startLine": 1
},
"insertedContent": {
"text": "import abc\nimport json\n\n\n"
}
}
]
}
],
"description": {
"text": "Organize imports"
}
}
],
"level": "error",
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///home/mathiaslaurin/.cache/bazel/_bazel_mathiaslaurin/f934e6242b74fe50c3a53c5460a4f89a/sandbox/linux-sandbox/4452/execroot/_main/repro.py"
},
"region": {
"endColumn": 11,
"endLine": 2,
"startColumn": 1,
"startLine": 1
}
}
}
],
"message": {
"text": "Import block is un-sorted or un-formatted"
},
"ruleId": "I001"
},
{
"fixes": [
{
"artifactChanges": [
{
"artifactLocation": {
"uri": "file:///home/mathiaslaurin/.cache/bazel/_bazel_mathiaslaurin/f934e6242b74fe50c3a53c5460a4f89a/sandbox/linux-sandbox/4452/execroot/_main/repro.py"
},
"replacements": [
{
"deletedRegion": {
"endColumn": 1,
"endLine": 2,
"startColumn": 1,
"startLine": 1
}
}
]
}
],
"description": {
"text": "Remove unused import: `json`"
}
}
],
"level": "error",
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///home/mathiaslaurin/.cache/bazel/_bazel_mathiaslaurin/f934e6242b74fe50c3a53c5460a4f89a/sandbox/linux-sandbox/4452/execroot/_main/repro.py"
},
"region": {
"endColumn": 12,
"endLine": 1,
"startColumn": 8,
"startLine": 1
}
}
}
],
"message": {
"text": "`json` imported but unused"
},
"ruleId": "F401"
},
{
"fixes": [
{
"artifactChanges": [
{
"artifactLocation": {
"uri": "file:///home/mathiaslaurin/.cache/bazel/_bazel_mathiaslaurin/f934e6242b74fe50c3a53c5460a4f89a/sandbox/linux-sandbox/4452/execroot/_main/repro.py"
},
"replacements": [
{
"deletedRegion": {
"endColumn": 1,
"endLine": 3,
"startColumn": 1,
"startLine": 2
}
}
]
}
],
"description": {
"text": "Remove unused import: `abc`"
}
}
],
"level": "error",
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///home/mathiaslaurin/.cache/bazel/_bazel_mathiaslaurin/f934e6242b74fe50c3a53c5460a4f89a/sandbox/linux-sandbox/4452/execroot/_main/repro.py"
},
"region": {
"endColumn": 11,
"endLine": 2,
"startColumn": 8,
"startLine": 2
}
}
}
],
"message": {
"text": "`abc` imported but unused"
},
"ruleId": "F401"
}
],
"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\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\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n\n\n## Preview\nWhen [preview] is enabled (and certain simplifying assumptions\nare met), we analyze all import statements for a given module\nwhen determining whether an import is used, rather than simply\nthe last of these statements. This can result in both different and\nmore import statements being marked as unused.\n\nFor example, if a module consists of\n\n\nimport a\nimport a.b\n\n\nthen both statements are marked as unused under [preview], whereas\nonly the second is marked as unused under stable behavior.\n\nAs another example, if a module consists of\n\n\nimport a.b\nimport a\n\na.b.foo()\n\n\nthen a diagnostic will only be emitted for the first line under [preview],\nwhereas a diagnostic would only be emitted for the second line under\nstable behavior.\n\nNote that this behavior is somewhat subjective and is designed\nto conform to the developer's intuition rather than Python's actual\nexecution. To wit, the statement `import a.b` automatically executes\n`import a`, so in some sense `import a` is _always_ redundant\nin the presence of `import a.b`.\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\nSee [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)\nfor more details on how Ruff\ndetermines whether an import is first or third-party.\n\n## Example\n\n\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n\n\nUse instead:\n\n\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\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## 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/spec/distributing.html#library-interface-public-and-private-symbols)\n\n[preview]: https://docs.astral.sh/ruff/preview/\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\nDe-duplicates, groups, and sorts imports based on the provided `isort` settings.\n\n## Why is this bad?\nConsistency is good. Use a common convention for imports to make your code\nmore readable and idiomatic.\n\n## Example\n\nimport pandas\nimport numpy as np\n\n\nUse instead:\n\nimport numpy as np\nimport pandas\n\n\n"
},
"help": {
"text": "Import block is un-sorted or un-formatted"
},
"helpUri": "https://docs.astral.sh/ruff/rules/unsorted-imports",
"id": "I001",
"properties": {
"id": "I001",
"kind": "isort",
"name": "unsorted-imports",
"problem.severity": "error"
},
"shortDescription": {
"text": "Import block is un-sorted or un-formatted"
}
}
],
"version": "0.14.7"
}
}
}
],
"version": "2.1.0"
}
But likely runs into https://github.com/astral-sh/ruff/issues/14985
```
### Any other information?
_No response_
Contributor guide
Research direction
Start with lint/ruff.bzl and the ruff_action entry point, then reproduce the issue with the provided repro.py, BUILD file, and bazel lint commands. Confirm that --machine produces SARIF results containing the reported Ruff errors, matching the output shown for rules_lint.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100