Skip to content

Conversation

@LoicRiegel
Copy link
Contributor

Summary

Not sure if it entirely closes #9891, but it's at least a step in the right direction

This change improves the JSON output of the "ruff rule" command.

  • the "fix" field is more easily parsable: possible values are now the values from the FixAvailability enum
  • replaced the "preview" field with a "group" field with the possible values of the RuleGroup enum (stable, preview, deprecated, removed). It's more accurate and parsable that way
  • everything else is the same

Test Plan

I saw no tests for the "ruff rule" command (maybe I missed them?)

So I tested manually:

# BEFORE
~> uvx ruff rule I001 --output-format json
{
  "name": "unsorted-imports",
  "code": "I001",
  "linter": "isort",
  "summary": "Import block is un-sorted or un-formatted",
  "message_formats": [
    "Import block is un-sorted or un-formatted"
  ],
  "fix": "Fix is sometimes available.",
  "explanation": "## 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```python\nimport pandas\nimport numpy as np\n```\n\nUse instead:\n```python\nimport numpy as np\nimport pandas\n```\n\n## Preview\nWhen [`preview`](https://docs.astral.sh/ruff/preview/) mode is enabled, Ruff applies a stricter criterion\nfor determining whether an import should be classified as first-party.\nSpecifically, for an import of the form `import foo.bar.baz`, Ruff will\ncheck that `foo/bar`, relative to a [user-specified `src`](https://docs.astral.sh/ruff/settings/#src) directory, contains either\nthe directory `baz` or else a file with the name `baz.py` or `baz.pyi`.\n",
  "preview": false
}

# AFTER
~> target\debug\ruff.exe rule I001 --output-format json
{
  "name": "unsorted-imports",
  "code": "I001",
  "linter": "isort",
  "summary": "Import block is un-sorted or un-formatted",
  "message_formats": [
    "Import block is un-sorted or un-formatted"
  ],
  "explanation": "## 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```python\nimport pandas\nimport numpy as np\n```\n\nUse instead:\n```python\nimport numpy as np\nimport pandas\n```\n\n## Preview\nWhen [`preview`](https://docs.astral.sh/ruff/preview/) mode is enabled, Ruff applies a stricter criterion\nfor determining whether an import should be classified as first-party.\nSpecifically, for an import of the form `import foo.bar.baz`, Ruff will\ncheck that `foo/bar`, relative to a [user-specified `src`](https://docs.astral.sh/ruff/settings/#src) directory, contains either\nthe directory `baz` or else a file with the name `baz.py` or `baz.pyi`.\n",
  "fix": "Sometimes",
  "group": "Stable"
}

Copy link
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This looks good to me. I'm surprised we don't have any tests for this either, but we should add some. I found a couple of ruff rule tests here:

#[test]
fn rule_f401() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401"]));
}

but none for the JSON format. This could be a good place to add one.

I also think this is a breaking change that we might need to gate behind preview. We could theoretically try to squeeze it into the minor release tomorrow, but I'd be interested in getting @MichaReiser's thoughts next week when he's back from PTO anyway, instead of trying to rush it.

Comment on lines 12 to 13
use ruff_db::diagnostic::SecondaryCode;
use strum_macros::EnumIter;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiny nit: I'd group these up above with the serde import. Unfortunately stable rustfmt still doesn't handle these groups automatically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@github-actions
Copy link
Contributor

github-actions bot commented Sep 9, 2025

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@ntBre ntBre added the cli Related to the command-line interface label Sep 9, 2025
@LoicRiegel
Copy link
Contributor Author

You're welcome. I'll add the tests, but I have no time until next week.
What should I do to put this behind the --preview flag, as you suggested? That means that my changes should only apply when the --preview flag is provided, correct?
How do you then track what features should be made stable when you do the next minor release, and who takes should take care of it?

@ntBre
Copy link
Contributor

ntBre commented Sep 11, 2025

Since ruff rule is a separate subcommand, I think we'd actually have to add a separate --preview flag to it. That might be a good thing to double-check with Micha too, so no worries at all about not working on it until next week :) But yes, my idea was to keep the old behavior on stable and enable the new behavior only when --preview is used.

For stabilizing features, we can just open an issue and add it to the next Milestone on GitHub. We go through all of those before every minor release and handle the stabilizations. In the linter we collect helper functions for preview behaviors in preview.rs in lieu of opening a stabilization issue immediately, but again we can't really reuse that here since rule is a separate subcommand.

explanation: Option<&'a str>,
preview: bool,
fix: FixAvailability,
group: RuleGroup,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest renaming this to status. It might otherwise be mistaken for a rule category (something we want to introduce in the future).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

fix,
explanation: rule.explanation(),
preview: rule.is_preview(),
fix: rule.fixable(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this is an improvement, but I'm not sure if it's worth going through the complications of adding a preview flag (which might even be more annoying for upstream tools than an outright breaking change because they now need to handle both formats if the user set preview = true in their configuration).

I'm inclined to just leave it as is OR introduce a new field fix_availability and deprecate the old field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to go with the second option. So the old field should be deprecated in the next minor release, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we would deprecate or outright remove it in the next minor. But again, I'm not sure what the value of doing so is. It only breaks downstream users. So I expect the field to stick around for a long time :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought having the enum values instead of entire sentences like "Fix is sometimes available." would allow users to parse the json more easily, which was the point raised by the issue
We could also leave both fields, even if this would mean some duplication...

Not sure what's best... Let me know what you prefer :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with reverting the change or adding a second field

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably need to restore the preview field in that case too right? I like the idea of leaving the existing fields untouched (restoring preview and the string fix field) and adding the status and fix_availability fields, which should be easier to match exhaustively.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we need to restore any breaking change.

@LoicRiegel
Copy link
Contributor Author

Hello everyone,
First, sorry for leaving this PR open for so long. I had some days off, and since then I've only had access to pretty slow computers...

But anyway, I've just updated the branch. So I left all the old fields untouched, I just added new ones.

I added basic integration tests for the "ruff rule" with "--output-json" command, very simillar to the existing ones testing "ruff rule".

Since there's no breaking change anymore, it's fine to not put the new fields behind a "--preview" CLI flag, right?

@MichaReiser
Copy link
Member

No worries. I sort of forgot about it too. I'll take a look now

Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you

@MichaReiser MichaReiser merged commit c2ae9c7 into astral-sh:main Oct 20, 2025
37 checks passed
@LoicRiegel LoicRiegel deleted the feat/better-rule-as-json branch October 20, 2025 07:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli Related to the command-line interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add easier way to gather all available rules from ruff

3 participants