Skip to content

[ty] Add explain rule subcommand#23766

Merged
ntBre merged 11 commits intomainfrom
brent/ty-rule-subcommand
Mar 10, 2026
Merged

[ty] Add explain rule subcommand#23766
ntBre merged 11 commits intomainfrom
brent/ty-rule-subcommand

Conversation

@ntBre
Copy link
Contributor

@ntBre ntBre commented Mar 6, 2026

Summary

Fixes astral-sh/ty#2061 by adding a top-level ty explain subcommand with a rule subcommand under it. By default ty explain rule describes all known lint rules, while ty explain rule <RULE> explains a specific rule. Both the text and json formats are supported with the --output-format flag.

Test Plan

New CLI tests mirroring the ones for Ruff

Examples

$ ty explain rule zero-stepsize-in-slice
# zero-stepsize-in-slice

Default level: error | Stable (since 0.0.1-alpha.1)

## What it does
Checks for step size 0 in slices.

## Why is this bad?
A slice with a step size of zero will raise a `ValueError` at runtime.

## Examples
```python
l = list(range(10))
l[1:10:0]  # ValueError: slice step cannot be zero
```
$ ty explain rule zero-stepsize-in-slice --output-format json
{
  "name": "zero-stepsize-in-slice",
  "summary": "detects a slice step size of zero",
  "documentation": "## What it does\nChecks for step size 0 in slices.\n\n## Why is this bad?\nA slice with a step size of zero will raise a `ValueError` at runtime.\n\n## Examples\n```python\nl = list(range(10))\nl[1:10:0]  # ValueError: slice step cannot be zero\n```",
  "default_level": "error",
  "status": {
    "type": "stable",
    "since": "0.0.1-alpha.1"
  }
}
$ ty explain rule --output-format json | jq length
113

Summary
--

Fixes astral-sh/ty#2061. I really just wanted a list of rules for my
categorization work, but I found the issue, and Claude knocked it out quickly.
No pressure to land (or review) this if there's more to it, I see that the issue
is labeled `wish`.

Test Plan
--

New CLI tests mirroring the ones for Ruff
@ntBre ntBre added cli Related to the command-line interface ty Multi-file analysis & type inference labels Mar 6, 2026
@astral-sh-bot
Copy link

astral-sh-bot bot commented Mar 6, 2026

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 85.05%. The percentage of expected errors that received a diagnostic held steady at 78.05%. The number of fully passing files held steady at 63/132.

@astral-sh-bot
Copy link

astral-sh-bot bot commented Mar 6, 2026

mypy_primer results

Changes were detected when running on open source projects
scikit-build-core (https://github.com/scikit-build/scikit-build-core)
+ src/scikit_build_core/build/wheel.py:99:20: error[no-matching-overload] No overload of bound method `__init__` matches arguments
- Found 59 diagnostics
+ Found 60 diagnostics

@astral-sh-bot
Copy link

astral-sh-bot bot commented Mar 6, 2026

Memory usage report

Memory usage unchanged ✅

@ntBre ntBre marked this pull request as ready for review March 6, 2026 21:57
@carljm carljm removed their request for review March 6, 2026 22:21
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. This looks good. I only have some bikeshedding questions.

Long term, it would be nice if there was also a command to explain:

  • categories
  • settings
  • non-rule codes

One option is to group all those under an explain command: ty explain rule <code>, ty explain setting, ty explain category.

I'm not sure what the best approach is regarding non-rule codes. ty explain code and ty explain lint are both unfamiliar terms for users. So I think I'm fine going with rule for now, unless you have a better idea.

Comment on lines +61 to +63
/// Explain all rules
#[arg(long, conflicts_with = "rule", group = "selector")]
all: bool,
Copy link
Member

Choose a reason for hiding this comment

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

I feel like we should simply default to all if the command is called without any arguments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense to me, I was just following Ruff.

let mut stdout = BufWriter::new(io::stdout().lock());
match format {
HelpFormat::Text => {
writeln!(stdout, "{}", format_rule_text(lint))?;
Copy link
Member

Choose a reason for hiding this comment

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

Should we implement Display for Explanation instead? It would make the two branches more similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, makes sense! That would probably be nicer in Ruff too.

@ntBre
Copy link
Contributor Author

ntBre commented Mar 9, 2026

Thanks for the review! I implemented Display for Explanation and dropped the --all flag in favor of defaulting to all rules without a specific code. That seems much more convenient.

Thank you. This looks good. I only have some bikeshedding questions.

Long term, it would be nice if there was also a command to explain:

  • categories
  • settings
  • non-rule codes

One option is to group all those under an explain command: ty explain rule <code>, ty explain setting, ty explain category.

I'm not sure what the best approach is regarding non-rule codes. ty explain code and ty explain lint are both unfamiliar terms for users. So I think I'm fine going with rule for now, unless you have a better idea.

I do like the idea of an explain command. It would be kind of nice if you could just pass anything to it without having to specify the type, i.e. ty explain <CATEGORY> instead of ty explain category <CATEGORY>, but I guess that's kind of at odds with --all or printing all items by default.

All that to say I don't really have a better idea. But I'm happy to change the subcommand name to explain if you think that will be a better start for the future.

@MichaReiser
Copy link
Member

It would be kind of nice if you could just pass anything to it without having to specify the type, i.e. ty explain instead of ty explain category

It might be worth going back to see if Ruff used to use explain before rule and, if so, why we changed it.

I think I prefer ty explain because it lets us explain more than just rule codes. I'm undecided if we should do ty explain rule <code> to future-proof the command or go with ty explain for now. I'll leave this up to you

@ntBre
Copy link
Contributor Author

ntBre commented Mar 9, 2026

It looks like we did use explain in Ruff initially, first as --explain until #2190 and then very briefly as the explain subcommand before it was renamed to rule in #2288 with the reasoning in b9f0a93:

We probably want to introduce multiple explain subcommands and
overloading explain to explain it all seems like a bad idea.
We may want to introduce a subcommand to explain config options and
config options may end up having the same name as their rules, e.g. the
current banned-api is both a rule name (although not yet exposed to
the user) and a config option.

The idea is:

  • ruff rule lists all rules supported by ruff
  • ruff rule <code> explains a specific rule
  • ruff linter lists all linters supported by ruff
  • ruff linter <name> lists all rules/options supported by a specific linter

so I guess it just depends if we want to stick with that and have separate top-level subcommands for each type or nest them all under the explain subcommand. I guess it's nice that separating them leads to shorter commands, but it also clutters the top-level namespace.

This also raises another possibility for ty rule without an argument: listing the supported rules. I don't think this was ever implemented in Ruff, but the analogy to git branch and other commands in #2288 (comment) is kind of interesting.

I don't feel super strongly either way but do slightly prefer the look of:

$ ty rule
$ ty category
$ ty setting

over

$ ty explain rule
$ ty explain category
$ ty explain setting

and at least it matches Ruff too.

@MichaReiser
Copy link
Member

I prefer a separate subcommand. Top level commands have a high cognitive overhead and it's also unclear what ty setting does (a ty setting explain could work but that's as long as `ty explain setting).

I prefer ty explain rule

@AlexWaygood AlexWaygood removed their request for review March 9, 2026 14:43
@ntBre
Copy link
Contributor Author

ntBre commented Mar 9, 2026

Sounds good, I switched it to ty explain rule.

@ntBre ntBre changed the title [ty] Add rule subcommand [ty] Add explain rule subcommand Mar 10, 2026
@ntBre ntBre enabled auto-merge (squash) March 10, 2026 15:25
@ntBre ntBre merged commit 95e1bbf into main Mar 10, 2026
50 checks passed
@ntBre ntBre deleted the brent/ty-rule-subcommand branch March 10, 2026 15:31
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 ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support ty rule "$RULE_NAME"

2 participants