aspect-build / aspect-build/rules_lint

[Bug]: ruff doesn't report any error on `--output_groups=rules_lint_machine`

Open
#690 2 comments 0 reactions 0 assignees View on GitHub
bug
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.