Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve rule config resolution #2312

Merged
merged 5 commits into from Jan 30, 2023

Conversation

not-my-profile
Copy link
Contributor

Ruff allows rules to be enabled with select and disabled with
ignore, where the more specific rule selector takes precedence,
for example:

`--select ALL --ignore E501` selects all rules except E501
`--ignore ALL --select E501` selects only E501

(If both selectors have the same specificity ignore selectors
take precedence.)

Ruff always had two quirks:

  • If pyproject.toml specified ignore = ["E501"] then you could
    previously not override that with --select E501 on the command-line
    (since the resolution didn't take into account that the select was
    specified after the ignore).

  • If pyproject.toml specified select = ["E501"] then you could
    previously not override that with --ignore E on the command-line
    (since the resolution didn't take into account that the ignore was
    specified after the select).

Since d067efe (#1245)
extend-select and extend-ignore always override
select and ignore and are applied iteratively in pairs,
which introduced another quirk:

  • If some pyproject.toml file specified extend-select
    or extend-ignore, select and ignore became pretty much
    unreliable after that with no way of resetting that.

This commit fixes all of these quirks by making later configuration
sources take precedence over earlier configuration sources.

While this is a breaking change, it fixes the broken status quo.

Fixes #2300.

@not-my-profile not-my-profile changed the title Improve rule resolution Improve rule config resolution Jan 28, 2023
@not-my-profile
Copy link
Contributor Author

I think this makes much more sense and is much more user-friendly / understandable.

This obviously breaks existing configurations that relied on the previous weird behavior ... however I'd expect most ruff config files to not rely on this weird behavior ... but that's just a hunch.

How you want to go about this is very much your call. It would be possible to support both the old and the new config resolution at the same time and only use the new resolution based on some tool.ruff.edition setting but I am not really interested in implementing that.

@charliermarsh
Copy link
Member

I just have to take a look at some of the more complex setups in the wild and see how they'd be affected.

@charliermarsh
Copy link
Member

How you want to go about this is very much your call. It would be possible to support both the old and the new config resolution at the same time and only use the new resolution based on some tool.ruff.edition setting but I am not really interested in implementing that.

Don't worry, I definitely don't want to support both.

let mut select_set: FxHashSet<_> = defaults::PREFIXES.iter().flatten().collect();
let mut fixable_set: FxHashSet<_> = RuleSelector::All.into_iter().collect();

let mut redirects = FxHashMap::default();
Copy link
Member

Choose a reason for hiding this comment

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

Do you mind adding some comments throughout this method? Or explaining the logic in more detail here? I'm tracing it but having trouble following.

As a specific example: if I have select = ["F"] in my pyproject.toml, and I do --select A, then I end up with two rule selections:

[RuleSelection { select: Some([Prefix { prefix: F, redirected_from: None }]), ignore: Some([Prefix { prefix: A, redirected_from: None }]), extend_select: None, extend_ignore: None, fixable: None, unfixable: None }, RuleSelection { select: Some([Prefix { prefix: A, redirected_from: None }]), ignore: None, extend_select: None, extend_ignore: None, fixable: None, unfixable: None }]

But, how do the F rules not end up in the select set?

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I see -- do we, in effect, only use the last RuleSelection that contains a non-empty select? The rest gets discarded?

Copy link
Member

Choose a reason for hiding this comment

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

If that's the correct interpretation how would I do the following: create a pyproject.toml that enables all the F rules, then extend it from another pyproject.toml to also include the E rules? I think I know the answer, but it would be helpful to hear it.

Copy link
Member

Choose a reason for hiding this comment

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

Separate question: under this scheme, is there ever any difference between specifying ignore and extend-ignore?

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 added some comments :)

if I have select = ["F"] in my pyproject.toml, and I do --select A, [..] But, how do the F rules not end up in the select set?

If selection.select.is_some() then the select_set is overridden.

do we, in effect, only use the last RuleSelection that contains a non-empty select? The rest gets discarded?

No, multiple RuleSelections can all contribute to select_set if they only use extend_select / ignore but not select.

how would I do the following: create a pyproject.toml that enables all the F rules, then extend it from another pyproject.toml to also include the E rules?

select can be extended via extend-select.

Separate question: under this scheme, is there ever any difference between specifying ignore and extend-ignore?

Very good catch! I didn't even notice that but indeed we can deprecate extend-ignore since it now can always be replaced with ignore. The reasoning behind that is that now ignore/extend-ignore can only ever deselect already selected rules ... it's impossible for an ignore option to take precedence over a latter select option. Since the "ignore set" isn't something that is kept track of anymore ... the function only keeps track of the effectively selected rules, extend-ignore doesn't fulfill any purpose anymore.

Copy link
Member

Choose a reason for hiding this comment

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

Definitely, will do. Mechanically, probably easiest for me to open those changes as a separate PR on main and then just rebase that PR after this merges...?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You have write permissions on my branch so you could just push your commits there :)

Copy link
Member

Choose a reason for hiding this comment

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

Ok, I went ahead and pushed a first-pass in the README.md and BREAKING_CHANGES.md. Feedback welcome.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you so much, I think you described it very well :)

Copy link
Member

Choose a reason for hiding this comment

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

Great. I'll try to get this out tomorrow!

@not-my-profile
Copy link
Contributor Author

(force pushed a small refactor that removed extend_ignore from the new RuleSelection struct and removed some needless Option wrapping around the fields that don't require it)

Previously we tested the resolve_codes helper function directly.
Since we want to rewrite our resolution logic in the next commit,
this commit changes the tests to test the more high-level From impl.
@not-my-profile
Copy link
Contributor Author

not-my-profile commented Jan 30, 2023

(force pushed to fix the CI failure, which was caused by the README now including the --extend-ignore setting which this PR hides from the help output)

I think we should also mention in the new Rule resolution section in the README and the BREAKING_CHANGES.md entry that extend-ignore is now deprecated since it behaves exactly like ignore.

(This is now already mentioned in the documentation of the extend-ignore setting but I think it would make sense to repeat it ... note that I have also updated the CLI help to hide the --extend-ignore flag.)

@@ -433,8 +433,6 @@ Rule selection:
Comma-separated list of rule codes to disable
--extend-select <RULE_CODE>
Like --select, but adds additional rule codes on top of the selected ones
--extend-ignore <RULE_CODE>
Like --ignore, but adds additional rule codes on top of the ignored ones
Copy link
Member

Choose a reason for hiding this comment

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

I do kind of wonder if we should just leave this visible in the CLI to avoid confusion (due to the asymmetry with --select). What do you think? Bad idea?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we leave it I think we should add DEPRECATED to the description ... however I don't think that would really reduce the confusion since we'd have to explain why it's deprecated ... which would probably be too verbose. I consider --extend-ignore to effectively be an alias for --ignore ... and we currently don't document e.g. that ruff clean has ruff --clean as an alias in the CLI help.

I don't have a strong opinion about this, if you think it should be listed we can add it back.

Note that I have also removed extend-ignore from the JSON schema ... which could also be confusing for users but I think it's worth it to stop it from being autocompleted for users writing new configs.

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 think I prefer to hide it. I have updated the message to mention that it's deprecated since it still shows up in the clap shell completion.

not-my-profile and others added 3 commits January 30, 2023 21:57
Ruff allows rules to be enabled with `select` and disabled with
`ignore`, where the more specific rule selector takes precedence,
for example:

    `--select ALL --ignore E501` selects all rules except E501
    `--ignore ALL --select E501` selects only E501

(If both selectors have the same specificity ignore selectors
take precedence.)

Ruff always had two quirks:

* If `pyproject.toml` specified `ignore = ["E501"]` then you could
  previously not override that with `--select E501` on the command-line
  (since the resolution didn't take into account that the select was
  specified after the ignore).

* If `pyproject.toml` specified `select = ["E501"]` then you could
  previously not override that with `--ignore E` on the command-line
  (since the resolution didn't take into account that the ignore was
  specified after the select).

Since d067efe (astral-sh#1245)
`extend-select` and `extend-ignore` always override
`select` and `ignore` and are applied iteratively in pairs,
which introduced another quirk:

* If some `pyproject.toml` file specified `extend-select`
  or `extend-ignore`, `select` and `ignore` became pretty much
  unreliable after that with no way of resetting that.

This commit fixes all of these quirks by making later configuration
sources take precedence over earlier configuration sources.

While this is a breaking change, we expect most ruff configuration
files to not rely on the previous unintutive behavior.
Now that the option is deprecated we no longer
want IDEs to suggest it in their autocompletion.
@charliermarsh charliermarsh merged commit ca1129a into astral-sh:main Jan 30, 2023
renovate bot added a commit to ixm-one/pytest-cmake-presets that referenced this pull request Jan 31, 2023
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [ruff](https://togithub.com/charliermarsh/ruff) | `^0.0.237` ->
`^0.0.238` |
[![age](https://badges.renovateapi.com/packages/pypi/ruff/0.0.238/age-slim)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://badges.renovateapi.com/packages/pypi/ruff/0.0.238/adoption-slim)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://badges.renovateapi.com/packages/pypi/ruff/0.0.238/compatibility-slim/0.0.237)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://badges.renovateapi.com/packages/pypi/ruff/0.0.238/confidence-slim/0.0.237)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>charliermarsh/ruff</summary>

###
[`v0.0.238`](https://togithub.com/charliermarsh/ruff/releases/tag/v0.0.238)

[Compare
Source](https://togithub.com/charliermarsh/ruff/compare/v0.0.237...v0.0.238)

<!-- Release notes generated using configuration in .github/release.yml
at main -->

#### What's Changed

##### ⚠️ Breaking Changes ⚠️

##### `select`, `extend-select`, `ignore`, and `extend-ignore` have new
semantics
([#&#8203;2312](https://togithub.com/charliermarsh/ruff/pull/2312))

Previously, the interplay between `select` and its related options could
lead to unexpected behavior. For example, `ruff --select E501 --ignore
ALL` and `ruff --select E501 --extend-ignore ALL` behaved differently.
(See [#&#8203;2312](https://togithub.com/charliermarsh/ruff/pull/2312)
for more examples.)

The new semantics are such that Ruff uses the "highest-priority"
`select` as the basis for the rule set, and then applies any
`extend-select`, `ignore`, and `extend-ignore` adjustments. CLI options
are given higher priority than `pyproject.toml` options, and the current
`pyproject.toml` file is given higher priority than any inherited
`pyproject.toml` files.

As an example: `ruff --select F401` will select rule `F401`, and ignore
any of the modifiers from the `pyproject.toml`, as the "highest-priorty"
select kicks off the resolution chain.

This change is largely backwards compatible -- most users should
experience no change in behavior. For more, see
[BREAKING_CHANGES.md](https://togithub.com/charliermarsh/ruff/blob/main/BREAKING_CHANGES.md#select-extend-select-ignore-and-extend-ignore-have-new-semantics-2312).

##### `remove-six-compat` (`UP016`) has been removed
([#&#8203;2332](https://togithub.com/charliermarsh/ruff/pull/2332))

The `remove-six-compat` rule has been removed. This rule was only useful
for one-time Python 2-to-3 upgrades.

##### Rules

- Implement Pylint's `too-many-arguments` rule (`PLR0913`) by
[@&#8203;akhildevelops](https://togithub.com/akhildevelops) in
[astral-sh/ruff#2308
- Extend conventional imports defaults to include TensorFlow et al by
[@&#8203;sbrugman](https://togithub.com/sbrugman) in
[astral-sh/ruff#2353

##### Settings

- feat: add ruff --statistics by
[@&#8203;spaceone](https://togithub.com/spaceone) in
[astral-sh/ruff#2284
- Ignore magic comparisons to bytes by default by
[@&#8203;charliermarsh](https://togithub.com/charliermarsh) in
[astral-sh/ruff#2365
- Implement `ruff linter` subcommand by
[@&#8203;not-my-profile](https://togithub.com/not-my-profile) in
[astral-sh/ruff#2294
- Improve rule config resolution by
[@&#8203;not-my-profile](https://togithub.com/not-my-profile) in
[astral-sh/ruff#2312

##### Bug Fixes

- Refine criteria for `exc_info` logger rules by
[@&#8203;charliermarsh](https://togithub.com/charliermarsh) in
[astral-sh/ruff#2364
- Respect per-file-ignores when checking noqa by
[@&#8203;sciyoshi](https://togithub.com/sciyoshi) in
[astral-sh/ruff#2309
- Place star before other member imports by
[@&#8203;charliermarsh](https://togithub.com/charliermarsh) in
[astral-sh/ruff#2320
- Allow list comprehensions for **all** assignment by
[@&#8203;charliermarsh](https://togithub.com/charliermarsh) in
[astral-sh/ruff#2326
- \[`TRY201`] don't check raise statements in nested exception handlers
by [@&#8203;sciyoshi](https://togithub.com/sciyoshi) in
[astral-sh/ruff#2337
- include tomllib in standard lib by
[@&#8203;sbrugman](https://togithub.com/sbrugman) in
[astral-sh/ruff#2345
- Avoid removing trailing comments when autofixing by
[@&#8203;charliermarsh](https://togithub.com/charliermarsh) in
[astral-sh/ruff#2352
- \[`I001`] fix isort for files with tab-based indentation by
[@&#8203;sciyoshi](https://togithub.com/sciyoshi) in
[astral-sh/ruff#2361
- Disable incompatible rules rather than merely warning by
[@&#8203;charliermarsh](https://togithub.com/charliermarsh) in
[astral-sh/ruff#2369

#### New Contributors

- [@&#8203;chirag127](https://togithub.com/chirag127) made their first
contribution in
[astral-sh/ruff#2307
- [@&#8203;akhildevelops](https://togithub.com/akhildevelops) made their
first contribution in
[astral-sh/ruff#2308

**Full Changelog**:
astral-sh/ruff@v0.0.237...v0.0.238

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://app.renovatebot.com/dashboard#github/ixm-one/pytest-cmake-presets).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNC4xMTYuMSIsInVwZGF0ZWRJblZlciI6IjM0LjExNi4xIn0=-->

Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

--extend-select and --extend-ignore work in unexpected ways
2 participants