Compare commits

..

102 Commits

Author SHA1 Message Date
Zanie Blue
1fadefa67b Bump version to 0.2.0 (#9762)
Follows https://github.com/astral-sh/ruff/pull/9680
2024-02-01 17:10:33 -06:00
Charlie Marsh
06ad687efd Deduplicate deprecation warnings for v0.2.0 release (#9764)
## Summary

Adds an additional warning macro (we should consolidate these later)
that shows a warning once based on the content of the warning itself.
This is less efficient than `warn_user_once!` and `warn_user_by_id!`,
but this is so expensive that it doesn't matter at all.

Applies this macro to the various warnings for the v0.2.0 release, and
also includes the filename in said warnings, so the FastAPI case is now:

```text
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in /Users/crmarsh/workspace/fastapi/pyproject.toml:
  - 'ignore' -> 'lint.ignore'
  - 'select' -> 'lint.select'
  - 'isort' -> 'lint.isort'
  - 'pyupgrade' -> 'lint.pyupgrade'
  - 'per-file-ignores' -> 'lint.per-file-ignores'
```

---------

Co-authored-by: Zanie <contact@zanie.dev>
2024-02-01 17:10:24 -06:00
Jane Lewis
148b64ead3 Fix issue where output format mode would not change to full if preview mode was set in configuration file (#9763)
## Summary

This was causing build failures for #9599. We were referencing the
command line overrides instead of the merged configuration data, hence
the issue.

## Test Plan

A snapshot test was added.
2024-02-01 16:07:21 -06:00
Charlie Marsh
99eddbd2a0 Remove stale preview documentation from stabilized rule behaviors (#9759)
These behaviors were stabilized, so the docs referring to them as
preview-only are incorrect.
2024-02-01 13:35:02 -06:00
Zanie Blue
836d2eaa01 Restore RUF011 documentation (#9758)
For consistency with other redirected rules as in
https://github.com/astral-sh/ruff/pull/9755

Follow-up to #9428
2024-02-01 13:35:02 -06:00
Zanie Blue
994514d686 Redirect PHG001 to S307 and PGH002 to G010 (#9756)
Follow-up to #9754 and #9689. Alternative to #9714.
Replaces #7506 and #7507
Same ideas as #9755
Part of #8931
2024-02-01 13:35:02 -06:00
Zanie Blue
a578414246 Redirect TRY200 to B904 (#9755)
Follow-up to #9754 and #9689. Alternative to #9714.

Marks `TRY200` as removed and redirects to `B904` instead of marking as
deprecated and suggesting `B904` instead.
2024-02-01 13:35:02 -06:00
Zanie Blue
0d752e56cd Add tests for redirected rules (#9754)
Extends https://github.com/astral-sh/ruff/pull/9752 adding internal test
rules for redirection

Fixes a bug where we did not see warnings for exact codes that are
redirected (just prefixes)
2024-02-01 13:35:02 -06:00
Zanie Blue
46c0937bfa Use fake rules for testing deprecation and removal infrastructure (#9752)
Updates #9689 and #9691 to use rule testing infrastructure from #9747
2024-02-01 13:35:02 -06:00
Zanie
e5008ca714 Fix bug where selection included deprecated rules during preview (#9746)
Cherry-picked from https://github.com/astral-sh/ruff/pull/9714 which is
being abandoned for now because we need to invest more into our
redirection infrastructure before it is feasible.

Fixes a bug in the implementation where we improperly included
deprecated rules in `RuleSelector.rules()` when preview is on. Includes
some clean-up of error messages and the implementation.
# Conflicts:
#	crates/ruff/tests/integration_test.rs
2024-02-01 13:35:02 -06:00
Charlie Marsh
85a7edcc70 Recategorize runtime-string-union to TCH010 (#9721)
## Summary

This rule was added to `flake8-type-checking` as `TC010`. We're about to
stabilize it, so we might as well use the correct code.

See: https://github.com/astral-sh/ruff/issues/9573.
2024-02-01 13:35:02 -06:00
Charlie Marsh
7db3aea1c6 Stabilize some rules for v0.2.0 release (#9712)
## Summary

This PR stabilizes the preview rules from:

- `flake8-trio` (6 rules)
- `flake8-quotes` (1 rule)
- `pyupgrade` (1 rule)
- `flake8-pyi` (1 rule)
- `flake8-simplify` (2 rules)
- `flake8-bandit` (9 rules; 14 remain in preview)
- `flake8-type-checking` (1 rule)
- `numpy` (1 rule)
- `ruff` (4 rules, one elevated from nursery; 6 remain in preview as
they were added within the last 30 days)
- `flake8-logging` (4 rules)

I see these are largely uncontroversial.
2024-02-01 13:35:02 -06:00
Zanie Blue
e0bc08a758 Add rule removal infrastructure (#9691)
Similar to https://github.com/astral-sh/ruff/pull/9689 — retains removed
rules for better error messages and documentation but removed rules
_cannot_ be used in any context.

Removes PLR1706 as a useful test case and something we want to
accomplish in #9680 anyway. The rule was in preview so we do not need to
deprecate it first.

Closes https://github.com/astral-sh/ruff/issues/9007

## Test plan

<img width="1110" alt="Rules table"
src="https://github.com/astral-sh/ruff/assets/2586601/ac9fa682-623c-44aa-8e51-d8ab0d308355">

<img width="1110" alt="Rule page"
src="https://github.com/astral-sh/ruff/assets/2586601/05850b2d-7ca5-49bb-8df8-bb931bab25cd">
2024-02-01 13:35:02 -06:00
Zanie Blue
a0ef087e73 Add rule deprecation infrastructure (#9689)
Adds a new `Deprecated` rule group in addition to `Stable` and
`Preview`.

Deprecated rules:
- Warn on explicit selection without preview
- Error on explicit selection with preview
- Are excluded when selected by prefix with preview

Deprecates `TRY200`, `ANN101`, and `ANN102` as a proof of concept. We
can consider deprecating them separately.
2024-02-01 13:35:02 -06:00
Zanie Blue
c86e14d1d4 Remove the NURSERY selector from the json schema (#9695) 2024-02-01 13:35:02 -06:00
Zanie
e37b3b0742 Always request the concise output format during ecosystem checks (#9708)
Fixes a regression in the ecosystem checks from
https://github.com/astral-sh/ruff/pull/9687 which was causing them to
run for multiple hours due to the size of the output.

We need the concise format for comparisons.

We should probably update the ecosystem checks to actually diff the full
output in the future because that'd be nice.
# Conflicts:
#	python/ruff-ecosystem/ruff_ecosystem/projects.py
2024-02-01 13:35:02 -06:00
Zanie
a0f32dfa55 Error if nursery rules are selected without preview (#9683)
Extends #9682 to error if the nursery selector is used or nursery rules
are selected without preview.

Part of #7992 — we will remove this in 0.3.0 instead so we can provide
nice errors in 0.2.0.
# Conflicts:
#	crates/ruff/tests/integration_test.rs
#	crates/ruff_workspace/src/configuration.rs
2024-02-01 13:35:02 -06:00
Zanie
6aa643346f Replace --show-source and --no-show-source with --output_format=<full|concise> (#9687)
Fixes #7350

## Summary

* `--show-source` and `--no-show-source` are now deprecated.
* `output-format` supports two new variants, `full` and `concise`.
`text` is now a deprecated variant, and any use of it is treated as the
default serialization format.
* `--output-format` now default to `concise`
* In preview mode, `--output-format` defaults to `full`
* `--show-source` will still set `--output-format` to `full` if the
output format is not otherwise specified.
* likewise, `--no-show-source` can override an output format that was
set in a file-based configuration, though it will also be overridden by
`--output-format`

## Test Plan

A lot of tests were updated to use `--output-format=full`. Additional
tests were added to ensure the correct deprecation warnings appeared,
and that deprecated options behaved as intended.
# Conflicts:
#	crates/ruff/tests/integration_test.rs
2024-02-01 13:35:02 -06:00
Charlie Marsh
ae13d8fddf Remove preview gating for flake8-simplify rules (#9686)
## Summary

Un-gates detecting `dict.get` rewrites in `if` expressions (rather than
just `if` statements).
2024-02-01 13:35:02 -06:00
Charlie Marsh
2d6fd0fc91 Remove preview gating for flake8-pie rules (#9684)
## Summary

Both of the preview behaviors gated here seem like improvements, so
let's make them stable in v0.2.0
2024-02-01 13:35:02 -06:00
Charlie Marsh
33fe988cfc Remove preview gating for pycodestyle rules (#9685)
## Summary

Un-gates the behavior to allow `sys.path` modifications between imports,
which removed a bunch of false positives in the ecosystem CI at the
time.
2024-02-01 13:35:02 -06:00
Zanie
0f674d1d90 Remove preview gating for newly-added stable fixes (#9681)
## Summary

At present, our versioning policy forbids the addition of safe fixes to
stable rules outside of a minor release, so we've accumulated a bunch of
new fixes that are behind `--preview`, and can be ungated in v0.2.0.

To find these, I just grepped for `preview.is_enabled()` and identified
all such cases. I then audited the `preview_rules` test fixtures and
removed any tests that existed only to test this autofix behavior.
# Conflicts:
#	crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM114_SIM114.py.snap
#	crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM114_SIM114.py.snap
2024-02-01 13:35:02 -06:00
Zanie
7962bca40a Recategorize static-key-dict-comprehension from RUF011 to B035 (#9428)
## Summary

This rule was added to flake8-bugbear. In general, we tend to prefer
redirecting to prominent plugins when our own rules are reimplemented
(since more projects have `B` activated than `RUF`).

## Test Plan

`cargo test`
# Conflicts:
#	crates/ruff_linter/src/rules/ruff/rules/mod.rs
2024-02-01 13:35:02 -06:00
Charlie Marsh
b81fc5ed11 [flake8-pyi] Mark unaliased-collections-abc-set-import fix as safe (#9679)
## Summary

Prompted by
https://github.com/astral-sh/ruff/issues/8482#issuecomment-1859299411.
The rename is only unsafe when the symbol is exported, so we can narrow
the conditions.
2024-02-01 13:35:02 -06:00
Micha Reiser
c2bf725086 Add deprecation message for top-level lint settings (#9582) 2024-02-01 13:35:02 -06:00
Micha Reiser
c3b33e9c4d Promote lint. settings over top-level settings (#9476) 2024-02-01 13:35:02 -06:00
Charlie Marsh
6996ff7b1e Use consistent method to detect preview enablement (#9760)
I missed these two in the v0.2.0 stabilizations because they use a match
instead of the dedicated method.
2024-02-01 18:58:05 +00:00
Zanie Blue
f18e7d40ac Add internal hidden rules for testing (#9747)
Updated implementation of https://github.com/astral-sh/ruff/pull/7369
which was left out in the cold.

This was motivated again following changes in #9691 and #9689 where we
could not test the changes without actually deprecating or removing
rules.

---

Follow-up to discussion in https://github.com/astral-sh/ruff/pull/7210

Moves integration tests from using rules that are transitively in
nursery / preview groups to dedicated test rules that only exist during
development. These rules always raise violations (they do not require
specific file behavior). The rules are not available in production or in
the documentation.

Uses features instead of `cfg(test)` for cross-crate support per
https://github.com/rust-lang/cargo/issues/8379
2024-02-01 08:44:51 -06:00
Aleksei Latyshev
2cc8acb0b7 [refurb] Implement metaclass_abcmeta (FURB180) (#9658)
## Summary

Implement [use-abc-shorthand
(FURB180)](https://github.com/dosisod/refurb/blob/master/refurb/checks/readability/use_abc_shorthand.py)
lint.

I changed the name to be more conformant with ruff rule-naming rules.


## Test Plan

cargo test
2024-01-31 22:31:12 +00:00
Charlie Marsh
ad83944ded Detect multi-statement lines in else removal (#9748)
The condition here wasn't quite right -- we can have multiple
statements, all on the same line.

Closes https://github.com/astral-sh/ruff/issues/9732.
2024-01-31 22:08:32 +00:00
Seo Sanghyeon
6e225cb57c Removing trailing whitespace inside multiline strings is unsafe (#9744)
Fix #8037.
2024-01-31 21:45:23 +00:00
dependabot[bot]
7992583908 Bump serde from 1.0.195 to 1.0.196 (#9741) 2024-01-31 10:48:50 -05:00
dependabot[bot]
1a46c9c2a2 Bump serde_with from 3.5.1 to 3.6.0 (#9740) 2024-01-31 10:48:44 -05:00
dependabot[bot]
3f4ab87061 Bump proc-macro2 from 1.0.76 to 1.0.78 (#9738) 2024-01-31 10:48:37 -05:00
Christopher Covington
7ae7bf6e30 Support IfExp with dual string arms in invalid-envvar-default (#9734)
## Summary

Just like #6537 and #6538 but for the `default` second parameter to
`getenv()`.

Also rename "BAD" to "BAR" in the tests, since those strings shouldn't
trigger the rule.

## Test Plan

Added passing and failing examples to `invalid_envvar_default.py`.
2024-01-31 10:41:24 -05:00
dependabot[bot]
9e3ff01ce8 Bump the actions group with 4 updates (#9737)
Bumps the actions group with 4 updates:
[tj-actions/changed-files](https://github.com/tj-actions/changed-files),
[actions/cache](https://github.com/actions/cache),
[peter-evans/find-comment](https://github.com/peter-evans/find-comment)
and
[peter-evans/create-or-update-comment](https://github.com/peter-evans/create-or-update-comment).

Updates `tj-actions/changed-files` from 41 to 42
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tj-actions/changed-files/releases">tj-actions/changed-files's
releases</a>.</em></p>
<blockquote>
<h2>v42</h2>
<h1>Changes in v42.0.2</h1>
<h2>What's Changed</h2>
<ul>
<li>Upgraded to v42.0.1 by <a
href="https://github.com/tj-actions-bot"><code>@​tj-actions-bot</code></a>
in <a
href="https://redirect.github.com/tj-actions/changed-files/pull/1884">tj-actions/changed-files#1884</a></li>
<li>feat: enhance error handling for non-git directories by <a
href="https://github.com/jackton1"><code>@​jackton1</code></a> in <a
href="https://redirect.github.com/tj-actions/changed-files/pull/1885">tj-actions/changed-files#1885</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/tj-actions/changed-files/compare/v42...v42.0.2">https://github.com/tj-actions/changed-files/compare/v42...v42.0.2</a></p>
<hr />
<h1>Changes in v42.0.1</h1>
<h2>What's Changed</h2>
<ul>
<li>Upgraded to v42 by <a
href="https://github.com/tj-actions-bot"><code>@​tj-actions-bot</code></a>
in <a
href="https://redirect.github.com/tj-actions/changed-files/pull/1874">tj-actions/changed-files#1874</a></li>
<li>chore(deps): update tj-actions/eslint-changed-files action to v23 by
<a href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/tj-actions/changed-files/pull/1875">tj-actions/changed-files#1875</a></li>
<li>chore(deps): lock file maintenance by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/tj-actions/changed-files/pull/1876">tj-actions/changed-files#1876</a></li>
<li>chore: update README.md by <a
href="https://github.com/jackton1"><code>@​jackton1</code></a> in <a
href="https://redirect.github.com/tj-actions/changed-files/pull/1877">tj-actions/changed-files#1877</a></li>
<li>chore: rename example worflows from test to example by <a
href="https://github.com/jackton1"><code>@​jackton1</code></a> in <a
href="https://redirect.github.com/tj-actions/changed-files/pull/1878">tj-actions/changed-files#1878</a></li>
<li>chore(deps): lock file maintenance by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/tj-actions/changed-files/pull/1879">tj-actions/changed-files#1879</a></li>
<li>chore(deps): update dependency ts-jest to v29.1.2 by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/tj-actions/changed-files/pull/1880">tj-actions/changed-files#1880</a></li>
<li>chore(deps): update typescript-eslint monorepo to v6.19.1 by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/tj-actions/changed-files/pull/1881">tj-actions/changed-files#1881</a></li>
<li>chore(deps): update dependency <code>@​types/node</code> to v20.11.6
by <a href="https://github.com/renovate"><code>@​renovate</code></a> in
<a
href="https://redirect.github.com/tj-actions/changed-files/pull/1883">tj-actions/changed-files#1883</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/tj-actions/changed-files/compare/v42...v42.0.1">https://github.com/tj-actions/changed-files/compare/v42...v42.0.1</a></p>
<hr />
<h1>Changes in v42.0.0</h1>
<h2>🔥🔥 BREAKING CHANGE 🔥🔥</h2>
<ul>
<li>Input file patterns that end with a <code>/</code> would now match
all sub-files within the directory without requiring you to specify the
globstar pattern.</li>
</ul>
<h3></h3>
<pre lang="yaml"><code>...
      - name: Get changed files
        id: changed-files
        uses: tj-actions/changed-files@v42
        with:
          files: 'dir/'  # Would also be the same as dir/** 
</code></pre>
<h2>What's Changed</h2>
<ul>
<li>Upgraded to v41.1.2 by <a
href="https://github.com/tj-actions-bot"><code>@​tj-actions-bot</code></a>
in <a
href="https://redirect.github.com/tj-actions/changed-files/pull/1869">tj-actions/changed-files#1869</a></li>
<li>chore(deps): update dependency prettier to v3.2.4 by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/tj-actions/changed-files/pull/1871">tj-actions/changed-files#1871</a></li>
<li>fix: update input warning by <a
href="https://github.com/jackton1"><code>@​jackton1</code></a> in <a
href="https://redirect.github.com/tj-actions/changed-files/pull/1870">tj-actions/changed-files#1870</a></li>
<li>rename: unsupported REST API inputs constant name by <a
href="https://github.com/jackton1"><code>@​jackton1</code></a> in <a
href="https://redirect.github.com/tj-actions/changed-files/pull/1872">tj-actions/changed-files#1872</a></li>
<li>feat: add support for include/exclude all nested files when a
directory is specified and ends with a slash by <a
href="https://github.com/jackton1"><code>@​jackton1</code></a> in <a
href="https://redirect.github.com/tj-actions/changed-files/pull/1873">tj-actions/changed-files#1873</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tj-actions/changed-files/blob/main/HISTORY.md">tj-actions/changed-files's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h1><a
href="https://github.com/tj-actions/changed-files/compare/v42.0.1...v42.0.2">42.0.2</a>
- (2024-01-25)</h1>
<h2><!-- raw HTML omitted -->🚀 Features</h2>
<ul>
<li>Enhance error handling for non-git directories (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/1885">#1885</a>)
(<a
href="90a06d6ba9">90a06d6</a>)
- (Tonye Jack)</li>
</ul>
<h2><!-- raw HTML omitted -->⬆️ Upgrades</h2>
<ul>
<li>Upgraded to v42.0.1 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/1884">#1884</a>)</li>
</ul>
<p>Co-authored-by: jackton1 <a
href="mailto:jackton1@users.noreply.github.com">jackton1@users.noreply.github.com</a>
(<a
href="2cb2c9234e">2cb2c92</a>)
- (tj-actions[bot])</p>
<h1><a
href="https://github.com/tj-actions/changed-files/compare/v42.0.0...v42.0.1">42.0.1</a>
- (2024-01-24)</h1>
<h2><!-- raw HTML omitted --> Add</h2>
<ul>
<li>Added missing changes and modified dist assets.
(<a
href="ea024b2d7f">ea024b2</a>)
- (GitHub Action)</li>
<li>Added missing changes and modified dist assets.
(<a
href="3af07c2040">3af07c2</a>)
- (GitHub Action)</li>
</ul>
<h2><!-- raw HTML omitted -->🔄 Update</h2>
<ul>
<li>Update env.ts (<a
href="3680129aa2">3680129</a>)
- (Tonye Jack)</li>
</ul>
<h2><!-- raw HTML omitted -->⚙️ Miscellaneous Tasks</h2>
<ul>
<li><strong>deps:</strong> Update dependency <code>@​types/node</code>
to v20.11.6 (<a
href="ac21d93904">ac21d93</a>)
- (renovate[bot])</li>
<li><strong>deps:</strong> Update typescript-eslint monorepo to v6.19.1
(<a
href="a4637ea6e7">a4637ea</a>)
- (renovate[bot])</li>
<li><strong>deps:</strong> Update dependency ts-jest to v29.1.2 (<a
href="fd9998cf5f">fd9998c</a>)
- (renovate[bot])</li>
<li><strong>deps:</strong> Lock file maintenance (<a
href="db4e584844">db4e584</a>)
- (renovate[bot])</li>
<li>Rename example worflows from test to example (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/1878">#1878</a>)
(<a
href="c6543c497a">c6543c4</a>)
- (Tonye Jack)</li>
<li>Update README.md (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/1877">#1877</a>)
(<a
href="88f9f3efbb">88f9f3e</a>)
- (Tonye Jack)</li>
<li><strong>deps:</strong> Lock file maintenance (<a
href="5d866cbe77">5d866cb</a>)
- (renovate[bot])</li>
<li><strong>deps:</strong> Update tj-actions/eslint-changed-files action
to v23 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/1875">#1875</a>)
(<a
href="346f237a17">346f237</a>)
- (renovate[bot])</li>
</ul>
<h2><!-- raw HTML omitted -->⬆️ Upgrades</h2>
<ul>
<li>Upgraded to v42 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/1874">#1874</a>)</li>
</ul>
<p>Co-authored-by: jackton1 <a
href="mailto:jackton1@users.noreply.github.com">jackton1@users.noreply.github.com</a>
(<a
href="c037f1e7c5">c037f1e</a>)
- (tj-actions[bot])</p>
<h1><a
href="https://github.com/tj-actions/changed-files/compare/v41.1.2...v42.0.0">42.0.0</a>
- (2024-01-18)</h1>
<h2><!-- raw HTML omitted -->🚀 Features</h2>
<ul>
<li>Add support for include/exclude all nested files when a directory is
specified and ends with a slash (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/1873">#1873</a>)
(<a
href="ae82ed4ae0">ae82ed4</a>)
- (Tonye Jack)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="90a06d6ba9"><code>90a06d6</code></a>
feat: enhance error handling for non-git directories (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/1885">#1885</a>)</li>
<li><a
href="2cb2c9234e"><code>2cb2c92</code></a>
Upgraded to v42.0.1 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/1884">#1884</a>)</li>
<li><a
href="ac21d93904"><code>ac21d93</code></a>
chore(deps): update dependency <code>@​types/node</code> to
v20.11.6</li>
<li><a
href="a4637ea6e7"><code>a4637ea</code></a>
chore(deps): update typescript-eslint monorepo to v6.19.1</li>
<li><a
href="fd9998cf5f"><code>fd9998c</code></a>
chore(deps): update dependency ts-jest to v29.1.2</li>
<li><a
href="ea024b2d7f"><code>ea024b2</code></a>
Added missing changes and modified dist assets.</li>
<li><a
href="db4e584844"><code>db4e584</code></a>
chore(deps): lock file maintenance</li>
<li><a
href="c6543c497a"><code>c6543c4</code></a>
chore: rename example worflows from test to example (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/1878">#1878</a>)</li>
<li><a
href="88f9f3efbb"><code>88f9f3e</code></a>
chore: update README.md (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/1877">#1877</a>)</li>
<li><a
href="3af07c2040"><code>3af07c2</code></a>
Added missing changes and modified dist assets.</li>
<li>Additional commits viewable in <a
href="https://github.com/tj-actions/changed-files/compare/v41...v42">compare
view</a></li>
</ul>
</details>
<br />

Updates `actions/cache` from 3 to 4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/cache/releases">actions/cache's
releases</a>.</em></p>
<blockquote>
<h2>v4.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update action to node20 by <a
href="https://github.com/takost"><code>@​takost</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1284">actions/cache#1284</a></li>
<li>feat: save-always flag by <a
href="https://github.com/to-s"><code>@​to-s</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1242">actions/cache#1242</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/takost"><code>@​takost</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1284">actions/cache#1284</a></li>
<li><a href="https://github.com/to-s"><code>@​to-s</code></a> made their
first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1242">actions/cache#1242</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v3...v4.0.0">https://github.com/actions/cache/compare/v3...v4.0.0</a></p>
<h2>v3.3.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Cache v3.3.3 by <a
href="https://github.com/robherley"><code>@​robherley</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1302">actions/cache#1302</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/robherley"><code>@​robherley</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1302">actions/cache#1302</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v3...v3.3.3">https://github.com/actions/cache/compare/v3...v3.3.3</a></p>
<h2>v3.3.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Fixed readme with new segment timeout values by <a
href="https://github.com/kotewar"><code>@​kotewar</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1133">actions/cache#1133</a></li>
<li>Readme fixes by <a
href="https://github.com/kotewar"><code>@​kotewar</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1134">actions/cache#1134</a></li>
<li>Updated description of the lookup-only input for main action by <a
href="https://github.com/kotewar"><code>@​kotewar</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1130">actions/cache#1130</a></li>
<li>Change two new actions mention as quoted text by <a
href="https://github.com/bishal-pdMSFT"><code>@​bishal-pdMSFT</code></a>
in <a
href="https://redirect.github.com/actions/cache/pull/1131">actions/cache#1131</a></li>
<li>Update Cross-OS Caching tips by <a
href="https://github.com/pdotl"><code>@​pdotl</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1122">actions/cache#1122</a></li>
<li>Bazel example (Take <a
href="https://redirect.github.com/actions/cache/issues/2">#2</a>️⃣) by
<a href="https://github.com/vorburger"><code>@​vorburger</code></a> in
<a
href="https://redirect.github.com/actions/cache/pull/1132">actions/cache#1132</a></li>
<li>Remove actions to add new PRs and issues to a project board by <a
href="https://github.com/jorendorff"><code>@​jorendorff</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1187">actions/cache#1187</a></li>
<li>Consume latest toolkit and fix dangling promise bug by <a
href="https://github.com/chkimes"><code>@​chkimes</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1217">actions/cache#1217</a></li>
<li>Bump action version to 3.3.2 by <a
href="https://github.com/bethanyj28"><code>@​bethanyj28</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1236">actions/cache#1236</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/vorburger"><code>@​vorburger</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1132">actions/cache#1132</a></li>
<li><a
href="https://github.com/jorendorff"><code>@​jorendorff</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1187">actions/cache#1187</a></li>
<li><a href="https://github.com/chkimes"><code>@​chkimes</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1217">actions/cache#1217</a></li>
<li><a
href="https://github.com/bethanyj28"><code>@​bethanyj28</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1236">actions/cache#1236</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v3...v3.3.2">https://github.com/actions/cache/compare/v3...v3.3.2</a></p>
<h2>v3.3.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Reduced download segment size to 128 MB and timeout to 10 minutes by
<a href="https://github.com/kotewar"><code>@​kotewar</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1129">actions/cache#1129</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v3...v3.3.1">https://github.com/actions/cache/compare/v3...v3.3.1</a></p>
<h2>v3.3.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Bug: Permission is missing in cache delete example by <a
href="https://github.com/kotokaze"><code>@​kotokaze</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1123">actions/cache#1123</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/cache/blob/main/RELEASES.md">actions/cache's
changelog</a>.</em></p>
<blockquote>
<h1>Releases</h1>
<h3>3.0.0</h3>
<ul>
<li>Updated minimum runner version support from node 12 -&gt; node
16</li>
</ul>
<h3>3.0.1</h3>
<ul>
<li>Added support for caching from GHES 3.5.</li>
<li>Fixed download issue for files &gt; 2GB during restore.</li>
</ul>
<h3>3.0.2</h3>
<ul>
<li>Added support for dynamic cache size cap on GHES.</li>
</ul>
<h3>3.0.3</h3>
<ul>
<li>Fixed avoiding empty cache save when no files are available for
caching. (<a
href="https://redirect.github.com/actions/cache/issues/624">issue</a>)</li>
</ul>
<h3>3.0.4</h3>
<ul>
<li>Fixed tar creation error while trying to create tar with path as
<code>~/</code> home folder on <code>ubuntu-latest</code>. (<a
href="https://redirect.github.com/actions/cache/issues/689">issue</a>)</li>
</ul>
<h3>3.0.5</h3>
<ul>
<li>Removed error handling by consuming actions/cache 3.0 toolkit, Now
cache server error handling will be done by toolkit. (<a
href="https://redirect.github.com/actions/cache/pull/834">PR</a>)</li>
</ul>
<h3>3.0.6</h3>
<ul>
<li>Fixed <a
href="https://redirect.github.com/actions/cache/issues/809">#809</a> -
zstd -d: no such file or directory error</li>
<li>Fixed <a
href="https://redirect.github.com/actions/cache/issues/833">#833</a> -
cache doesn't work with github workspace directory</li>
</ul>
<h3>3.0.7</h3>
<ul>
<li>Fixed <a
href="https://redirect.github.com/actions/cache/issues/810">#810</a> -
download stuck issue. A new timeout is introduced in the download
process to abort the download if it gets stuck and doesn't finish within
an hour.</li>
</ul>
<h3>3.0.8</h3>
<ul>
<li>Fix zstd not working for windows on gnu tar in issues <a
href="https://redirect.github.com/actions/cache/issues/888">#888</a> and
<a
href="https://redirect.github.com/actions/cache/issues/891">#891</a>.</li>
<li>Allowing users to provide a custom timeout as input for aborting
download of a cache segment using an environment variable
<code>SEGMENT_DOWNLOAD_TIMEOUT_MINS</code>. Default is 60 minutes.</li>
</ul>
<h3>3.0.9</h3>
<ul>
<li>Enhanced the warning message for cache unavailablity in case of
GHES.</li>
</ul>
<h3>3.0.10</h3>
<ul>
<li>Fix a bug with sorting inputs.</li>
<li>Update definition for restore-keys in README.md</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="13aacd865c"><code>13aacd8</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/cache/issues/1242">#1242</a>
from to-s/main</li>
<li><a
href="53b35c5439"><code>53b35c5</code></a>
Merge branch 'main' into main</li>
<li><a
href="65b8989fab"><code>65b8989</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/cache/issues/1284">#1284</a>
from takost/update-to-node-20</li>
<li><a
href="d0be34d544"><code>d0be34d</code></a>
Fix dist</li>
<li><a
href="66cf064d47"><code>66cf064</code></a>
Merge branch 'main' into update-to-node-20</li>
<li><a
href="1326563738"><code>1326563</code></a>
Merge branch 'main' into main</li>
<li><a
href="e71876755e"><code>e718767</code></a>
Fix format</li>
<li><a
href="01229828ff"><code>0122982</code></a>
Apply workaround for earlyExit</li>
<li><a
href="3185ecfd61"><code>3185ecf</code></a>
Update &quot;only-&quot; actions to node20</li>
<li><a
href="25618a0a67"><code>25618a0</code></a>
Bump version</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/cache/compare/v3...v4">compare
view</a></li>
</ul>
</details>
<br />

Updates `peter-evans/find-comment` from 2 to 3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/peter-evans/find-comment/releases">peter-evans/find-comment's
releases</a>.</em></p>
<blockquote>
<h2>Find Comment v3.0.0</h2>
<p>⚙️  Updated runtime to Node.js 20</p>
<ul>
<li>The action now requires a minimum version of <a
href="https://github.com/actions/runner/releases/tag/v2.308.0">v2.308.0</a>
for the Actions runner. Update self-hosted runners to v2.308.0 or later
to ensure compatibility.</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>build(deps-dev): bump prettier from 2.8.7 to 2.8.8 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/173">peter-evans/find-comment#173</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.15.13 to
18.16.3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/175">peter-evans/find-comment#175</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from
5.59.0 to 5.59.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/176">peter-evans/find-comment#176</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code>
from 5.59.0 to 5.59.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/174">peter-evans/find-comment#174</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code>
from 5.59.1 to 5.59.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/177">peter-evans/find-comment#177</a></li>
<li>build(deps-dev): bump eslint from 8.39.0 to 8.40.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/179">peter-evans/find-comment#179</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from
5.59.1 to 5.59.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/178">peter-evans/find-comment#178</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.16.3 to
18.16.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/180">peter-evans/find-comment#180</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code>
from 5.59.2 to 5.59.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/181">peter-evans/find-comment#181</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from
5.59.2 to 5.59.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/183">peter-evans/find-comment#183</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.16.5 to
18.16.9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/182">peter-evans/find-comment#182</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from
5.59.5 to 5.59.6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/184">peter-evans/find-comment#184</a></li>
<li>build(deps-dev): bump eslint from 8.40.0 to 8.41.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/186">peter-evans/find-comment#186</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.16.9 to
18.16.13 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/187">peter-evans/find-comment#187</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code>
from 5.59.5 to 5.59.6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/185">peter-evans/find-comment#185</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.16.13 to
18.16.16 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/188">peter-evans/find-comment#188</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from
5.59.6 to 5.59.7 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/190">peter-evans/find-comment#190</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code>
from 5.59.6 to 5.59.7 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/189">peter-evans/find-comment#189</a></li>
<li>build(deps-dev): bump eslint from 8.41.0 to 8.42.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/191">peter-evans/find-comment#191</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from
5.59.7 to 5.59.8 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/193">peter-evans/find-comment#193</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code>
from 5.59.7 to 5.59.8 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/194">peter-evans/find-comment#194</a></li>
<li>build(deps-dev): bump eslint-plugin-github from 4.7.0 to 4.8.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/192">peter-evans/find-comment#192</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code>
from 5.59.8 to 5.59.9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/195">peter-evans/find-comment#195</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from
5.59.8 to 5.59.9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/197">peter-evans/find-comment#197</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.16.16 to
18.16.17 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/196">peter-evans/find-comment#196</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from
5.59.9 to 5.59.11 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/198">peter-evans/find-comment#198</a></li>
<li>build(deps-dev): bump eslint from 8.42.0 to 8.43.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/199">peter-evans/find-comment#199</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.16.17 to
18.16.18 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/200">peter-evans/find-comment#200</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code>
from 5.59.9 to 5.59.11 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/201">peter-evans/find-comment#201</a></li>
<li>build(deps-dev): bump eslint-plugin-jest from 27.2.1 to 27.2.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/202">peter-evans/find-comment#202</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code>
from 5.59.11 to 5.60.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/203">peter-evans/find-comment#203</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from
5.59.11 to 5.60.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/204">peter-evans/find-comment#204</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.16.18 to
18.16.19 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/205">peter-evans/find-comment#205</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from
5.60.0 to 5.60.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/206">peter-evans/find-comment#206</a></li>
<li>build(deps-dev): bump eslint from 8.43.0 to 8.44.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/207">peter-evans/find-comment#207</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code>
from 5.60.0 to 5.60.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/208">peter-evans/find-comment#208</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code>
from 5.60.1 to 5.61.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/209">peter-evans/find-comment#209</a></li>
<li>build(deps): bump tough-cookie from 4.1.2 to 4.1.3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/211">peter-evans/find-comment#211</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from
5.60.1 to 5.61.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/210">peter-evans/find-comment#210</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code>
from 5.61.0 to 5.62.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/212">peter-evans/find-comment#212</a></li>
<li>build(deps-dev): bump eslint from 8.44.0 to 8.45.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/214">peter-evans/find-comment#214</a></li>
<li>build(deps-dev): bump eslint-plugin-jest from 27.2.2 to 27.2.3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/215">peter-evans/find-comment#215</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from
5.61.0 to 5.62.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/213">peter-evans/find-comment#213</a></li>
<li>build(deps-dev): bump eslint-plugin-github from 4.8.0 to 4.9.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/216">peter-evans/find-comment#216</a></li>
<li>build(deps-dev): bump word-wrap from 1.2.3 to 1.2.4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/find-comment/pull/217">peter-evans/find-comment#217</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="d5fe37641a"><code>d5fe376</code></a>
feat: update runtime to node 20 (<a
href="https://redirect.github.com/peter-evans/find-comment/issues/282">#282</a>)</li>
<li><a
href="e3754082ec"><code>e375408</code></a>
build(deps-dev): bump <code>@​types/node</code> from 18.19.6 to 18.19.8
(<a
href="https://redirect.github.com/peter-evans/find-comment/issues/279">#279</a>)</li>
<li><a
href="6f781399d6"><code>6f78139</code></a>
build(deps-dev): bump prettier from 3.2.1 to 3.2.4 (<a
href="https://redirect.github.com/peter-evans/find-comment/issues/278">#278</a>)</li>
<li><a
href="663f5b8fd8"><code>663f5b8</code></a>
build(deps-dev): bump eslint-plugin-jest from 27.6.1 to 27.6.3 (<a
href="https://redirect.github.com/peter-evans/find-comment/issues/276">#276</a>)</li>
<li><a
href="1950d48590"><code>1950d48</code></a>
build(deps-dev): bump prettier from 3.1.1 to 3.2.1 (<a
href="https://redirect.github.com/peter-evans/find-comment/issues/277">#277</a>)</li>
<li><a
href="4c49b27bc3"><code>4c49b27</code></a>
build(deps-dev): bump eslint-plugin-prettier from 5.1.2 to 5.1.3 (<a
href="https://redirect.github.com/peter-evans/find-comment/issues/275">#275</a>)</li>
<li><a
href="141f79c0a8"><code>141f79c</code></a>
build(deps-dev): bump <code>@​types/node</code> from 18.19.4 to 18.19.6
(<a
href="https://redirect.github.com/peter-evans/find-comment/issues/274">#274</a>)</li>
<li><a
href="90d027df0e"><code>90d027d</code></a>
build(deps-dev): bump eslint-plugin-jest from 27.6.0 to 27.6.1 (<a
href="https://redirect.github.com/peter-evans/find-comment/issues/273">#273</a>)</li>
<li><a
href="4541d1b6b0"><code>4541d1b</code></a>
build(deps-dev): bump eslint-plugin-prettier from 5.1.1 to 5.1.2 (<a
href="https://redirect.github.com/peter-evans/find-comment/issues/272">#272</a>)</li>
<li><a
href="3e2c601e8c"><code>3e2c601</code></a>
build(deps-dev): bump <code>@​types/node</code> from 18.19.3 to 18.19.4
(<a
href="https://redirect.github.com/peter-evans/find-comment/issues/271">#271</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/peter-evans/find-comment/compare/v2...v3">compare
view</a></li>
</ul>
</details>
<br />

Updates `peter-evans/create-or-update-comment` from 3 to 4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/peter-evans/create-or-update-comment/releases">peter-evans/create-or-update-comment's
releases</a>.</em></p>
<blockquote>
<h2>Create or Update Comment v4.0.0</h2>
<p>⚙️  Updated runtime to Node.js 20</p>
<ul>
<li>The action now requires a minimum version of <a
href="https://github.com/actions/runner/releases/tag/v2.308.0">v2.308.0</a>
for the Actions runner. Update self-hosted runners to v2.308.0 or later
to ensure compatibility.</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>build(deps): bump actions/setup-node from 3 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/273">peter-evans/create-or-update-comment#273</a></li>
<li>build(deps-dev): bump <code>@​vercel/ncc</code> from 0.38.0 to
0.38.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/274">peter-evans/create-or-update-comment#274</a></li>
<li>build(deps-dev): bump eslint-plugin-jest from 27.4.2 to 27.4.3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/276">peter-evans/create-or-update-comment#276</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.18.5 to
18.18.6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/277">peter-evans/create-or-update-comment#277</a></li>
<li>build(deps-dev): bump eslint from 8.51.0 to 8.52.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/275">peter-evans/create-or-update-comment#275</a></li>
<li>build(deps-dev): bump eslint-plugin-jest from 27.4.3 to 27.6.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/278">peter-evans/create-or-update-comment#278</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.18.6 to
18.18.8 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/279">peter-evans/create-or-update-comment#279</a></li>
<li>build(deps-dev): bump eslint from 8.52.0 to 8.53.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/280">peter-evans/create-or-update-comment#280</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.18.8 to
18.18.9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/281">peter-evans/create-or-update-comment#281</a></li>
<li>build(deps-dev): bump prettier from 3.0.3 to 3.1.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/282">peter-evans/create-or-update-comment#282</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.18.9 to
18.18.12 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/283">peter-evans/create-or-update-comment#283</a></li>
<li>build(deps-dev): bump eslint from 8.53.0 to 8.54.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/284">peter-evans/create-or-update-comment#284</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.18.12 to
18.18.13 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/285">peter-evans/create-or-update-comment#285</a></li>
<li>build(deps-dev): bump eslint from 8.54.0 to 8.55.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/286">peter-evans/create-or-update-comment#286</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.18.13 to
18.19.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/287">peter-evans/create-or-update-comment#287</a></li>
<li>build(deps): bump chuhlomin/render-template from 1.8 to 1.9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/288">peter-evans/create-or-update-comment#288</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.19.2 to
18.19.3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/289">peter-evans/create-or-update-comment#289</a></li>
<li>build(deps-dev): bump prettier from 3.1.0 to 3.1.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/290">peter-evans/create-or-update-comment#290</a></li>
<li>build(deps-dev): bump eslint-plugin-prettier from 5.0.1 to 5.1.0 by
<a href="https://github.com/dependabot"><code>@​dependabot</code></a> in
<a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/292">peter-evans/create-or-update-comment#292</a></li>
<li>build(deps-dev): bump eslint from 8.55.0 to 8.56.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/293">peter-evans/create-or-update-comment#293</a></li>
<li>build(deps): bump actions/download-artifact from 3 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/295">peter-evans/create-or-update-comment#295</a></li>
<li>build(deps-dev): bump eslint-plugin-prettier from 5.1.0 to 5.1.2 by
<a href="https://github.com/dependabot"><code>@​dependabot</code></a> in
<a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/296">peter-evans/create-or-update-comment#296</a></li>
<li>build(deps-dev): bump eslint-plugin-jest from 27.6.0 to 27.6.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/297">peter-evans/create-or-update-comment#297</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.19.3 to
18.19.4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/298">peter-evans/create-or-update-comment#298</a></li>
<li>build(deps-dev): bump eslint-plugin-prettier from 5.1.2 to 5.1.3 by
<a href="https://github.com/dependabot"><code>@​dependabot</code></a> in
<a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/299">peter-evans/create-or-update-comment#299</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.19.4 to
18.19.6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/300">peter-evans/create-or-update-comment#300</a></li>
<li>build(deps-dev): bump prettier from 3.1.1 to 3.2.3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/301">peter-evans/create-or-update-comment#301</a></li>
<li>build(deps-dev): bump eslint-plugin-jest from 27.6.1 to 27.6.3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/302">peter-evans/create-or-update-comment#302</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.19.6 to
18.19.7 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/303">peter-evans/create-or-update-comment#303</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 18.19.7 to
18.19.8 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/304">peter-evans/create-or-update-comment#304</a></li>
<li>build(deps-dev): bump prettier from 3.2.3 to 3.2.4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/305">peter-evans/create-or-update-comment#305</a></li>
<li>feat: update runtime to node 20 by <a
href="https://github.com/peter-evans"><code>@​peter-evans</code></a> in
<a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/306">peter-evans/create-or-update-comment#306</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/peter-evans/create-or-update-comment/compare/v3.1.0...v4.0.0">https://github.com/peter-evans/create-or-update-comment/compare/v3.1.0...v4.0.0</a></p>
<h2>Create or Update Comment v3.1.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Add truncate warning to body of comment by <a
href="https://github.com/ethanmdavidson"><code>@​ethanmdavidson</code></a>
and <a
href="https://github.com/peter-evans"><code>@​peter-evans</code></a> in
<a
href="https://redirect.github.com/peter-evans/create-or-update-comment/pull/272">peter-evans/create-or-update-comment#272</a></li>
<li>46 dependency updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/peter-evans/create-or-update-comment/compare/v3.0.2...v3.1.0">https://github.com/peter-evans/create-or-update-comment/compare/v3.0.2...v3.1.0</a></p>
<h2>Create or Update Comment v3.0.2</h2>
<h2>What's Changed</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="71345be026"><code>71345be</code></a>
feat: update runtime to node 20 (<a
href="https://redirect.github.com/peter-evans/create-or-update-comment/issues/306">#306</a>)</li>
<li><a
href="d41bfe36e5"><code>d41bfe3</code></a>
build(deps-dev): bump prettier from 3.2.3 to 3.2.4 (<a
href="https://redirect.github.com/peter-evans/create-or-update-comment/issues/305">#305</a>)</li>
<li><a
href="73b4b9e4e3"><code>73b4b9e</code></a>
build(deps-dev): bump <code>@​types/node</code> from 18.19.7 to 18.19.8
(<a
href="https://redirect.github.com/peter-evans/create-or-update-comment/issues/304">#304</a>)</li>
<li><a
href="b865fac7fa"><code>b865fac</code></a>
build(deps-dev): bump <code>@​types/node</code> from 18.19.6 to 18.19.7
(<a
href="https://redirect.github.com/peter-evans/create-or-update-comment/issues/303">#303</a>)</li>
<li><a
href="52b668a928"><code>52b668a</code></a>
build(deps-dev): bump eslint-plugin-jest from 27.6.1 to 27.6.3 (<a
href="https://redirect.github.com/peter-evans/create-or-update-comment/issues/302">#302</a>)</li>
<li><a
href="974f56a1c3"><code>974f56a</code></a>
build(deps-dev): bump prettier from 3.1.1 to 3.2.3 (<a
href="https://redirect.github.com/peter-evans/create-or-update-comment/issues/301">#301</a>)</li>
<li><a
href="2cbfe8b17b"><code>2cbfe8b</code></a>
build(deps-dev): bump <code>@​types/node</code> from 18.19.4 to 18.19.6
(<a
href="https://redirect.github.com/peter-evans/create-or-update-comment/issues/300">#300</a>)</li>
<li><a
href="761872a701"><code>761872a</code></a>
build(deps-dev): bump eslint-plugin-prettier from 5.1.2 to 5.1.3 (<a
href="https://redirect.github.com/peter-evans/create-or-update-comment/issues/299">#299</a>)</li>
<li><a
href="72c3238a49"><code>72c3238</code></a>
build(deps-dev): bump <code>@​types/node</code> from 18.19.3 to 18.19.4
(<a
href="https://redirect.github.com/peter-evans/create-or-update-comment/issues/298">#298</a>)</li>
<li><a
href="07daf7bbdb"><code>07daf7b</code></a>
build(deps-dev): bump eslint-plugin-jest from 27.6.0 to 27.6.1 (<a
href="https://redirect.github.com/peter-evans/create-or-update-comment/issues/297">#297</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/peter-evans/create-or-update-comment/compare/v3...v4">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 09:39:57 -06:00
dependabot[bot]
63a7de9018 Bump wild from 2.2.0 to 2.2.1 (#9739) 2024-01-31 15:37:30 +00:00
dependabot[bot]
b77021b9ed Bump clap from 4.4.13 to 4.4.18 (#9742) 2024-01-31 15:37:00 +00:00
Charlie Marsh
f0066e1b89 Use publicly available Apple Silicon runners (#9726)
## Summary

This PR switches over to the `macos-14` runners for our macOS wheel
builds, which are GitHub's newly announced public M1 macOS runners
(https://github.blog/changelog/2024-01-30-github-actions-introducing-the-new-m1-macos-runner-available-to-open-source/).

Before:

- x64_64: 10m 38s
(https://github.com/astral-sh/ruff/actions/runs/7703465381/job/20993903864)
- Universal: 19m 35s
(https://github.com/astral-sh/ruff/actions/runs/7703465381/job/20993902533)

After:

- x64_64: 3m 30s
(https://github.com/astral-sh/ruff/actions/runs/7719827902/job/21043743558?pr=9726)
- Universal: 5m 59s
(https://github.com/astral-sh/ruff/actions/runs/7719827902/job/21043743243?pr=9726)

So it's like > 3x speedup for what is currently the bottleneck in our
release pipeline.
2024-01-31 10:36:41 -05:00
Zanie Blue
7642fb7f27 Excludes upload and download artifact dependencies from dependabot (#9736)
e.g. in https://github.com/astral-sh/ruff/pull/9667 we cannot upgrade
them but want to upgrade the rest
2024-01-31 15:18:19 +00:00
Sai-Suraj-27
7a1fa0e5d8 Update README.md by adding ivy repository to the who's using Ruff section (#9735)
## Summary
I have recently made a contribution to a big python based repo
**replacing flake8 with ruff**
(https://github.com/unifyai/ivy/pull/27779). So, as per this
[discussion](https://github.com/astral-sh/ruff/discussions/9731). I am
making this PR to add the [ivy](https://github.com/unifyai/ivy)
repository (**with > 13k stars **) to the Ruff's readme

## Test Plan
No, need of any tests for the changes made.
2024-01-31 15:09:26 +00:00
Micha Reiser
ce14f4dea5 Range formatting API (#9635) 2024-01-31 11:13:37 +01:00
Alex Waygood
6bb126415d RUF023: Don't sort __match_args__, only __slots__ (#9724)
Fixes #9723. I'm pretty embarrassed I forgot that order was important
here :(
2024-01-30 22:44:49 +00:00
Dhruv Manilawala
541aef4e6c Implement blank_line_after_nested_stub_class preview style (#9155)
## Summary

This PR implements the `blank_line_after_nested_stub_class` preview
style in the formatter.

The logic is divided into 3 parts:
1. In between preceding and following nodes at top level and nested
suite
2. When there's a trailing comment after the class
3. When there is no following node from (1) which is the case when it's
the last or the only node in a suite

We handle (3) with `FormatLeadingAlternateBranchComments`.

## Test Plan

- Add new test cases and update existing snapshots
- Checked the `typeshed` diff

fixes: #8891
2024-01-31 00:09:38 +05:30
Mikko Leppänen
79f0522eb7 [flake8-async] Take pathlib.Path into account when analyzing async functions (#9703)
## Summary

This review contains a fix for
[ASYNC101](https://docs.astral.sh/ruff/rules/open-sleep-or-subprocess-in-async-function/)
(open-sleep-or-subprocess-in-async-function)

The problem is that ruff does not take open calls from pathlib.Path into
account in async functions. Path.open() call is still a blocking call.
In addition, PTH123 suggests to use pathlib.Path instead of os.open. So
this might create an additional confusion.

See: https://github.com/astral-sh/ruff/issues/6892

## Test Plan

```bash
cargo test
```
2024-01-30 17:42:50 +00:00
Alex Waygood
0c8d140321 RUF022, RUF023: never add two trailing commas to the end of a sequence (#9698)
Fixes the issues highlighted in
https://github.com/astral-sh/ruff/issues/8402#issuecomment-1916203707
and
https://github.com/astral-sh/ruff/issues/8402#issuecomment-1916213693
2024-01-30 17:19:38 +00:00
Zanie Blue
f38fb2432f Add timeouts to all CI jobs (#9709)
To prevent jobs from running far beyond their expected time
2024-01-30 11:13:23 -06:00
Steve C
f0e598ea84 [flake8-return] Fix indentation syntax error (RET505) (#9705)
## Summary

Fix for
https://github.com/astral-sh/ruff/issues/8402#issuecomment-1916223126

## Test Plan

`cargo test`
2024-01-30 16:46:04 +00:00
Bartosz Sławecki
b6a96452fc [pylint] Add __mro_entries__ to known dunder methods (PLW3201) (#9706)
## Summary

This change adds
[`__mro_entries__`](https://docs.python.org/3/reference/datamodel.html#object.__mro_entries__)
to the list of known dunder methods.
2024-01-30 11:41:19 -05:00
Steve C
214563261d [flake8-simplify] - Fix syntax error in autofix (SIM114) (#9704)
## Summary

A fix for
https://github.com/astral-sh/ruff/issues/8402#issuecomment-1916215124

Improves the code, as well. :)

## Test Plan

`cargo test`
2024-01-30 11:36:44 -05:00
Micha Reiser
3c7fea769c Show source-type in formatter snapshot tests with options (#9699) 2024-01-30 10:08:50 +00:00
Steve C
dacda0f202 [pylint] Show verbatim constant in magic-value-comparison (PLR2004) (#9694)
## Summary

Tweaks PLR2004 to show the literal source text, rather than the constant
value.

I noticed this when I had a hexadecimal constant, and the linter turned
it into base-10.

Now, if you have `0x300`, it will show `0x300` instead of `768`.

Also, added backticks around the constant in the output message.

## Test Plan

`cargo test`
2024-01-30 00:22:09 -05:00
Charlie Marsh
95e1444c90 Remove empty section from changelog (#9693) 2024-01-29 19:52:13 -05:00
Charlie Marsh
a7755d7a8d Bump version to v0.1.15 (#9690) 2024-01-29 17:44:05 -05:00
Charlie Marsh
11449acfd9 Avoid marking InitVar as a typing-only annotation (#9688)
## Summary

Given:

```python
from dataclasses import InitVar, dataclass

@dataclass
class C:
    i: int
    j: int = None
    database: InitVar[DatabaseType] = None

    def __post_init__(self, database):
        if self.j is None and database is not None:
            self.j = database.lookup('j')

c = C(10, database=my_database)
```

We should avoid marking `InitVar` as typing-only, since it _is_ required
by the dataclass at runtime.

Note that by default, we _already_ don't flag this, since the
`@dataclass` member is needed at runtime too -- so it's only a problem
with `strict` mode.

Closes https://github.com/astral-sh/ruff/issues/9666.
2024-01-29 16:27:20 -05:00
Zanie Blue
4ccbacd44b Error if the NURSERY selector is used with preview (#9682)
Changes our warning for combined use of `--preview` and `--select
NURSERY` to a hard error.

This should go out _before_ #9680 where we will ban use of `NURSERY`
outside of preview as well (see #9683).

Part of https://github.com/astral-sh/ruff/issues/7992
2024-01-29 13:33:46 -06:00
Charlie Marsh
05a2f52206 Document literal-membership fix safety conditions (#9677)
## Summary

This seems safe to me. See
https://github.com/astral-sh/ruff/issues/8482#issuecomment-1859299411.

## Test Plan

`cargo test`
2024-01-29 17:48:40 +00:00
Charlie Marsh
a6f7100b55 [pycodestyle] Allow dtype comparisons in type-comparison (#9676)
## Summary

Per https://github.com/astral-sh/ruff/issues/9570:

> `dtype` are a bit of a strange beast, but definitely best thought of
as instances, not classes, and they are meant to be comparable not just
to their own class, but also to the corresponding scalar types (e.g.,
`x.dtype == np.float32`) and strings (e.g., `x.dtype == ['i1,i4']`;
basically, `__eq__` always tries to do `dtype(other)`.

This PR thus allows comparisons to `dtype` in preview.

Closes https://github.com/astral-sh/ruff/issues/9570.

## Test Plan

`cargo test`
2024-01-29 12:39:01 -05:00
Charlie Marsh
50122d2308 [flake8-pytest-style] Add fix safety documentation for duplicate-parameterize-test-cases (#9678)
See:
https://github.com/astral-sh/ruff/issues/8482#issuecomment-1859299411.
2024-01-29 17:33:22 +00:00
Mikko Leppänen
ad2cfa3dba [flake8-return] Consider exception suppress for unnecessary assignment (#9673)
## Summary

This review contains a fix for
[RET504](https://docs.astral.sh/ruff/rules/unnecessary-assign/)
(unnecessary-assign)

The problem is that Ruff suggests combining a return statement inside
contextlib.suppress. Even though it is an unsafe fix it might lead to an
invalid code that is not equivalent to the original one.

See: https://github.com/astral-sh/ruff/issues/5909

## Test Plan

```bash
cargo test
```
2024-01-29 12:29:05 -05:00
Micha Reiser
0045032905 Set source type: Stub for black tests with options (#9674) 2024-01-29 15:54:30 +01:00
Charlie Marsh
bea8f2ee3a Detect automagic-like assignments in notebooks (#9653)
## Summary

Given a statement like `colors = 6`, we currently treat the cell as an
automagic (since `colors` is an automagic) -- i.e., we assume it's
equivalent to `%colors = 6`. This PR adds some additional detection
whereby if the statement is an _assignment_, we avoid treating it as
such. I audited the list of automagics, and I believe this is safe for
all of them.

Closes https://github.com/astral-sh/ruff/issues/8526.

Closes https://github.com/astral-sh/ruff/issues/9648.

## Test Plan

`cargo test`
2024-01-29 12:55:44 +00:00
dependabot[bot]
c8074b0e18 Bump serde_with from 3.5.0 to 3.5.1 (#9672)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 10:10:22 +00:00
dependabot[bot]
740d8538de Bump chrono from 0.4.31 to 0.4.33 (#9671)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 11:01:46 +01:00
dependabot[bot]
341d7447a0 Bump serde_json from 1.0.111 to 1.0.113 (#9670)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 11:00:55 +01:00
dependabot[bot]
2e8aff647b Bump rayon from 1.8.0 to 1.8.1 (#9669)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 11:00:41 +01:00
dependabot[bot]
e555cf0ae4 Bump wasm-bindgen-test from 0.3.39 to 0.3.40 (#9668)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 10:59:58 +01:00
Mikko Leppänen
b9139a31d5 [flake8-pie] Omit bound tuples passed to .startswith or .endswith (#9661)
## Summary

This review contains a fix for
[PIE810](https://docs.astral.sh/ruff/rules/multiple-starts-ends-with/)
(multiple-starts-ends-with)

The problem is that ruff suggests combining multiple startswith/endswith
calls into a single call even though there might be a call with tuple of
strs. This leads to calling startswith/endswith with tuple of tuple of
strs which is incorrect and violates startswith/endswith conctract and
results in runtime failure.

However the following will be valid and fixed correctly => 
```python
x = ("hello", "world")
y = "h"
z = "w"
msg = "hello world"

if msg.startswith(x) or msg.startswith(y) or msg.startswith(z) :
      sys.exit(1)
```
```
ruff --fix --select PIE810 --unsafe-fixes
```
=> 
```python
if msg.startswith(x) or msg.startswith((y,z)):
      sys.exit(1)
```

See: https://github.com/astral-sh/ruff/issues/8906

## Test Plan

```bash
cargo test
```
2024-01-28 19:29:04 +00:00
Charlie Marsh
7329bf459c Avoid panic when fixing inlined else blocks (#9657)
Closes https://github.com/astral-sh/ruff/issues/9655.
2024-01-27 14:15:33 +00:00
Charlie Marsh
157d5bacfc [pydocstyle] Re-implement last-line-after-section (D413) (#9654)
## Summary

This rule was just incorrect, it didn't match the examples in the docs.
(It's a very rarely-used rule since it's not included in any of the
conventions.)

Closes https://github.com/astral-sh/ruff/issues/9452.
2024-01-26 19:30:59 +00:00
Mikko Leppänen
d496c164d3 [ruff] Guard against use of default_factory as a keyword argument (RUF026) (#9651)
## Summary

Add a rule for defaultdict(default_factory=callable). Instead suggest
using defaultdict(callable).

See: https://github.com/astral-sh/ruff/issues/9509

If a user tries to bind a "non-callable" to default_factory, the rule
ignores it. Another option would be to warn that it's probably not what
you want. Because Python allows the following:

```python 
from collections import defaultdict

defaultdict(default_factory=1)
```
this raises after you actually try to use it:

```python
dd = defaultdict(default_factory=1)
dd[1]
```
=> 
```bash
KeyError: 1
```

Instead using callable directly in the constructor it will raise (not
being a callable):

```python 
from collections import defaultdict

defaultdict(1)
```
=> 
```bash
TypeError: first argument must be callable or None
```




## Test Plan

```bash
cargo test
```
2024-01-26 19:10:05 +00:00
Charlie Marsh
b61b0edeea Add Pydantic's BaseConfig to default-copy list (#9650)
Closes https://github.com/astral-sh/ruff/issues/9647.
2024-01-26 14:54:48 +00:00
Charlie Marsh
79a0ddc112 Avoid rendering display-only rules as fixable (#9649)
Closes https://github.com/astral-sh/ruff/issues/9505.

The `ERA` rule is no longer marked as fixable:

![Screenshot 2024-01-26 at 9 17
48 AM](https://github.com/astral-sh/ruff/assets/1309177/fdc6217f-38ff-4098-b6ca-37ff51b710ab)
2024-01-26 09:47:01 -05:00
Micha Reiser
91046e4c81 Preserve indent around multiline strings (#9637) 2024-01-26 08:18:30 +01:00
Micha Reiser
5fe0fdd0a8 Delete is_node_with_body method (#9643) 2024-01-25 14:41:13 +00:00
Steve C
ffd13e65ae [flake8-return] - Add fixes for (RET505, RET506, RET507, RET508) (#9595) 2024-01-25 08:28:32 +01:00
Steve C
dba2cb79cb [pylint] Implement too-many-nested-blocks (PLR1702) (#9172)
## Summary

Implement
[`PLR1702`/`too-many-nested-blocks`](https://pylint.readthedocs.io/en/latest/user_guide/messages/refactor/too-many-nested-blocks.html)

See: #970 

## Test Plan

`cargo test`
2024-01-24 19:30:01 +00:00
Mikko Leppänen
45628a5883 [flake8-return] Take NoReturn annotation into account when analyzing implicit returns (#9636)
## Summary

When we are analyzing the implicit return rule this change add an
additional check to verify if the call expression has been annotated
with NoReturn type from typing module.

See: https://github.com/astral-sh/ruff/issues/5474

## Test Plan

```bash
cargo test
```
2024-01-24 17:19:26 +00:00
Andrew Gallant
fc3e2664f9 help: enable auto-wrapping of help output (#9633)
Previously, without the 'wrap_help' feature enabled, Clap would not do
any auto-wrapping of help text. For help text with long lines, this
tends to lead to non-ideal formatting. It can be especially difficult to
read when the width of the terminal is smaller.

This commit enables 'wrap_help', which will automatically cause Clap to
query the terminal size and wrap according to that. Or, if the terminal
size cannot be determined, it will default to a maximum line width of
100.

Ref https://github.com/astral-sh/ruff/pull/9599#discussion_r1464992692
2024-01-24 10:51:07 -05:00
Charlie Marsh
b0d6fd7343 Generate custom JSON schema for dynamic setting (#9632)
## Summary

If you paste in the TOML for our default configuration (from the docs),
it's rejected by our JSON Schema:

![Screenshot 2024-01-23 at 10 08
09 PM](https://github.com/astral-sh/ruff/assets/1309177/7b4ea6e8-07db-4590-bd1e-73a01a35d747)

It seems like the issue is with:

```toml
# Set the line length limit used when formatting code snippets in
# docstrings.
#
# This only has an effect when the `docstring-code-format` setting is
# enabled.
docstring-code-line-length = "dynamic"
```

Specifically, since that value uses a custom Serde implementation, I
guess Schemars bails out? This PR adds a custom representation to allow
`"dynamic"` (but no other strings):

![Screenshot 2024-01-23 at 10 27
21 PM](https://github.com/astral-sh/ruff/assets/1309177/ab7809d4-b077-44e9-8f98-ed893aaefe5d)

This seems like it should work but I don't have a great way to test it.

Closes https://github.com/astral-sh/ruff/issues/9630.
2024-01-24 04:26:02 +00:00
Charlie Marsh
87821252d7 Update SchemaStore script to install npm dependencies (#9631)
## Summary

SchemaStore now depends on some Prettier plugins, so we need to install
Prettier and its plugins as part of the project (rather than relying on
a global install).

## Test Plan

Ran the script!
2024-01-23 23:19:36 -05:00
Akira Noda
57313d9d63 [pylint] Implement assigning-non-slot (E0237) (#9623)
## Summary

Implement [assigning-non-slot /
E0237](https://pylint.readthedocs.io/en/latest/user_guide/messages/error/assigning-non-slot.html)

related #970

## Test Plan

`cargo test`
2024-01-24 02:50:22 +00:00
Mikko Leppänen
eab1a6862b [ruff] Detect unnecessary dict comprehensions for iterables (RUF025) (#9613)
## Summary

Checks for unnecessary `dict` comprehension when creating a new
dictionary from iterable. Suggest to replace with
`dict.fromkeys(iterable)`

See: https://github.com/astral-sh/ruff/issues/9592

## Test Plan

```bash
cargo test
```
2024-01-24 02:15:38 +00:00
Micha Reiser
395bf3dc98 Fix the input for black's line ranges test file (#9622) 2024-01-23 10:40:23 +00:00
Charlie Marsh
47b8a897e7 [flake8-simplify] Support inverted returns in needless-bool (SIM103) (#9619)
Closes https://github.com/astral-sh/ruff/issues/9618.
2024-01-23 03:26:53 +00:00
dependabot[bot]
e81976f754 Bump ignore from 0.4.21 to 0.4.22 (#9606)
Bumps [ignore](https://github.com/BurntSushi/ripgrep) from 0.4.21 to
0.4.22.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="2c3897585d"><code>2c38975</code></a>
ignore-0.4.22</li>
<li><a
href="c8e4a84519"><code>c8e4a84</code></a>
cli: prefix all non-fatal error messages with 'rg: '</li>
<li><a
href="b9c774937f"><code>b9c7749</code></a>
ignore: fix reference cycle for compiled matchers</li>
<li><a
href="67dd809a80"><code>67dd809</code></a>
ignore: add some 'allow(dead_code)' annotations</li>
<li><a
href="e0a85678e1"><code>e0a8567</code></a>
complete/fish: improve shell completions for fish</li>
<li><a
href="56c7ad175a"><code>56c7ad1</code></a>
ignore/types: add Lean</li>
<li><a
href="2a4dba3fbf"><code>2a4dba3</code></a>
ignore/types: add meson.options</li>
<li><a
href="daa157b5f9"><code>daa157b</code></a>
core: actually implement --sortr=path</li>
<li><a
href="0096c74c11"><code>0096c74</code></a>
grep-0.3.1</li>
<li><a
href="8c48355b03"><code>8c48355</code></a>
deps: bump grep-printer to 0.2.1</li>
<li>Additional commits viewable in <a
href="https://github.com/BurntSushi/ripgrep/compare/ignore-0.4.21...ignore-0.4.22">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ignore&package-manager=cargo&previous-version=0.4.21&new-version=0.4.22)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 08:00:16 -05:00
Alex Waygood
f5061dbb8e Add a rule/autofix to sort __slots__ and __match_args__ (#9564)
## Summary

This PR introduces a new rule to sort `__slots__` and `__match_args__`
according to a [natural sort](https://en.wikipedia.org/wiki/Natural_sort_order), as was
requested in https://github.com/astral-sh/ruff/issues/1198#issuecomment-1881418365.

The implementation here generalises some of the machinery introduced in
3aae16f1bd
so that different kinds of sorts can be applied to lists of string
literals. (We use an "isort-style" sort for `__all__`, but that isn't
really appropriate for `__slots__` and `__match_args__`, where nearly
all items will be snake_case.) Several sections of code have been moved
from `sort_dunder_all.rs` to a new module, `sorting_helpers.rs`, which
`sort_dunder_all.rs` and `sort_dunder_slots.rs` both make use of.

`__match_args__` is very similar to `__all__`, in that it can only be a
tuple or a list. `__slots__` differs from the other two, however, in
that it can be any iterable of strings. If slots is a dictionary, the
values are used by the builtin `help()` function as per-attribute
docstrings that show up in the output of `help()`. (There's no
particular use-case for making `__slots__` a set, but it's perfectly
legal at runtime, so there's no reason for us not to handle it in this
rule.)

Note that we don't do an autofix for multiline `__slots__` if `__slots__` is a dictionary: that's out of scope. Everything else, we can nearly always fix, however.

## Test Plan

`cargo test` / `cargo insta review`.

I also ran this rule on CPython, and the diff looked pretty good

---

Co-authored-by: Micha Reiser <micha@reiser.io>
2024-01-22 12:21:55 +00:00
dependabot[bot]
a3d667ed04 Bump shlex from 1.2.0 to 1.3.0 (#9609)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 08:39:48 +00:00
dependabot[bot]
76a069d0c9 Bump serde_with from 3.4.0 to 3.5.0 (#9608)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 08:39:28 +00:00
dependabot[bot]
b994fb3ce0 Bump smallvec from 1.11.2 to 1.13.1 (#9607)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 09:31:09 +01:00
dependabot[bot]
40a0231189 Bump serde_json from 1.0.109 to 1.0.111 (#9605)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 09:29:46 +01:00
Steve C
9c8a4d927e [flake8-simplify] Add fix for if-with-same-arms (SIM114) (#9591)
## Summary

 add fix for `if-with-same-arms` / `SIM114`

Also preserves comments!

## Test Plan

`cargo test`
2024-01-22 04:37:18 +00:00
trag1c
836e4406a5 Corrected code block hyperlink (#9602)
## Summary

Apparently MkDocs doesn't like when reference-style links have
formatting inside :)

<details>
<summary>Screenshots (before and after the change)</summary>
<img width="1235" alt="61353"
src="https://github.com/astral-sh/ruff/assets/77130613/e32a82bd-0c5d-4edb-998f-b53659a6c54d">

<img width="1237" alt="15526"
src="https://github.com/astral-sh/ruff/assets/77130613/bdafcda5-eb9c-4af6-af03-b4849c1e5c81">
</details>
2024-01-21 22:57:34 -05:00
Steve C
e54ed28ba9 [pylint] Add fix for collapsible-else-if (PLR5501) (#9594)
## Summary

adds a fix for `collapsible-else-if` / `PLR5501`

## Test Plan

`cargo test`
2024-01-21 19:53:15 -05:00
Tom Kuson
1e4b421a00 [ruff] Implement mutable-fromkeys-value (RUF024) (#9597)
## Summary

Implement rule `mutable-fromkeys-value` (`RUF023`).

Autofixes

```python
dict.fromkeys(foo, [])
```

to

```python
{key: [] for key in foo}
```

The fix is marked as unsafe as it changes runtime behaviour. It also
uses `key` as the comprehension variable, which may not always be
desired.

Closes #4613.

## Test Plan

`cargo test`
2024-01-22 00:22:02 +00:00
Charlie Marsh
a1f3cda190 Include global --config when determining namespace packages (#9603)
## Summary

When determining whether _any_ settings have namespace packages, we need
to consider the global settings (as would be provided via `--config`).
This was a subtle fallout of a refactor.

Closes https://github.com/astral-sh/ruff/issues/9579.

## Test Plan

Tested locally by compiling Ruff and running against this
[namespace-test](https://github.com/gokay05/namespace-test) repo.
2024-01-21 19:10:43 -05:00
Steve C
837984168a [pycodestyle] Add fix for multiple-imports-on-one-line (E401) (#9518)
## Summary

Add autofix for `multiple_imports_on_one_line`, `E401`

## Test Plan

`cargo test`
2024-01-21 15:33:38 -05:00
Charlie Marsh
b64aa1e86d Split pycodestyle import rules into separate files (#9600) 2024-01-21 19:30:00 +00:00
Steve C
9e5f3f1b1b [pylint] Add fix for useless-else-on-loop (PLW0120) (#9590)
## Summary

adds fix for `useless-else-on-loop` / `PLW0120`.

## Test Plan

`cargo test`
2024-01-21 11:17:58 -05:00
Robert Craigie
9dc59cbb81 Docs: fix isort rule code (#9598)
## Summary

Fixes a typo in the docs, the isort rule code in an example was not
correct.
2024-01-21 15:30:14 +00:00
Zanie Blue
a42600e9a2 Always unset the required-version option during ecosystem checks (#9593)
Uses our existing configuration overrides to unset the
`required-version` option in ecosystem projects during checks.

The downside to this approach, is we will now update the config file of
_every_ project (with a config file). This roughly normalizes the configuration file, as we
don't preserve comments and such. We could instead do a more targeted
approach applying this override to projects which we know use this
setting 🤷‍♀️
2024-01-20 23:07:11 -06:00
Steve C
49a445a23d [pylint] Implement potential-index-error (PLE0643) (#9545)
## Summary

add `potential-index-error` rule (`PLE0643`)

See: #970 

## Test Plan

`cargo test`
2024-01-21 03:59:48 +00:00
462 changed files with 18289 additions and 7500 deletions

View File

@@ -9,6 +9,10 @@ updates:
actions:
patterns:
- "*"
ignore:
# The latest versions of these are not compatible with our release workflow
- dependency-name: "actions/upload-artifact"
- dependency-name: "actions/download-artifact"
- package-ecosystem: "cargo"
directory: "/"

View File

@@ -35,7 +35,7 @@ jobs:
with:
fetch-depth: 0
- uses: tj-actions/changed-files@v41
- uses: tj-actions/changed-files@v42
id: changed
with:
files_yaml: |
@@ -76,6 +76,7 @@ jobs:
cargo-fmt:
name: "cargo fmt"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
@@ -87,6 +88,7 @@ jobs:
runs-on: ubuntu-latest
needs: determine_changes
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
@@ -100,10 +102,11 @@ jobs:
run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features --locked -- -D warnings
cargo-test-linux:
name: "cargo test (linux)"
runs-on: ubuntu-latest
needs: determine_changes
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
name: "cargo test (linux)"
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
@@ -114,7 +117,10 @@ jobs:
tool: cargo-insta
- uses: Swatinem/rust-cache@v2
- name: "Run tests"
run: cargo insta test --all --all-features --unreferenced reject
run: cargo insta test --all --exclude ruff_dev --all-features --unreferenced reject
- name: "Run dev tests"
# e.g. generating the schema — these should not run with all features enabled
run: cargo insta test -p ruff_dev --unreferenced reject
# Check for broken links in the documentation.
- run: cargo doc --all --no-deps
env:
@@ -126,10 +132,11 @@ jobs:
path: target/debug/ruff
cargo-test-windows:
name: "cargo test (windows)"
runs-on: windows-latest
needs: determine_changes
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
name: "cargo test (windows)"
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
@@ -142,13 +149,14 @@ jobs:
- name: "Run tests"
shell: bash
# We can't reject unreferenced snapshots on windows because flake8_executable can't run on windows
run: cargo insta test --all --all-features
run: cargo insta test --all --exclude ruff_dev --all-features
cargo-test-wasm:
name: "cargo test (wasm)"
runs-on: ubuntu-latest
needs: determine_changes
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
name: "cargo test (wasm)"
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
@@ -166,10 +174,11 @@ jobs:
wasm-pack test --node
cargo-fuzz:
name: "cargo fuzz"
runs-on: ubuntu-latest
needs: determine_changes
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
name: "cargo fuzz"
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
@@ -188,6 +197,7 @@ jobs:
runs-on: ubuntu-latest
needs: determine_changes
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
@@ -213,6 +223,7 @@ jobs:
if: github.event_name == 'pull_request' && ${{
needs.determine_changes.outputs.code == 'true'
}}
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
@@ -322,6 +333,7 @@ jobs:
runs-on: ubuntu-latest
needs: determine_changes
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: "Install nightly Rust toolchain"
@@ -336,6 +348,7 @@ jobs:
python-package:
name: "python package"
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
@@ -360,6 +373,7 @@ jobs:
pre-commit:
name: "pre-commit"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
@@ -371,7 +385,7 @@ jobs:
- name: "Install pre-commit"
run: pip install pre-commit
- name: "Cache pre-commit"
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
@@ -388,6 +402,7 @@ jobs:
docs:
name: "mkdocs"
runs-on: ubuntu-latest
timeout-minutes: 10
env:
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
steps:
@@ -425,6 +440,7 @@ jobs:
runs-on: ubuntu-latest
needs: determine_changes
if: needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main'
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
@@ -441,6 +457,7 @@ jobs:
check-ruff-lsp:
name: "test ruff-lsp"
runs-on: ubuntu-latest
timeout-minutes: 5
needs:
- cargo-test-linux
- determine_changes
@@ -484,6 +501,7 @@ jobs:
runs-on: ubuntu-latest
needs: determine_changes
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
timeout-minutes: 20
steps:
- name: "Checkout Branch"
uses: actions/checkout@v4

View File

@@ -61,7 +61,7 @@ jobs:
echo 'EOF' >> $GITHUB_OUTPUT
- name: Find existing comment
uses: peter-evans/find-comment@v2
uses: peter-evans/find-comment@v3
if: steps.generate-comment.outcome == 'success'
id: find-comment
with:
@@ -71,7 +71,7 @@ jobs:
- name: Create or update comment
if: steps.find-comment.outcome == 'success'
uses: peter-evans/create-or-update-comment@v3
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ steps.pr-number.outputs.pr-number }}

View File

@@ -58,7 +58,7 @@ jobs:
path: dist
macos-x86_64:
runs-on: macos-latest
runs-on: macos-14
steps:
- uses: actions/checkout@v4
with:
@@ -74,11 +74,6 @@ jobs:
with:
target: x86_64
args: --release --locked --out dist
- name: "Test wheel - x86_64"
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
ruff --help
python -m ruff --help
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
@@ -98,7 +93,7 @@ jobs:
*.sha256
macos-universal:
runs-on: macos-latest
runs-on: macos-14
steps:
- uses: actions/checkout@v4
with:

View File

@@ -1,5 +1,215 @@
# Changelog
## 0.2.0
### Breaking changes
- The `NURSERY` selector cannot be used anymore
- Legacy selection of nursery rules by exact codes is no longer allowed without preview enabled
See also, the "Remapped rules" section which may result in disabled rules.
### Deprecations
The following rules are now deprecated:
- [`missing-type-function-argument`](https://docs.astral.sh/ruff/rules/missing-type-function-argument/) (`ANN001`)
- [`missing-type-args`](https://docs.astral.sh/ruff/rules/missing-type-args/) (`ANN002`)
The following command line options are now deprecated:
- `--show-source`; use `--output-format full` instead
- `--no-show-source`; use `--output-format concise` instead
- `--output-format text`; use `full` or `concise` instead
The following settings have moved and the previous name is deprecated:
- `ruff.allowed-confusables` → [`ruff.lint.allowed-confusables`](https://docs.astral.sh//ruff/settings/#lint_allowed-confusables)
- `ruff.dummy-variable-rgx` → [`ruff.lint.dummy-variable-rgx`](https://docs.astral.sh//ruff/settings/#lint_dummy-variable-rgx)
- `ruff.explicit-preview-rules` → [`ruff.lint.explicit-preview-rules`](https://docs.astral.sh//ruff/settings/#lint_explicit-preview-rules)
- `ruff.extend-fixable` → [`ruff.lint.extend-fixable`](https://docs.astral.sh//ruff/settings/#lint_extend-fixable)
- `ruff.extend-ignore` → [`ruff.lint.extend-ignore`](https://docs.astral.sh//ruff/settings/#lint_extend-ignore)
- `ruff.extend-per-file-ignores` → [`ruff.lint.extend-per-file-ignores`](https://docs.astral.sh//ruff/settings/#lint_extend-per-file-ignores)
- `ruff.extend-safe-fixes` → [`ruff.lint.extend-safe-fixes`](https://docs.astral.sh//ruff/settings/#lint_extend-safe-fixes)
- `ruff.extend-select` → [`ruff.lint.extend-select`](https://docs.astral.sh//ruff/settings/#lint_extend-select)
- `ruff.extend-unfixable` → [`ruff.lint.extend-unfixable`](https://docs.astral.sh//ruff/settings/#lint_extend-unfixable)
- `ruff.extend-unsafe-fixes` → [`ruff.lint.extend-unsafe-fixes`](https://docs.astral.sh//ruff/settings/#lint_extend-unsafe-fixes)
- `ruff.external` → [`ruff.lint.external`](https://docs.astral.sh//ruff/settings/#lint_external)
- `ruff.fixable` → [`ruff.lint.fixable`](https://docs.astral.sh//ruff/settings/#lint_fixable)
- `ruff.flake8-annotations` → [`ruff.lint.flake8-annotations`](https://docs.astral.sh//ruff/settings/#lint_flake8-annotations)
- `ruff.flake8-bandit` → [`ruff.lint.flake8-bandit`](https://docs.astral.sh//ruff/settings/#lint_flake8-bandit)
- `ruff.flake8-bugbear` → [`ruff.lint.flake8-bugbear`](https://docs.astral.sh//ruff/settings/#lint_flake8-bugbear)
- `ruff.flake8-builtins` → [`ruff.lint.flake8-builtins`](https://docs.astral.sh//ruff/settings/#lint_flake8-builtins)
- `ruff.flake8-comprehensions` → [`ruff.lint.flake8-comprehensions`](https://docs.astral.sh//ruff/settings/#lint_flake8-comprehensions)
- `ruff.flake8-copyright` → [`ruff.lint.flake8-copyright`](https://docs.astral.sh//ruff/settings/#lint_flake8-copyright)
- `ruff.flake8-errmsg` → [`ruff.lint.flake8-errmsg`](https://docs.astral.sh//ruff/settings/#lint_flake8-errmsg)
- `ruff.flake8-gettext` → [`ruff.lint.flake8-gettext`](https://docs.astral.sh//ruff/settings/#lint_flake8-gettext)
- `ruff.flake8-implicit-str-concat` → [`ruff.lint.flake8-implicit-str-concat`](https://docs.astral.sh//ruff/settings/#lint_flake8-implicit-str-concat)
- `ruff.flake8-import-conventions` → [`ruff.lint.flake8-import-conventions`](https://docs.astral.sh//ruff/settings/#lint_flake8-import-conventions)
- `ruff.flake8-pytest-style` → [`ruff.lint.flake8-pytest-style`](https://docs.astral.sh//ruff/settings/#lint_flake8-pytest-style)
- `ruff.flake8-quotes` → [`ruff.lint.flake8-quotes`](https://docs.astral.sh//ruff/settings/#lint_flake8-quotes)
- `ruff.flake8-self` → [`ruff.lint.flake8-self`](https://docs.astral.sh//ruff/settings/#lint_flake8-self)
- `ruff.flake8-tidy-imports` → [`ruff.lint.flake8-tidy-imports`](https://docs.astral.sh//ruff/settings/#lint_flake8-tidy-imports)
- `ruff.flake8-type-checking` → [`ruff.lint.flake8-type-checking`](https://docs.astral.sh//ruff/settings/#lint_flake8-type-checking)
- `ruff.flake8-unused-arguments` → [`ruff.lint.flake8-unused-arguments`](https://docs.astral.sh//ruff/settings/#lint_flake8-unused-arguments)
- `ruff.ignore` → [`ruff.lint.ignore`](https://docs.astral.sh//ruff/settings/#lint_ignore)
- `ruff.ignore-init-module-imports` → [`ruff.lint.ignore-init-module-imports`](https://docs.astral.sh//ruff/settings/#lint_ignore-init-module-imports)
- `ruff.isort` → [`ruff.lint.isort`](https://docs.astral.sh//ruff/settings/#lint_isort)
- `ruff.logger-objects` → [`ruff.lint.logger-objects`](https://docs.astral.sh//ruff/settings/#lint_logger-objects)
- `ruff.mccabe` → [`ruff.lint.mccabe`](https://docs.astral.sh//ruff/settings/#lint_mccabe)
- `ruff.pep8-naming` → [`ruff.lint.pep8-naming`](https://docs.astral.sh//ruff/settings/#lint_pep8-naming)
- `ruff.per-file-ignores` → [`ruff.lint.per-file-ignores`](https://docs.astral.sh//ruff/settings/#lint_per-file-ignores)
- `ruff.pycodestyle` → [`ruff.lint.pycodestyle`](https://docs.astral.sh//ruff/settings/#lint_pycodestyle)
- `ruff.pydocstyle` → [`ruff.lint.pydocstyle`](https://docs.astral.sh//ruff/settings/#lint_pydocstyle)
- `ruff.pyflakes` → [`ruff.lint.pyflakes`](https://docs.astral.sh//ruff/settings/#lint_pyflakes)
- `ruff.pylint` → [`ruff.lint.pylint`](https://docs.astral.sh//ruff/settings/#lint_pylint)
- `ruff.pyupgrade` → [`ruff.lint.pyupgrade`](https://docs.astral.sh//ruff/settings/#lint_pyupgrade)
- `ruff.select` → [`ruff.lint.select`](https://docs.astral.sh//ruff/settings/#lint_select)
- `ruff.task-tags` → [`ruff.lint.task-tags`](https://docs.astral.sh//ruff/settings/#lint_task-tags)
- `ruff.typing-modules` → [`ruff.lint.typing-modules`](https://docs.astral.sh//ruff/settings/#lint_typing-modules)
- `ruff.unfixable` → [`ruff.lint.unfixable`](https://docs.astral.sh//ruff/settings/#lint_unfixable)
### Remapped rules
The following rules have been remapped to new codes:
- [`raise-without-from-inside-except`](https://docs.astral.sh/ruff/rules/raise-without-from-inside-except/): `TRY200` to `B904`
- [`suspicious-eval-usage`](https://docs.astral.sh/ruff/rules/suspicious-eval-usage/): `PGH001` to `S307`
- [`logging-warn`](https://docs.astral.sh/ruff/rules/logging-warn/): `PGH002` to `G010`
- [`static-key-dict-comprehension`](https://docs.astral.sh/ruff/rules/static-key-dict-comprehension): `RUF011` to `B035`
- [`runtime-string-union`](https://docs.astral.sh/ruff/rules/runtime-string-union): `TCH006` to `TCH010`
### Stabilizations
The following rules have been stabilized and are no longer in preview:
- [`trio-timeout-without-await`](https://docs.astral.sh/ruff/rules/trio-timeout-without-await) (`TRIO100`)
- [`trio-sync-call`](https://docs.astral.sh/ruff/rules/trio-sync-call) (`TRIO105`)
- [`trio-async-function-with-timeout`](https://docs.astral.sh/ruff/rules/trio-async-function-with-timeout) (`TRIO109`)
- [`trio-unneeded-sleep`](https://docs.astral.sh/ruff/rules/trio-unneeded-sleep) (`TRIO110`)
- [`trio-zero-sleep-call`](https://docs.astral.sh/ruff/rules/trio-zero-sleep-call) (`TRIO115`)
- [`unnecessary-escaped-quote`](https://docs.astral.sh/ruff/rules/unnecessary-escaped-quote) (`Q004`)
- [`enumerate-for-loop`](https://docs.astral.sh/ruff/rules/enumerate-for-loop) (`SIM113`)
- [`zip-dict-keys-and-values`](https://docs.astral.sh/ruff/rules/zip-dict-keys-and-values) (`SIM911`)
- [`timeout-error-alias`](https://docs.astral.sh/ruff/rules/timeout-error-alias) (`UP041`)
- [`flask-debug-true`](https://docs.astral.sh/ruff/rules/flask-debug-true) (`S201`)
- [`tarfile-unsafe-members`](https://docs.astral.sh/ruff/rules/tarfile-unsafe-members) (`S202`)
- [`ssl-insecure-version`](https://docs.astral.sh/ruff/rules/ssl-insecure-version) (`S502`)
- [`ssl-with-bad-defaults`](https://docs.astral.sh/ruff/rules/ssl-with-bad-defaults) (`S503`)
- [`ssl-with-no-version`](https://docs.astral.sh/ruff/rules/ssl-with-no-version) (`S504`)
- [`weak-cryptographic-key`](https://docs.astral.sh/ruff/rules/weak-cryptographic-key) (`S505`)
- [`ssh-no-host-key-verification`](https://docs.astral.sh/ruff/rules/ssh-no-host-key-verification) (`S507`)
- [`django-raw-sql`](https://docs.astral.sh/ruff/rules/django-raw-sql) (`S611`)
- [`mako-templates`](https://docs.astral.sh/ruff/rules/mako-templates) (`S702`)
- [`generator-return-from-iter-method`](https://docs.astral.sh/ruff/rules/generator-return-from-iter-method) (`PYI058`)
- [`runtime-string-union`](https://docs.astral.sh/ruff/rules/runtime-string-union) (`TCH006`)
- [`numpy2-deprecation`](https://docs.astral.sh/ruff/rules/numpy2-deprecation) (`NPY201`)
- [`quadratic-list-summation`](https://docs.astral.sh/ruff/rules/quadratic-list-summation) (`RUF017`)
- [`assignment-in-assert`](https://docs.astral.sh/ruff/rules/assignment-in-assert) (`RUF018`)
- [`unnecessary-key-check`](https://docs.astral.sh/ruff/rules/unnecessary-key-check) (`RUF019`)
- [`never-union`](https://docs.astral.sh/ruff/rules/never-union) (`RUF020`)
- [`direct-logger-instantiation`](https://docs.astral.sh/ruff/rules/direct-logger-instantiation) (`LOG001`)
- [`invalid-get-logger-argument`](https://docs.astral.sh/ruff/rules/invalid-get-logger-argument) (`LOG002`)
- [`exception-without-exc-info`](https://docs.astral.sh/ruff/rules/exception-without-exc-info) (`LOG007`)
- [`undocumented-warn`](https://docs.astral.sh/ruff/rules/undocumented-warn) (`LOG009`)
Fixes for the following rules have been stabilized and are now available without preview:
- [`triple-single-quotes`](https://docs.astral.sh/ruff/rules/triple-single-quotes) (`D300`)
- [`non-pep604-annotation`](https://docs.astral.sh/ruff/rules/non-pep604-annotation) (`UP007`)
- [`dict-get-with-none-default`](https://docs.astral.sh/ruff/rules/dict-get-with-none-default) (`SIM910`)
- [`in-dict-keys`](https://docs.astral.sh/ruff/rules/in-dict-keys) (`SIM118`)
- [`collapsible-else-if`](https://docs.astral.sh/ruff/rules/collapsible-else-if) (`PLR5501`)
- [`if-with-same-arms`](https://docs.astral.sh/ruff/rules/if-with-same-arms) (`SIM114`)
- [`useless-else-on-loop`](https://docs.astral.sh/ruff/rules/useless-else-on-loop) (`PLW0120`)
- [`unnecessary-literal-union`](https://docs.astral.sh/ruff/rules/unnecessary-literal-union) (`PYI030`)
- [`unnecessary-spread`](https://docs.astral.sh/ruff/rules/unnecessary-spread) (`PIE800`)
- [`error-instead-of-exception`](https://docs.astral.sh/ruff/rules/error-instead-of-exception) (`TRY400`)
- [`redefined-while-unused`](https://docs.astral.sh/ruff/rules/redefined-while-unused) (`F811`)
- [`duplicate-value`](https://docs.astral.sh/ruff/rules/duplicate-value) (`B033`)
- [`multiple-imports-on-one-line`](https://docs.astral.sh/ruff/rules/multiple-imports-on-one-line) (`E401`)
- [`non-pep585-annotation`](https://docs.astral.sh/ruff/rules/non-pep585-annotation) (`UP006`)
Fixes for the following rules have been promoted from unsafe to safe:
- [`unaliased-collections-abc-set-import`](https://docs.astral.sh/ruff/rules/unaliased-collections-abc-set-import) (`PYI025`)
The following behaviors have been stabilized:
- [`module-import-not-at-top-of-file`](https://docs.astral.sh/ruff/rules/module-import-not-at-top-of-file/) (`E402`) allows `sys.path` modifications between imports
- [`reimplemented-container-builtin`](https://docs.astral.sh/ruff/rules/reimplemented-container-builtin/) (`PIE807`) includes lambdas that can be replaced with `dict`
- [`unnecessary-placeholder`](https://docs.astral.sh/ruff/rules/unnecessary-placeholder/) (`PIE790`) applies to unnecessary ellipses (`...`)
- [`if-else-block-instead-of-dict-get`](https://docs.astral.sh/ruff/rules/if-else-block-instead-of-dict-get/) (`SIM401`) applies to `if-else` expressions
### Preview features
- \[`refurb`\] Implement `metaclass_abcmeta` (`FURB180`) ([#9658](https://github.com/astral-sh/ruff/pull/9658))
- Implement `blank_line_after_nested_stub_class` preview style ([#9155](https://github.com/astral-sh/ruff/pull/9155))
- The preview rule [`and-or-ternary`](https://docs.astral.sh/ruff/rules/and-or-ternary) (`PLR1706`) was removed
### Bug fixes
- \[`flake8-async`\] Take `pathlib.Path` into account when analyzing async functions ([#9703](https://github.com/astral-sh/ruff/pull/9703))
- \[`flake8-return`\] - fix indentation syntax error (`RET505`) ([#9705](https://github.com/astral-sh/ruff/pull/9705))
- Detect multi-statement lines in else removal ([#9748](https://github.com/astral-sh/ruff/pull/9748))
- `RUF022`, `RUF023`: never add two trailing commas to the end of a sequence ([#9698](https://github.com/astral-sh/ruff/pull/9698))
- `RUF023`: Don't sort `__match_args__`, only `__slots__` ([#9724](https://github.com/astral-sh/ruff/pull/9724))
- \[`flake8-simplify`\] - Fix syntax error in autofix (`SIM114`) ([#9704](https://github.com/astral-sh/ruff/pull/9704))
- \[`pylint`\] Show verbatim constant in `magic-value-comparison` (`PLR2004`) ([#9694](https://github.com/astral-sh/ruff/pull/9694))
- Removing trailing whitespace inside multiline strings is unsafe ([#9744](https://github.com/astral-sh/ruff/pull/9744))
- Support `IfExp` with dual string arms in `invalid-envvar-default` ([#9734](https://github.com/astral-sh/ruff/pull/9734))
- \[`pylint`\] Add `__mro_entries__` to known dunder methods (`PLW3201`) ([#9706](https://github.com/astral-sh/ruff/pull/9706))
### Documentation
- Removed rules are now retained in the documentation ([#9691](https://github.com/astral-sh/ruff/pull/9691))
- Deprecated rules are now indicated in the documentation ([#9689](https://github.com/astral-sh/ruff/pull/9689))
## 0.1.15
### Preview features
- Error when `NURSERY` selector is used with `--preview` ([#9682](https://github.com/astral-sh/ruff/pull/9682))
- Preserve indentation around multiline strings in formatter ([#9637](https://github.com/astral-sh/ruff/pull/9637))
- \[`flake8-return`\] Add fixes for all rules (`RET505`, `RET506`, `RET507`, `RET508`) ([#9595](https://github.com/astral-sh/ruff/pull/9595))
- \[`flake8-simplify`\] Add fix for `if-with-same-arms` (`SIM114`) ([#9591](https://github.com/astral-sh/ruff/pull/9591))
- \[`pycodestyle`\] Add fix for `multiple-imports-on-one-line` (`E401`) ([#9518](https://github.com/astral-sh/ruff/pull/9518))
- \[`pylint`\] Add fix for `collapsible-else-if` (`PLR5501`) ([#9594](https://github.com/astral-sh/ruff/pull/9594))
- \[`pylint`\] Add fix for `useless-else-on-loop` (`PLW0120`) ([#9590](https://github.com/astral-sh/ruff/pull/9590))
- \[`pylint`\] Implement `assigning-non-slot` (`E0237`) ([#9623](https://github.com/astral-sh/ruff/pull/9623))
- \[`pylint`\] Implement `potential-index-error` (`PLE0643`) ([#9545](https://github.com/astral-sh/ruff/pull/9545))
- \[`pylint`\] Implement `too-many-nested-blocks` (`PLR1702`) ([#9172](https://github.com/astral-sh/ruff/pull/9172))
- \[`ruff`\] Add rule to sort `__slots__` and `__match_args__` ([#9564](https://github.com/astral-sh/ruff/pull/9564))
- \[`ruff`\] Detect unnecessary `dict` comprehensions for iterables (`RUF025`) ([#9613](https://github.com/astral-sh/ruff/pull/9613))
- \[`ruff`\] Guard against use of `default_factory` as a keyword argument (`RUF026`) ([#9651](https://github.com/astral-sh/ruff/pull/9651))
- \[`ruff`\] Implement `mutable-fromkeys-value` (`RUF024`) ([#9597](https://github.com/astral-sh/ruff/pull/9597))
### CLI
- Enable auto-wrapping of `--help` output ([#9633](https://github.com/astral-sh/ruff/pull/9633))
### Bug fixes
- Avoid rendering display-only rules as fixable ([#9649](https://github.com/astral-sh/ruff/pull/9649))
- Detect automagic-like assignments in notebooks ([#9653](https://github.com/astral-sh/ruff/pull/9653))
- Generate custom JSON schema for dynamic setting ([#9632](https://github.com/astral-sh/ruff/pull/9632))
- \[`flake8-no-pep420`\] Include global `--config` when determining namespace packages ([#9603](https://github.com/astral-sh/ruff/pull/9603))
- \[`flake8-pie`\] Omit bound tuples passed to `.startswith` or `.endswith` ([#9661](https://github.com/astral-sh/ruff/pull/9661))
- \[`flake8-return`\] Avoid panic when fixing inlined else blocks ([#9657](https://github.com/astral-sh/ruff/pull/9657))
- \[`flake8-return`\] Consider exception suppression in unnecessary assignment ([#9673](https://github.com/astral-sh/ruff/pull/9673))
- \[`flake8-return`\] Take `NoReturn` annotation into account when analyzing implicit returns ([#9636](https://github.com/astral-sh/ruff/pull/9636))
- \[`flake8-simplify`\] Support inverted returns in `needless-bool` (`SIM103`) ([#9619](https://github.com/astral-sh/ruff/pull/9619))
- \[`flake8-type-checking`\] Add Pydantic's `BaseConfig` to default-copy list ([#9650](https://github.com/astral-sh/ruff/pull/9650))
- \[`flake8-type-checking`\] Avoid marking `InitVar` as a typing-only annotation ([#9688](https://github.com/astral-sh/ruff/pull/9688))
- \[`pycodestyle`\] Allow `dtype` comparisons in `type-comparison` ([#9676](https://github.com/astral-sh/ruff/pull/9676))
- \[`pydocstyle`\] Re-implement `last-line-after-section` (`D413`) ([#9654](https://github.com/astral-sh/ruff/pull/9654))
### Documentation
- \[`flake8-pytest-style`\] Add fix safety documentation for `duplicate-parameterize-test-cases` ([#9678](https://github.com/astral-sh/ruff/pull/9678))
- \[`pylint`\] Document `literal-membership` fix safety conditions ([#9677](https://github.com/astral-sh/ruff/pull/9677))
- \[`isort`\] Fix reference to `isort` rule code ([#9598](https://github.com/astral-sh/ruff/pull/9598))
## 0.1.14
### Preview features

95
Cargo.lock generated
View File

@@ -75,9 +75,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.4"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -273,14 +273,14 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.31"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
dependencies = [
"android-tzdata",
"iana-time-zone",
"num-traits",
"windows-targets 0.48.5",
"windows-targets 0.52.0",
]
[[package]]
@@ -312,9 +312,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.13"
version = "4.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52bdc885e4cacc7f7c9eedc1ef6da641603180c783c41a15c264944deeaab642"
checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
dependencies = [
"clap_builder",
"clap_derive",
@@ -322,14 +322,15 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.12"
version = "4.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9"
checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
"terminal_size",
]
[[package]]
@@ -956,9 +957,9 @@ dependencies = [
[[package]]
name = "ignore"
version = "0.4.21"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060"
checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
dependencies = [
"crossbeam-deque",
"globset",
@@ -1770,9 +1771,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.76"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
@@ -1855,9 +1856,9 @@ dependencies = [
[[package]]
name = "rayon"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
dependencies = [
"either",
"rayon-core",
@@ -1865,9 +1866,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.12.0"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
@@ -2004,7 +2005,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.1.14"
version = "0.2.0"
dependencies = [
"anyhow",
"argfile",
@@ -2164,7 +2165,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.1.14"
version = "0.2.0"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.2",
@@ -2416,7 +2417,7 @@ dependencies = [
[[package]]
name = "ruff_shrinking"
version = "0.1.14"
version = "0.2.0"
dependencies = [
"anyhow",
"clap",
@@ -2641,9 +2642,9 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]]
name = "serde"
version = "1.0.195"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [
"serde_derive",
]
@@ -2661,9 +2662,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.195"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
@@ -2683,9 +2684,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.109"
version = "1.0.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9"
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
dependencies = [
"itoa",
"ryu",
@@ -2712,9 +2713,9 @@ dependencies = [
[[package]]
name = "serde_with"
version = "3.4.0"
version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23"
checksum = "1b0ed1662c5a68664f45b76d18deb0e234aff37207086803165c961eb695e981"
dependencies = [
"serde",
"serde_with_macros",
@@ -2722,9 +2723,9 @@ dependencies = [
[[package]]
name = "serde_with_macros"
version = "3.4.0"
version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788"
checksum = "568577ff0ef47b879f736cd66740e022f3672788cdf002a05a4e609ea5a6fb15"
dependencies = [
"darling",
"proc-macro2",
@@ -2752,9 +2753,9 @@ dependencies = [
[[package]]
name = "shlex"
version = "1.2.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "similar"
@@ -2770,9 +2771,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "smallvec"
version = "1.11.2"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "spin"
@@ -2891,6 +2892,16 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "terminal_size"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
dependencies = [
"rustix",
"windows-sys 0.48.0",
]
[[package]]
name = "terminfo"
version = "0.8.0"
@@ -3417,9 +3428,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.39"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12"
checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461"
dependencies = [
"cfg-if",
"js-sys",
@@ -3458,9 +3469,9 @@ checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
[[package]]
name = "wasm-bindgen-test"
version = "0.3.39"
version = "0.3.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cf9242c0d27999b831eae4767b2a146feb0b27d332d553e605864acd2afd403"
checksum = "139bd73305d50e1c1c4333210c0db43d989395b64a237bd35c10ef3832a7f70c"
dependencies = [
"console_error_panic_hook",
"js-sys",
@@ -3472,9 +3483,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.39"
version = "0.3.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89"
checksum = "70072aebfe5da66d2716002c729a14e4aec4da0e23cc2ea66323dac541c93928"
dependencies = [
"proc-macro2",
"quote",
@@ -3510,9 +3521,9 @@ dependencies = [
[[package]]
name = "wild"
version = "2.2.0"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10d01931a94d5a115a53f95292f51d316856b68a035618eb831bbba593a30b67"
checksum = "a3131afc8c575281e1e80f36ed6a092aa502c08b18ed7524e86fbbb12bb410e1"
dependencies = [
"glob",
]

View File

@@ -20,8 +20,8 @@ assert_cmd = { version = "2.0.13" }
bincode = { version = "1.3.3" }
bitflags = { version = "2.4.1" }
cachedir = { version = "0.3.1" }
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
clap = { version = "4.4.13", features = ["derive"] }
chrono = { version = "0.4.33", default-features = false, features = ["clock"] }
clap = { version = "4.4.18", features = ["derive"] }
clap_complete_command = { version = "0.5.1" }
clearscreen = { version = "2.0.0" }
codspeed-criterion-compat = { version = "2.3.3", default-features = false }
@@ -40,7 +40,7 @@ fs-err = { version ="2.11.0"}
glob = { version = "0.3.1" }
globset = { version = "0.4.14" }
hexf-parse = { version ="0.2.1"}
ignore = { version = "0.4.21" }
ignore = { version = "0.4.22" }
imara-diff ={ version = "0.1.5"}
imperative = { version = "1.0.4" }
indicatif ={ version = "0.17.7"}
@@ -64,27 +64,27 @@ path-absolutize = { version = "3.1.1" }
pathdiff = { version = "0.2.1" }
pep440_rs = { version = "0.4.0", features = ["serde"] }
pretty_assertions = "1.3.0"
proc-macro2 = { version = "1.0.73" }
proc-macro2 = { version = "1.0.78" }
pyproject-toml = { version = "0.8.1" }
quick-junit = { version = "0.3.5" }
quote = { version = "1.0.23" }
rand = { version = "0.8.5" }
rayon = { version = "1.8.0" }
rayon = { version = "1.8.1" }
regex = { version = "1.10.2" }
result-like = { version = "0.5.0" }
rustc-hash = { version = "1.1.0" }
schemars = { version = "0.8.16" }
seahash = { version ="4.1.0"}
semver = { version = "1.0.21" }
serde = { version = "1.0.195", features = ["derive"] }
serde = { version = "1.0.196", features = ["derive"] }
serde-wasm-bindgen = { version = "0.6.3" }
serde_json = { version = "1.0.109" }
serde_json = { version = "1.0.113" }
serde_test = { version = "1.0.152" }
serde_with = { version = "3.4.0", default-features = false, features = ["macros"] }
serde_with = { version = "3.6.0", default-features = false, features = ["macros"] }
shellexpand = { version = "3.0.0" }
shlex = { version ="1.2.0"}
shlex = { version ="1.3.0"}
similar = { version = "2.4.0", features = ["inline"] }
smallvec = { version = "1.11.2" }
smallvec = { version = "1.13.1" }
static_assertions = "1.1.0"
strum = { version = "0.25.0", features = ["strum_macros"] }
strum_macros = { version = "0.25.3" }
@@ -107,7 +107,7 @@ url = { version = "2.5.0" }
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
walkdir = { version = "2.3.2" }
wasm-bindgen = { version = "0.2.84" }
wasm-bindgen-test = { version = "0.3.39" }
wasm-bindgen-test = { version = "0.3.40" }
wild = { version = "2" }
[workspace.lints.rust]

View File

@@ -150,7 +150,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.1.14
rev: v0.2.0
hooks:
# Run the linter.
- id: ruff
@@ -402,6 +402,7 @@ Ruff is used by a number of major open-source projects and companies, including:
[Diffusers](https://github.com/huggingface/diffusers))
- ING Bank ([popmon](https://github.com/ing-bank/popmon), [probatus](https://github.com/ing-bank/probatus))
- [Ibis](https://github.com/ibis-project/ibis)
- [ivy](https://github.com/unifyai/ivy)
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
- [LangChain](https://github.com/hwchase17/langchain)
- [Litestar](https://litestar.dev/)

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.1.14"
version = "0.2.0"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -30,7 +30,7 @@ bincode = { workspace = true }
bitflags = { workspace = true }
cachedir = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true, features = ["derive", "env"] }
clap = { workspace = true, features = ["derive", "env", "wrap_help"] }
clap_complete_command = { workspace = true }
clearscreen = { workspace = true }
colored = { workspace = true }
@@ -54,6 +54,8 @@ walkdir = { workspace = true }
wild = { workspace = true }
[dev-dependencies]
# Enable test rules during development
ruff_linter = { path = "../ruff_linter", features = ["clap", "test-rules"] }
assert_cmd = { workspace = true }
# Avoid writing colored snapshots when running tests from the terminal
colored = { workspace = true, features = ["no-color"]}

View File

@@ -11,7 +11,7 @@ use ruff_linter::settings::types::{
ExtensionPair, FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion,
SerializationFormat, UnsafeFixes,
};
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
use ruff_linter::{warn_user, RuleParser, RuleSelector, RuleSelectorParser};
use ruff_workspace::configuration::{Configuration, RuleSelection};
use ruff_workspace::options::PycodestyleOptions;
use ruff_workspace::resolver::ConfigurationTransformer;
@@ -104,6 +104,7 @@ pub struct CheckCommand {
no_unsafe_fixes: bool,
/// Show violations with source code.
/// Use `--no-show-source` to disable.
/// (Deprecated: use `--output-format=full` or `--output-format=concise` instead of `--show-source` and `--no-show-source`, respectively)
#[arg(long, overrides_with("no_show_source"))]
show_source: bool,
#[clap(long, overrides_with("show_source"), hide = true)]
@@ -131,6 +132,8 @@ pub struct CheckCommand {
ignore_noqa: bool,
/// Output serialization format for violations.
/// The default serialization format is "concise".
/// In preview mode, the default serialization format is "full".
#[arg(long, value_enum, env = "RUFF_OUTPUT_FORMAT")]
pub output_format: Option<SerializationFormat>,
@@ -533,7 +536,6 @@ impl CheckCommand {
self.no_respect_gitignore,
),
select: self.select,
show_source: resolve_bool_arg(self.show_source, self.no_show_source),
target_version: self.target_version,
unfixable: self.unfixable,
// TODO(charlie): Included in `pyproject.toml`, but not inherited.
@@ -543,7 +545,11 @@ impl CheckCommand {
unsafe_fixes: resolve_bool_arg(self.unsafe_fixes, self.no_unsafe_fixes)
.map(UnsafeFixes::from),
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
output_format: self.output_format,
output_format: resolve_output_format(
self.output_format,
resolve_bool_arg(self.show_source, self.no_show_source),
resolve_bool_arg(self.preview, self.no_preview).unwrap_or_default(),
),
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
extension: self.extension,
},
@@ -594,6 +600,43 @@ fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
}
}
fn resolve_output_format(
output_format: Option<SerializationFormat>,
show_sources: Option<bool>,
preview: bool,
) -> Option<SerializationFormat> {
Some(match (output_format, show_sources) {
(Some(o), None) => o,
(Some(SerializationFormat::Grouped), Some(true)) => {
warn_user!("`--show-source` with `--output-format=grouped` is deprecated, and will not show source files. Use `--output-format=full` to show source information.");
SerializationFormat::Grouped
}
(Some(fmt), Some(true)) => {
warn_user!("The `--show-source` argument is deprecated and has been ignored in favor of `--output-format={fmt}`.");
fmt
}
(Some(fmt), Some(false)) => {
warn_user!("The `--no-show-source` argument is deprecated and has been ignored in favor of `--output-format={fmt}`.");
fmt
}
(None, Some(true)) => {
warn_user!("The `--show-source` argument is deprecated. Use `--output-format=full` instead.");
SerializationFormat::Full
}
(None, Some(false)) => {
warn_user!("The `--no-show-source` argument is deprecated. Use `--output-format=concise` instead.");
SerializationFormat::Concise
}
(None, None) => return None
}).map(|format| match format {
SerializationFormat::Text => {
warn_user!("`--output-format=text` is deprecated. Use `--output-format=full` or `--output-format=concise` instead. `text` will be treated as `{}`.", SerializationFormat::default(preview));
SerializationFormat::default(preview)
},
other => other
})
}
/// CLI settings that are distinct from configuration (commands, lists of files,
/// etc.).
#[allow(clippy::struct_excessive_bools)]
@@ -648,7 +691,6 @@ pub struct CliOverrides {
pub preview: Option<PreviewMode>,
pub respect_gitignore: Option<bool>,
pub select: Option<Vec<RuleSelector>>,
pub show_source: Option<bool>,
pub target_version: Option<PythonVersion>,
pub unfixable: Option<Vec<RuleSelector>>,
// TODO(charlie): Captured in pyproject.toml as a default, but not part of `Settings`.
@@ -735,9 +777,6 @@ impl ConfigurationTransformer for CliOverrides {
if let Some(respect_gitignore) = &self.respect_gitignore {
config.respect_gitignore = Some(*respect_gitignore);
}
if let Some(show_source) = &self.show_source {
config.show_source = Some(*show_source);
}
if let Some(show_fixes) = &self.show_fixes {
config.show_fixes = Some(*show_fixes);
}

View File

@@ -255,7 +255,6 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
unsafe_fixes,
output_format,
show_fixes,
show_source,
..
} = pyproject_config.settings;
@@ -284,9 +283,6 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
if show_fixes {
printer_flags |= PrinterFlags::SHOW_FIX_SUMMARY;
}
if show_source {
printer_flags |= PrinterFlags::SHOW_SOURCE;
}
if cli.ecosystem_ci {
warn_user!(
"The formatting of fixes emitted by this option is a work-in-progress, subject to \
@@ -325,9 +321,18 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
printer_flags,
);
// the settings should already be combined with the CLI overrides at this point
// TODO(jane): let's make this `PreviewMode`
// TODO: this should reference the global preview mode once https://github.com/astral-sh/ruff/issues/8232
// is resolved.
let preview = pyproject_config.settings.linter.preview.is_enabled();
if cli.watch {
if output_format != SerializationFormat::Text {
warn_user!("`--output-format text` is always used in watch mode.");
if output_format != SerializationFormat::default(preview) {
warn_user!(
"`--output-format {}` is always used in watch mode.",
SerializationFormat::default(preview)
);
}
// Configure the file watcher.
@@ -353,7 +358,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
fix_mode,
unsafe_fixes,
)?;
printer.write_continuously(&mut writer, &messages)?;
printer.write_continuously(&mut writer, &messages, preview)?;
// In watch mode, we may need to re-resolve the configuration.
// TODO(charlie): Re-compute other derivative values, like the `printer`.
@@ -386,7 +391,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
fix_mode,
unsafe_fixes,
)?;
printer.write_continuously(&mut writer, &messages)?;
printer.write_continuously(&mut writer, &messages, preview)?;
}
Err(err) => return Err(err.into()),
}

View File

@@ -27,8 +27,6 @@ bitflags! {
pub(crate) struct Flags: u8 {
/// Whether to show violations when emitting diagnostics.
const SHOW_VIOLATIONS = 0b0000_0001;
/// Whether to show the source code when emitting diagnostics.
const SHOW_SOURCE = 0b000_0010;
/// Whether to show a summary of the fixed violations when emitting diagnostics.
const SHOW_FIX_SUMMARY = 0b0000_0100;
/// Whether to show a diff of each fixed violation when emitting diagnostics.
@@ -218,7 +216,10 @@ impl Printer {
if !self.flags.intersects(Flags::SHOW_VIOLATIONS) {
if matches!(
self.format,
SerializationFormat::Text | SerializationFormat::Grouped
SerializationFormat::Text
| SerializationFormat::Full
| SerializationFormat::Concise
| SerializationFormat::Grouped
) {
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
if !diagnostics.fixed.is_empty() {
@@ -245,11 +246,12 @@ impl Printer {
SerializationFormat::Junit => {
JunitEmitter.emit(writer, &diagnostics.messages, &context)?;
}
SerializationFormat::Text => {
SerializationFormat::Concise
| SerializationFormat::Full => {
TextEmitter::default()
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
.with_show_fix_diff(self.flags.intersects(Flags::SHOW_FIX_DIFF))
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
.with_show_source(self.format == SerializationFormat::Full)
.with_unsafe_fixes(self.unsafe_fixes)
.emit(writer, &diagnostics.messages, &context)?;
@@ -265,7 +267,6 @@ impl Printer {
}
SerializationFormat::Grouped => {
GroupedEmitter::default()
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
.with_unsafe_fixes(self.unsafe_fixes)
.emit(writer, &diagnostics.messages, &context)?;
@@ -294,6 +295,7 @@ impl Printer {
SerializationFormat::Sarif => {
SarifEmitter.emit(writer, &diagnostics.messages, &context)?;
}
SerializationFormat::Text => unreachable!("Text is deprecated and should have been automatically converted to the default serialization format")
}
writer.flush()?;
@@ -342,7 +344,9 @@ impl Printer {
}
match self.format {
SerializationFormat::Text => {
SerializationFormat::Text
| SerializationFormat::Full
| SerializationFormat::Concise => {
// Compute the maximum number of digits in the count and code, for all messages,
// to enable pretty-printing.
let count_width = num_digits(
@@ -403,6 +407,7 @@ impl Printer {
&self,
writer: &mut dyn Write,
diagnostics: &Diagnostics,
preview: bool,
) -> Result<()> {
if matches!(self.log_level, LogLevel::Silent) {
return Ok(());
@@ -430,7 +435,7 @@ impl Printer {
let context = EmitterContext::new(&diagnostics.notebook_indexes);
TextEmitter::default()
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
.with_show_source(preview)
.with_unsafe_fixes(self.unsafe_fixes)
.emit(writer, &diagnostics.messages, &context)?;
}

View File

@@ -0,0 +1,149 @@
//! A test suite that ensures deprecated command line options have appropriate warnings / behaviors
use ruff_linter::settings::types::SerializationFormat;
use std::process::Command;
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
const BIN_NAME: &str = "ruff";
const STDIN: &str = "l = 1";
fn ruff_check(show_source: Option<bool>, output_format: Option<String>) -> Command {
let mut cmd = Command::new(get_cargo_bin(BIN_NAME));
let output_format = output_format.unwrap_or(format!("{}", SerializationFormat::default(false)));
cmd.arg("--output-format");
cmd.arg(output_format);
cmd.arg("--no-cache");
match show_source {
Some(true) => {
cmd.arg("--show-source");
}
Some(false) => {
cmd.arg("--no-show-source");
}
None => {}
}
cmd.arg("-");
cmd
}
#[test]
fn ensure_show_source_is_deprecated() {
assert_cmd_snapshot!(ruff_check(Some(true), None).pass_stdin(STDIN), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `l`
Found 1 error.
----- stderr -----
warning: The `--show-source` argument is deprecated and has been ignored in favor of `--output-format=concise`.
"###);
}
#[test]
fn ensure_no_show_source_is_deprecated() {
assert_cmd_snapshot!(ruff_check(Some(false), None).pass_stdin(STDIN), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `l`
Found 1 error.
----- stderr -----
warning: The `--no-show-source` argument is deprecated and has been ignored in favor of `--output-format=concise`.
"###);
}
#[test]
fn ensure_output_format_is_deprecated() {
assert_cmd_snapshot!(ruff_check(None, Some("text".into())).pass_stdin(STDIN), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `l`
Found 1 error.
----- stderr -----
warning: `--output-format=text` is deprecated. Use `--output-format=full` or `--output-format=concise` instead. `text` will be treated as `concise`.
"###);
}
#[test]
fn ensure_output_format_overrides_show_source() {
assert_cmd_snapshot!(ruff_check(Some(true), Some("concise".into())).pass_stdin(STDIN), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `l`
Found 1 error.
----- stderr -----
warning: The `--show-source` argument is deprecated and has been ignored in favor of `--output-format=concise`.
"###);
}
#[test]
fn ensure_full_output_format_overrides_no_show_source() {
assert_cmd_snapshot!(ruff_check(Some(false), Some("full".into())).pass_stdin(STDIN), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `l`
|
1 | l = 1
| ^ E741
|
Found 1 error.
----- stderr -----
warning: The `--no-show-source` argument is deprecated and has been ignored in favor of `--output-format=full`.
"###);
}
#[test]
fn ensure_output_format_uses_concise_over_no_show_source() {
assert_cmd_snapshot!(ruff_check(Some(false), Some("concise".into())).pass_stdin(STDIN), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `l`
Found 1 error.
----- stderr -----
warning: The `--no-show-source` argument is deprecated and has been ignored in favor of `--output-format=concise`.
"###);
}
#[test]
fn ensure_deprecated_output_format_overrides_show_source() {
assert_cmd_snapshot!(ruff_check(Some(true), Some("text".into())).pass_stdin(STDIN), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `l`
Found 1 error.
----- stderr -----
warning: The `--show-source` argument is deprecated and has been ignored in favor of `--output-format=text`.
warning: `--output-format=text` is deprecated. Use `--output-format=full` or `--output-format=concise` instead. `text` will be treated as `concise`.
"###);
}
#[test]
fn ensure_deprecated_output_format_overrides_no_show_source() {
assert_cmd_snapshot!(ruff_check(Some(false), Some("text".into())).pass_stdin(STDIN), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `l`
Found 1 error.
----- stderr -----
warning: The `--no-show-source` argument is deprecated and has been ignored in favor of `--output-format=text`.
warning: `--output-format=text` is deprecated. Use `--output-format=full` or `--output-format=concise` instead. `text` will be treated as `concise`.
"###);
}

View File

@@ -508,6 +508,9 @@ if __name__ == '__main__':
say_hy("dear Ruff contributor")
----- stderr -----
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
- 'extend-select' -> 'lint.extend-select'
- 'ignore' -> 'lint.ignore'
"###);
Ok(())
}
@@ -546,6 +549,9 @@ if __name__ == '__main__':
say_hy("dear Ruff contributor")
----- stderr -----
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
- 'extend-select' -> 'lint.extend-select'
- 'ignore' -> 'lint.ignore'
"###);
Ok(())
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@
#![cfg(not(target_family = "wasm"))]
use regex::escape;
use std::fs;
use std::process::Command;
use std::str;
@@ -11,7 +12,11 @@ use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
use tempfile::TempDir;
const BIN_NAME: &str = "ruff";
const STDIN_BASE_OPTIONS: &[&str] = &["--no-cache", "--output-format", "text"];
const STDIN_BASE_OPTIONS: &[&str] = &["--no-cache", "--output-format", "concise"];
fn tempdir_filter(tempdir: &TempDir) -> String {
format!(r"{}\\?/?", escape(tempdir.path().to_str().unwrap()))
}
#[test]
fn top_level_options() -> Result<()> {
@@ -27,24 +32,32 @@ inline-quotes = "single"
"#,
)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
.args(["--stdin-filename", "test.py"])
.arg("-")
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
success: false
exit_code: 1
----- stdout -----
test.py:1:5: Q000 [*] Double quotes found but single quotes preferred
test.py:1:5: B005 Using `.strip()` with multi-character strings is misleading
test.py:1:19: Q000 [*] Double quotes found but single quotes preferred
Found 3 errors.
[*] 2 fixable with the `--fix` option.
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
.args(["--stdin-filename", "test.py"])
.arg("-")
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
success: false
exit_code: 1
----- stdout -----
test.py:1:5: Q000 [*] Double quotes found but single quotes preferred
test.py:1:5: B005 Using `.strip()` with multi-character strings is misleading
test.py:1:19: Q000 [*] Double quotes found but single quotes preferred
Found 3 errors.
[*] 2 fixable with the `--fix` option.
----- stderr -----
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `[TMP]/ruff.toml`:
- 'extend-select' -> 'lint.extend-select'
- 'flake8-quotes' -> 'lint.flake8-quotes'
"###);
});
----- stderr -----
"###);
Ok(())
}
@@ -63,6 +76,9 @@ inline-quotes = "single"
"#,
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
@@ -80,6 +96,8 @@ inline-quotes = "single"
----- stderr -----
"###);
});
Ok(())
}
@@ -98,6 +116,9 @@ inline-quotes = "single"
"#,
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
@@ -114,7 +135,11 @@ inline-quotes = "single"
[*] 2 fixable with the `--fix` option.
----- stderr -----
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `[TMP]/ruff.toml`:
- 'extend-select' -> 'lint.extend-select'
"###);
});
Ok(())
}
@@ -137,6 +162,9 @@ inline-quotes = "single"
"#,
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
@@ -153,7 +181,11 @@ inline-quotes = "single"
[*] 2 fixable with the `--fix` option.
----- stderr -----
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `[TMP]/ruff.toml`:
- 'flake8-quotes' -> 'lint.flake8-quotes'
"###);
});
Ok(())
}
@@ -209,6 +241,9 @@ OTHER = "OTHER"
fs::write(out_dir.join("a.py"), r#"a = "a""#)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.arg("check")
@@ -228,7 +263,11 @@ OTHER = "OTHER"
[*] 3 fixable with the `--fix` option.
----- stderr -----
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
- 'extend-select' -> 'lint.extend-select'
"###);
});
Ok(())
}
@@ -249,6 +288,9 @@ inline-quotes = "single"
"#,
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.arg("check")
@@ -271,7 +313,11 @@ if __name__ == "__main__":
[*] 2 fixable with the `--fix` option.
----- stderr -----
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
- 'extend-select' -> 'lint.extend-select'
"###);
});
Ok(())
}
@@ -290,6 +336,9 @@ max-line-length = 100
"#,
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
@@ -309,7 +358,12 @@ _ = "---------------------------------------------------------------------------
Found 1 error.
----- stderr -----
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `[TMP]/ruff.toml`:
- 'select' -> 'lint.select'
- 'pycodestyle' -> 'lint.pycodestyle'
"###);
});
Ok(())
}
@@ -327,6 +381,9 @@ inline-quotes = "single"
"#,
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.arg("check")
@@ -351,7 +408,11 @@ if __name__ == "__main__":
[*] 1 fixable with the `--fix` option.
----- stderr -----
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
- 'extend-select' -> 'lint.extend-select'
"###);
});
Ok(())
}
@@ -369,6 +430,9 @@ inline-quotes = "single"
"#,
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.arg("check")
@@ -393,7 +457,11 @@ if __name__ == "__main__":
[*] 1 fixable with the `--fix` option.
----- stderr -----
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
- 'extend-select' -> 'lint.extend-select'
"###);
});
Ok(())
}
@@ -422,6 +490,9 @@ ignore = ["D203", "D212"]
"#,
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(sub_dir)
.arg("check")
@@ -434,6 +505,8 @@ ignore = ["D203", "D212"]
----- stderr -----
warning: No Python files found under the given path(s)
"###);
});
Ok(())
}
@@ -490,6 +563,9 @@ include = ["*.ipy"]
"#,
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.arg("check")
@@ -506,5 +582,7 @@ include = ["*.ipy"]
----- stderr -----
"###);
});
Ok(())
}

View File

@@ -39,6 +39,8 @@ fn check_project_include_defaults() {
[BASEPATH]/include-test/subdirectory/c.py
----- stderr -----
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `nested-project/pyproject.toml`:
- 'select' -> 'lint.select'
"###);
});
}

View File

@@ -51,7 +51,7 @@ else:
```
## Options
- `pyflakes.extend-generics`
- `lint.pyflakes.extend-generics`
## References
- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)

View File

@@ -17,9 +17,8 @@ Settings path: "[BASEPATH]/pyproject.toml"
cache_dir = "[BASEPATH]/.ruff_cache"
fix = false
fix_only = false
output_format = text
output_format = concise
show_fixes = false
show_source = false
unsafe_fixes = hint
# File Resolver Settings

View File

@@ -25,7 +25,7 @@ ruff_python_trivia = { path = "../ruff_python_trivia" }
ruff_workspace = { path = "../ruff_workspace", features = ["schemars"]}
anyhow = { workspace = true }
clap = { workspace = true }
clap = { workspace = true, features = ["wrap_help"] }
ignore = { workspace = true }
imara-diff = { workspace = true }
indicatif = { workspace = true }

View File

@@ -114,12 +114,15 @@ pub(super) fn main(args: &Args) -> Result<()> {
/// Returns the output of `ruff help`.
fn help_text() -> String {
args::Args::command().render_help().to_string()
args::Args::command()
.term_width(79)
.render_help()
.to_string()
}
/// Returns the output of a given subcommand (e.g., `ruff help check`).
fn subcommand_help_text(subcommand: &str) -> Result<String> {
let mut cmd = args::Args::command();
let mut cmd = args::Args::command().term_width(79);
// The build call is necessary for the help output to contain `Usage: ruff
// check` instead of `Usage: check` see https://github.com/clap-rs/clap/issues/4685

View File

@@ -26,9 +26,9 @@ pub(crate) fn main(args: &Args) -> Result<()> {
for rule in Rule::iter() {
if let Some(explanation) = rule.explanation() {
let mut output = String::new();
output.push_str(&format!("# {} ({})", rule.as_ref(), rule.noqa_code()));
output.push('\n');
output.push('\n');
let (linter, _) = Linter::parse_code(&rule.noqa_code().to_string()).unwrap();
if linter.url().is_some() {
@@ -37,6 +37,22 @@ pub(crate) fn main(args: &Args) -> Result<()> {
output.push('\n');
}
if rule.is_deprecated() {
output.push_str(
r"**Warning: This rule is deprecated and will be removed in a future release.**",
);
output.push('\n');
output.push('\n');
}
if rule.is_removed() {
output.push_str(
r"**Warning: This rule has been removed and its documentation is only available for historical reasons.**",
);
output.push('\n');
output.push('\n');
}
let fix_availability = rule.fixable();
if matches!(
fix_availability,
@@ -116,7 +132,7 @@ fn process_documentation(documentation: &str, out: &mut String, rule_name: &str)
}
}
let anchor = option.replace('.', "-");
let anchor = option.replace('.', "_");
out.push_str(&format!("- [`{option}`][{option}]\n"));
after.push_str(&format!("[{option}]: ../settings.md#{anchor}\n"));
@@ -142,13 +158,13 @@ mod tests {
let mut output = String::new();
process_documentation(
"
See also [`mccabe.max-complexity`] and [`task-tags`].
See also [`lint.mccabe.max-complexity`] and [`lint.task-tags`].
Something [`else`][other].
## Options
- `task-tags`
- `mccabe.max-complexity`
- `lint.task-tags`
- `lint.mccabe.max-complexity`
[other]: http://example.com.",
&mut output,
@@ -157,18 +173,18 @@ Something [`else`][other].
assert_eq!(
output,
"
See also [`mccabe.max-complexity`][mccabe.max-complexity] and [`task-tags`][task-tags].
See also [`lint.mccabe.max-complexity`][lint.mccabe.max-complexity] and [`lint.task-tags`][lint.task-tags].
Something [`else`][other].
## Options
- [`task-tags`][task-tags]
- [`mccabe.max-complexity`][mccabe.max-complexity]
- [`lint.task-tags`][lint.task-tags]
- [`lint.mccabe.max-complexity`][lint.mccabe.max-complexity]
[other]: http://example.com.
[task-tags]: ../settings.md#task-tags
[mccabe.max-complexity]: ../settings.md#mccabe-max-complexity
[lint.task-tags]: ../settings.md#lint_task-tags
[lint.mccabe.max-complexity]: ../settings.md#lint_mccabe_max-complexity
"
);
}

View File

@@ -1,6 +1,7 @@
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
//!
//! Used for <https://docs.astral.sh/ruff/settings/>.
use itertools::Itertools;
use std::fmt::Write;
use ruff_python_trivia::textwrap;
@@ -9,16 +10,29 @@ use ruff_workspace::options_base::{OptionField, OptionSet, OptionsMetadata, Visi
pub(crate) fn generate() -> String {
let mut output = String::new();
generate_set(&mut output, &Set::Toplevel(Options::metadata()));
generate_set(
&mut output,
Set::Toplevel(Options::metadata()),
&mut Vec::new(),
);
output
}
fn generate_set(output: &mut String, set: &Set) {
if set.level() < 2 {
writeln!(output, "### {title}\n", title = set.title()).unwrap();
} else {
writeln!(output, "#### {title}\n", title = set.title()).unwrap();
fn generate_set(output: &mut String, set: Set, parents: &mut Vec<Set>) {
match &set {
Set::Toplevel(_) => {
output.push_str("### Top-level\n");
}
Set::Named { name, .. } => {
let title = parents
.iter()
.filter_map(|set| set.name())
.chain(std::iter::once(name.as_str()))
.join(".");
writeln!(output, "#### `{title}`\n",).unwrap();
}
}
if let Some(documentation) = set.metadata().documentation() {
@@ -35,72 +49,68 @@ fn generate_set(output: &mut String, set: &Set) {
fields.sort_unstable_by(|(name, _), (name2, _)| name.cmp(name2));
sets.sort_unstable_by(|(name, _), (name2, _)| name.cmp(name2));
parents.push(set);
// Generate the fields.
for (name, field) in &fields {
emit_field(output, name, field, set);
emit_field(output, name, field, parents.as_slice());
output.push_str("---\n\n");
}
// Generate all the sub-sets.
for (set_name, sub_set) in &sets {
generate_set(output, &Set::Named(set_name, *sub_set, set.level() + 1));
generate_set(
output,
Set::Named {
name: set_name.to_string(),
set: *sub_set,
},
parents,
);
}
parents.pop();
}
enum Set<'a> {
enum Set {
Toplevel(OptionSet),
Named(&'a str, OptionSet, u32),
Named { name: String, set: OptionSet },
}
impl<'a> Set<'a> {
fn name(&self) -> Option<&'a str> {
impl Set {
fn name(&self) -> Option<&str> {
match self {
Set::Toplevel(_) => None,
Set::Named(name, _, _) => Some(name),
}
}
fn title(&self) -> &'a str {
match self {
Set::Toplevel(_) => "Top-level",
Set::Named(name, _, _) => name,
Set::Named { name, .. } => Some(name),
}
}
fn metadata(&self) -> &OptionSet {
match self {
Set::Toplevel(set) => set,
Set::Named(_, set, _) => set,
}
}
fn level(&self) -> u32 {
match self {
Set::Toplevel(_) => 0,
Set::Named(_, _, level) => *level,
Set::Named { set, .. } => set,
}
}
}
fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set: &Set) {
let header_level = if parent_set.level() < 2 {
"####"
} else {
"#####"
};
fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[Set]) {
let header_level = if parents.is_empty() { "####" } else { "#####" };
let parents_anchor = parents.iter().filter_map(|parent| parent.name()).join("_");
if parents_anchor.is_empty() {
output.push_str(&format!(
"{header_level} [`{name}`](#{name}) {{: #{name} }}\n"
));
} else {
output.push_str(&format!(
"{header_level} [`{name}`](#{parents_anchor}_{name}) {{: #{parents_anchor}_{name} }}\n"
));
// if there's a set name, we need to add it to the anchor
if let Some(set_name) = parent_set.name() {
// the anchor used to just be the name, but now it's the group name
// for backwards compatibility, we need to keep the old anchor
output.push_str(&format!("<span id=\"{name}\"></span>\n"));
output.push_str(&format!(
"{header_level} [`{name}`](#{set_name}-{name}) {{: #{set_name}-{name} }}\n"
));
} else {
output.push_str(&format!("{header_level} [`{name}`](#{name})\n"));
}
output.push('\n');
if let Some(deprecated) = &field.deprecated {
@@ -129,12 +139,12 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
output.push_str("**Example usage**:\n\n");
output.push_str(&format_tab(
"pyproject.toml",
&format_header(field.scope, parent_set, ConfigurationFile::PyprojectToml),
&format_header(field.scope, parents, ConfigurationFile::PyprojectToml),
field.example,
));
output.push_str(&format_tab(
"ruff.toml",
&format_header(field.scope, parent_set, ConfigurationFile::RuffToml),
&format_header(field.scope, parents, ConfigurationFile::RuffToml),
field.example,
));
output.push('\n');
@@ -152,52 +162,22 @@ fn format_tab(tab_name: &str, header: &str, content: &str) -> String {
/// Format the TOML header for the example usage for a given option.
///
/// For example: `[tool.ruff.format]` or `[tool.ruff.lint.isort]`.
fn format_header(
scope: Option<&str>,
parent_set: &Set,
configuration: ConfigurationFile,
) -> String {
match configuration {
ConfigurationFile::PyprojectToml => {
let mut header = if let Some(set_name) = parent_set.name() {
if set_name == "format" {
String::from("tool.ruff.format")
} else {
format!("tool.ruff.lint.{set_name}")
}
} else {
"tool.ruff".to_string()
};
if let Some(scope) = scope {
if !header.is_empty() {
header.push('.');
}
header.push_str(scope);
}
format!("[{header}]")
}
ConfigurationFile::RuffToml => {
let mut header = if let Some(set_name) = parent_set.name() {
if set_name == "format" {
String::from("format")
} else {
format!("lint.{set_name}")
}
} else {
String::new()
};
if let Some(scope) = scope {
if !header.is_empty() {
header.push('.');
}
header.push_str(scope);
}
if header.is_empty() {
String::new()
} else {
format!("[{header}]")
}
}
fn format_header(scope: Option<&str>, parents: &[Set], configuration: ConfigurationFile) -> String {
let tool_parent = match configuration {
ConfigurationFile::PyprojectToml => Some("tool.ruff"),
ConfigurationFile::RuffToml => None,
};
let header = tool_parent
.into_iter()
.chain(parents.iter().filter_map(|parent| parent.name()))
.chain(scope)
.join(".");
if header.is_empty() {
String::new()
} else {
format!("[{header}]")
}
}

View File

@@ -3,6 +3,7 @@
//! Used for <https://docs.astral.sh/ruff/rules/>.
use itertools::Itertools;
use ruff_linter::codes::RuleGroup;
use std::borrow::Cow;
use strum::IntoEnumIterator;
@@ -14,6 +15,10 @@ use ruff_workspace::options_base::OptionsMetadata;
const FIX_SYMBOL: &str = "🛠️";
const PREVIEW_SYMBOL: &str = "🧪";
const REMOVED_SYMBOL: &str = "";
const WARNING_SYMBOL: &str = "⚠️";
const STABLE_SYMBOL: &str = "✔️";
const SPACER: &str = "&nbsp;&nbsp;&nbsp;&nbsp;";
fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>, linter: &Linter) {
table_out.push_str("| Code | Name | Message | |");
@@ -21,20 +26,33 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
table_out.push_str("| ---- | ---- | ------- | ------: |");
table_out.push('\n');
for rule in rules {
let status_token = match rule.group() {
RuleGroup::Removed => {
format!("<span title='Rule has been removed'>{REMOVED_SYMBOL}</span>")
}
RuleGroup::Deprecated => {
format!("<span title='Rule has been deprecated'>{WARNING_SYMBOL}</span>")
}
#[allow(deprecated)]
RuleGroup::Preview | RuleGroup::Nursery => {
format!("<span title='Rule is in preview'>{PREVIEW_SYMBOL}</span>")
}
RuleGroup::Stable => {
// A full opacity checkmark is a bit aggressive for indicating stable
format!("<span title='Rule is stable' style='opacity: 0.6'>{STABLE_SYMBOL}</span>")
}
};
let fix_token = match rule.fixable() {
FixAvailability::Always | FixAvailability::Sometimes => {
format!("<span title='Automatic fix available'>{FIX_SYMBOL}</span>")
}
FixAvailability::None => {
format!("<span style='opacity: 0.1' aria-hidden='true'>{FIX_SYMBOL}</span>")
format!("<span title='Automatic fix not available' style='opacity: 0.1' aria-hidden='true'>{FIX_SYMBOL}</span>")
}
};
let preview_token = if rule.is_preview() || rule.is_nursery() {
format!("<span title='Rule is in preview'>{PREVIEW_SYMBOL}</span>")
} else {
format!("<span style='opacity: 0.1' aria-hidden='true'>{PREVIEW_SYMBOL}</span>")
};
let status_token = format!("{fix_token} {preview_token}");
let tokens = format!("{status_token} {fix_token}");
let rule_name = rule.as_ref();
@@ -48,9 +66,20 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
Cow::Borrowed(message)
};
// Start and end of style spans
let mut ss = "";
let mut se = "";
if rule.is_removed() {
ss = "<span style='opacity: 0.5', title='This rule has been removed'>";
se = "</span>";
} else if rule.is_deprecated() {
ss = "<span style='opacity: 0.8', title='This rule has been deprecated'>";
se = "</span>";
}
#[allow(clippy::or_fun_call)]
table_out.push_str(&format!(
"| {0}{1} {{ #{0}{1} }} | {2} | {3} | {4} |",
"| {ss}{0}{1}{se} {{ #{0}{1} }} | {ss}{2}{se} | {ss}{3}{se} | {ss}{4}{se} |",
linter.common_prefix(),
linter.code_for_rule(rule).unwrap(),
rule.explanation()
@@ -58,7 +87,7 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
.then_some(format_args!("[{rule_name}](rules/{rule_name}.md)"))
.unwrap_or(format_args!("{rule_name}")),
message,
status_token,
tokens,
));
table_out.push('\n');
}
@@ -69,15 +98,33 @@ pub(crate) fn generate() -> String {
// Generate the table string.
let mut table_out = String::new();
table_out.push_str(&format!(
"The {FIX_SYMBOL} emoji indicates that a rule is automatically fixable by the `--fix` command-line option."));
table_out.push('\n');
table_out.push_str("### Legend");
table_out.push('\n');
table_out.push_str(&format!(
"The {PREVIEW_SYMBOL} emoji indicates that a rule is in [\"preview\"](faq.md#what-is-preview)."
"{SPACER}{STABLE_SYMBOL}{SPACER} The rule is stable."
));
table_out.push('\n');
table_out.push_str("<br />");
table_out.push_str(&format!(
"{SPACER}{PREVIEW_SYMBOL}{SPACER} The rule is unstable and is in [\"preview\"](faq.md#what-is-preview)."
));
table_out.push_str("<br />");
table_out.push_str(&format!(
"{SPACER}{WARNING_SYMBOL}{SPACER} The rule has been deprecated and will be removed in a future release."
));
table_out.push_str("<br />");
table_out.push_str(&format!(
"{SPACER}{REMOVED_SYMBOL}{SPACER} The rule has been removed only the documentation is available."
));
table_out.push_str("<br />");
table_out.push_str(&format!(
"{SPACER}{FIX_SYMBOL}{SPACER} The rule is automatically fixable by the `--fix` command-line option."
));
table_out.push_str("<br />");
table_out.push('\n');
for linter in Linter::iter() {

View File

@@ -328,6 +328,12 @@ pub struct SourcePosition(TextSize);
impl<Context> Format<Context> for SourcePosition {
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
if let Some(FormatElement::SourcePosition(last_position)) = f.buffer.elements().last() {
if *last_position == self.0 {
return Ok(());
}
}
f.write_element(FormatElement::SourcePosition(self.0));
Ok(())
@@ -353,8 +359,8 @@ where
Context: FormatContext,
{
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
if let Some(source_position) = self.position {
f.write_element(FormatElement::SourcePosition(source_position));
if let Some(position) = self.position {
source_position(position).fmt(f)?;
}
f.write_element(FormatElement::Text {

View File

@@ -53,7 +53,7 @@ pub use crate::diagnostics::{ActualStart, FormatError, InvalidDocumentError, Pri
pub use format_element::{normalize_newlines, FormatElement, LINE_TERMINATORS};
pub use group_id::GroupId;
use ruff_macros::CacheKey;
use ruff_text_size::{TextRange, TextSize};
use ruff_text_size::{TextLen, TextRange, TextSize};
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, CacheKey)]
#[cfg_attr(
@@ -432,6 +432,90 @@ impl Printed {
pub fn take_verbatim_ranges(&mut self) -> Vec<TextRange> {
std::mem::take(&mut self.verbatim_ranges)
}
/// Slices the formatted code to the sub-slices that covers the passed `source_range`.
///
/// The implementation uses the source map generated during formatting to find the closest range
/// in the formatted document that covers `source_range` or more. The returned slice
/// matches the `source_range` exactly (except indent, see below) if the formatter emits [`FormatElement::SourcePosition`] for
/// the range's offsets.
///
/// Returns the entire document if the source map is empty.
#[must_use]
pub fn slice_range(self, source_range: TextRange) -> PrintedRange {
let mut start_marker: Option<SourceMarker> = None;
let mut end_marker: Option<SourceMarker> = None;
// Note: The printer can generate multiple source map entries for the same source position.
// For example if you have:
// * `source_position(276)`
// * `token("def")`
// * `token("foo")`
// * `source_position(284)`
// The printer uses the source position 276 for both the tokens `def` and `foo` because that's the only position it knows of.
//
// Warning: Source markers are often emitted sorted by their source position but it's not guaranteed.
// They are only guaranteed to be sorted in increasing order by their destination position.
for marker in self.sourcemap {
// Take the closest start marker, but skip over start_markers that have the same start.
if marker.source <= source_range.start()
&& !start_marker.is_some_and(|existing| existing.source >= marker.source)
{
start_marker = Some(marker);
}
if marker.source >= source_range.end()
&& !end_marker.is_some_and(|existing| existing.source <= marker.source)
{
end_marker = Some(marker);
}
}
let start = start_marker.map(|marker| marker.dest).unwrap_or_default();
let end = end_marker.map_or_else(|| self.code.text_len(), |marker| marker.dest);
let code_range = TextRange::new(start, end);
PrintedRange {
code: self.code[code_range].to_string(),
source_range,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct PrintedRange {
code: String,
source_range: TextRange,
}
impl PrintedRange {
pub fn new(code: String, source_range: TextRange) -> Self {
Self { code, source_range }
}
pub fn empty() -> Self {
Self {
code: String::new(),
source_range: TextRange::default(),
}
}
/// The formatted code.
pub fn as_code(&self) -> &str {
&self.code
}
/// The range the formatted code corresponds to in the source document.
pub fn source_range(&self) -> TextRange {
self.source_range
}
#[must_use]
pub fn with_code(self, code: String) -> Self {
Self { code, ..self }
}
}
/// Public return type of the formatter

View File

@@ -60,7 +60,10 @@ impl<'a> Printer<'a> {
document: &'a Document,
indent: u16,
) -> PrintResult<Printed> {
let mut stack = PrintCallStack::new(PrintElementArgs::new(Indention::Level(indent)));
let indentation = Indention::Level(indent);
self.state.pending_indent = indentation;
let mut stack = PrintCallStack::new(PrintElementArgs::new(indentation));
let mut queue: PrintQueue<'a> = PrintQueue::new(document.as_ref());
loop {
@@ -143,7 +146,13 @@ impl<'a> Printer<'a> {
FormatElement::SourcePosition(position) => {
self.state.source_position = *position;
self.push_marker();
// The printer defers printing indents until the next text
// is printed. Pushing the marker now would mean that the
// mapped range includes the indent range, which we don't want.
// Only add a marker if we're not in an indented context, e.g. at the end of the file.
if self.state.pending_indent.is_empty() {
self.push_marker();
}
}
FormatElement::LineSuffixBoundary => {
@@ -511,11 +520,12 @@ impl<'a> Printer<'a> {
dest: self.state.buffer.text_len(),
};
if let Some(last) = self.state.source_markers.last() {
if last != &marker {
self.state.source_markers.push(marker);
}
} else {
if self
.state
.source_markers
.last()
.map_or(true, |last| last != &marker)
{
self.state.source_markers.push(marker);
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.1.14"
version = "0.2.0"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -85,6 +85,8 @@ tempfile = { workspace = true }
[features]
default = []
schemars = ["dep:schemars"]
# Enables rules for internal integration tests
test-rules = []
[lints]
workspace = true

View File

@@ -1,31 +1,87 @@
import os
import subprocess
import time
from pathlib import Path
# Violation cases:
async def foo():
async def func():
open("foo")
async def foo():
async def func():
time.sleep(1)
async def foo():
async def func():
subprocess.run("foo")
async def foo():
async def func():
subprocess.call("foo")
async def foo():
async def func():
subprocess.foo(0)
async def foo():
async def func():
os.wait4(10)
async def foo():
async def func():
os.wait(12)
# Violation cases for pathlib:
async def func():
Path("foo").open() # ASYNC101
async def func():
p = Path("foo")
p.open() # ASYNC101
async def func():
with Path("foo").open() as f: # ASYNC101
pass
async def func() -> None:
p = Path("foo")
async def bar():
p.open() # ASYNC101
async def func() -> None:
(p1, p2) = (Path("foo"), Path("bar"))
p1.open() # ASYNC101
# Non-violation cases for pathlib:
class Foo:
def open(self):
pass
async def func():
Foo().open() # OK
async def func():
def open():
pass
open() # OK
def func():
Path("foo").open() # OK

View File

@@ -9,6 +9,22 @@ obj.startswith(foo) or obj.startswith("foo")
# error
obj.endswith(foo) or obj.startswith(foo) or obj.startswith("foo")
def func():
msg = "hello world"
x = "h"
y = ("h", "e", "l", "l", "o")
z = "w"
if msg.startswith(x) or msg.startswith(y) or msg.startswith(z): # Error
print("yes")
def func():
msg = "hello world"
if msg.startswith(("h", "e", "l", "l", "o")) or msg.startswith("h") or msg.startswith("w"): # Error
print("yes")
# ok
obj.startswith(("foo", "bar"))
# ok
@@ -17,3 +33,46 @@ obj.endswith(("foo", "bar"))
obj.startswith("foo") or obj.endswith("bar")
# ok
obj.startswith("foo") or abc.startswith("bar")
def func():
msg = "hello world"
x = "h"
y = ("h", "e", "l", "l", "o")
if msg.startswith(x) or msg.startswith(y): # OK
print("yes")
def func():
msg = "hello world"
y = ("h", "e", "l", "l", "o")
if msg.startswith(y): # OK
print("yes")
def func():
msg = "hello world"
y = ("h", "e", "l", "l", "o")
if msg.startswith(y) or msg.startswith(y): # OK
print("yes")
def func():
msg = "hello world"
y = ("h", "e", "l", "l", "o")
x = ("w", "o", "r", "l", "d")
if msg.startswith(y) or msg.startswith(x) or msg.startswith("h"): # OK
print("yes")
def func():
msg = "hello world"
y = ("h", "e", "l", "l", "o")
x = ("w", "o", "r", "l", "d")
if msg.startswith(y) or msg.endswith(x) or msg.startswith("h"): # OK
print("yes")

View File

@@ -1,14 +1,15 @@
import _thread
import builtins
import os
import posix
from posix import abort
import sys as std_sys
import typing
import typing_extensions
import _thread
import _winapi
from posix import abort
from typing import NoReturn
import _winapi
import pytest
import typing_extensions
from pytest import xfail as py_xfail
###
@@ -326,3 +327,44 @@ def end_of_file():
if False:
return 1
x = 2 \
# function return type annotation NoReturn
def foo(x: int) -> int:
def bar() -> NoReturn:
abort()
if x == 5:
return 5
bar()
def foo(string: str) -> str:
def raises(value: str) -> NoReturn:
raise RuntimeError("something went wrong")
match string:
case "a":
return "first"
case "b":
return "second"
case "c":
return "third"
case _:
raises(string)
def foo() -> int:
def baz() -> int:
return 1
def bar() -> NoReturn:
a = 1 + 2
raise AssertionError("Very bad")
if baz() > 3:
return 1
bar()

View File

@@ -363,3 +363,46 @@ def foo():
def mavko_debari(P_kbar):
D=0.4853881 + 3.6006116*P - 0.0117368*(P-1.3822)**2
return D
# contextlib suppress in with statement
import contextlib
def foo():
x = 2
with contextlib.suppress(Exception):
x = x + 1
return x
def foo(data):
with open("in.txt") as file_out, contextlib.suppress(IOError):
file_out.write(data)
data = 10
return data
def foo(data):
with open("in.txt") as file_out:
file_out.write(data)
with contextlib.suppress(IOError):
data = 10
return data
def foo():
y = 1
x = 2
with contextlib.suppress(Exception):
x = 1
y = y + 2
return y # RET504
def foo():
y = 1
if y > 0:
with contextlib.suppress(Exception):
y = 2
return y

View File

@@ -103,6 +103,14 @@ def bar4(x):
return None
def fibo(n):
if n<2:
return n;
else:
last = 1;
last2 = 0;
###
# Non-error
###
@@ -131,6 +139,51 @@ def bar3(x, y, z):
return None
def bar4(x):
if True:
return
else:
# comment
pass
def bar5():
if True:
return
else: # comment
pass
def bar6():
if True:
return
else\
:\
# comment
pass
def bar7():
if True:
return
else\
: # comment
pass
def bar8():
if True:
return
else: pass
def bar9():
if True:
return
else:\
pass
x = 0
if x == 1:
@@ -139,3 +192,9 @@ elif x == 2:
y = "b"
else:
y = "c"
# Regression test for: https://github.com/astral-sh/ruff/issues/9732
def sb(self):
if self._sb is not None: return self._sb
else: self._sb = '\033[01;%dm'; self._sa = '\033[0;0m';

View File

@@ -4,6 +4,11 @@ if a:
elif c:
b
if a: # we preserve comments, too!
b
elif c: # but not on the second branch
b
if x == 1:
for _ in range(20):
print("hello")
@@ -63,6 +68,10 @@ elif result.eofs == "F":
errors = 1
elif result.eofs == "E":
errors = 1
elif result.eofs == "X":
errors = 1
elif result.eofs == "C":
errors = 1
# OK
@@ -70,7 +79,7 @@ def complicated_calc(*arg, **kwargs):
return 42
def foo(p):
def func(p):
if p == 2:
return complicated_calc(microsecond=0)
elif p == 3:
@@ -103,7 +112,7 @@ elif c:
x = 1
def foo():
def func():
a = True
b = False
if a > b: # end-of-line
@@ -114,3 +123,28 @@ def foo():
return 4
elif b is None:
return 4
def func():
"""Ensure that the named expression is parenthesized when merged."""
a = True
b = False
if a > b: # end-of-line
return 3
elif a := 1:
return 3
if a: # we preserve comments, too!
b
elif c: # but not on the second branch
b
if a: b # here's a comment
elif c: b
if(x > 200): pass
elif(100 < x and x < 200 and 300 < y and y < 800):
pass

View File

@@ -1,18 +0,0 @@
from __future__ import annotations
from typing import TypeVar
x: "int" | str # TCH006
x: ("int" | str) | "bool" # TCH006
def func():
x: "int" | str # OK
z: list[str, str | "int"] = [] # TCH006
type A = Value["int" | str] # OK
OldS = TypeVar('OldS', int | 'str', str) # TCH006

View File

@@ -1,16 +0,0 @@
from typing import TypeVar
x: "int" | str # TCH006
x: ("int" | str) | "bool" # TCH006
def func():
x: "int" | str # OK
z: list[str, str | "int"] = [] # TCH006
type A = Value["int" | str] # OK
OldS = TypeVar('OldS', int | 'str', str) # TCH006

View File

@@ -0,0 +1,18 @@
from __future__ import annotations
from typing import TypeVar
x: "int" | str # TCH010
x: ("int" | str) | "bool" # TCH010
def func():
x: "int" | str # OK
z: list[str, str | "int"] = [] # TCH010
type A = Value["int" | str] # OK
OldS = TypeVar('OldS', int | 'str', str) # TCH010

View File

@@ -0,0 +1,16 @@
from typing import TypeVar
x: "int" | str # TCH010
x: ("int" | str) | "bool" # TCH010
def func():
x: "int" | str # OK
z: list[str, str | "int"] = [] # TCH010
type A = Value["int" | str] # OK
OldS = TypeVar('OldS', int | 'str', str) # TCH010

View File

@@ -0,0 +1,18 @@
"""Test: avoid marking an `InitVar` as typing-only."""
from __future__ import annotations
from dataclasses import FrozenInstanceError, InitVar, dataclass
from pathlib import Path
@dataclass
class C:
i: int
j: int = None
database: InitVar[Path] = None
err: FrozenInstanceError = None
def __post_init__(self, database):
...

View File

@@ -1,5 +1,6 @@
#: E401
import os, sys
#: Okay
import os
import sys
@@ -59,3 +60,21 @@ import foo
a = 1
import bar
#: E401
import re as regex, string # also with a comment!
import re as regex, string; x = 1
x = 1; import re as regex, string
def blah():
import datetime as dt, copy
def nested_and_tested():
import builtins, textwrap as tw
x = 1; import re as regex, string
import re as regex, string; x = 1
if True: import re as regex, string

View File

@@ -126,3 +126,15 @@ class Foo:
# Okay
if type(value) is str:
...
import numpy as np
#: Okay
x.dtype == float
#: Okay
np.dtype(int) == float
#: E721
dtype == float

View File

@@ -0,0 +1,2 @@
'''trailing whitespace
inside a multiline string'''

View File

@@ -0,0 +1,59 @@
"""Do something.
Args:
x: the value
with a hanging indent
Returns:
the value
"""
def func():
"""Do something.
Args:
x: the value
with a hanging indent
Returns:
the value
"""
def func():
"""Do something.
Args:
x: the value
with a hanging indent
Returns:
the value
"""
def func():
"""Do something.
Args:
x: the value
with a hanging indent
Returns:
the value
"""
def func():
"""Do something.
Args:
x: the value
with a hanging indent
Returns:
the value"""

View File

@@ -1,9 +0,0 @@
from ast import literal_eval
eval("3 + 4")
literal_eval({1: 2})
def fn() -> None:
eval("3 + 4")

View File

@@ -1,11 +0,0 @@
def eval(content: str) -> None:
pass
eval("3 + 4")
literal_eval({1: 2})
def fn() -> None:
eval("3 + 4")

View File

@@ -1,10 +0,0 @@
import logging
import warnings
from warnings import warn
warnings.warn("this is ok")
warn("by itself is also ok")
logging.warning("this is fine")
logger = logging.getLogger(__name__)
logger.warning("this is fine")

View File

@@ -1,8 +0,0 @@
import logging
from logging import warn
logging.warn("this is not ok")
warn("not ok")
logger = logging.getLogger(__name__)
logger.warn("this is not ok")

View File

@@ -1,73 +0,0 @@
# OK
1<2 and 'b' and 'c'
1<2 or 'a' and 'b'
1<2 and 'a'
1<2 or 'a'
2>1
1<2 and 'a' or 'b' and 'c'
1<2 and 'a' or 'b' or 'c'
1<2 and 'a' or 'b' or 'c' or (lambda x: x+1)
1<2 and 'a' or 'b' or (lambda x: x+1) or 'c'
default = 'default'
if (not isinstance(default, bool) and isinstance(default, int)) \
or (isinstance(default, str) and default):
pass
docid, token = None, None
(docid is None and token is None) or (docid is not None and token is not None)
vendor, os_version = 'darwin', '14'
vendor == "debian" and os_version in ["12"] or vendor == "ubuntu" and os_version in []
# Don't emit if the parent is an `if` statement.
if (task_id in task_dict and task_dict[task_id] is not task) \
or task_id in used_group_ids:
pass
no_target, is_x64, target = True, False, 'target'
if (no_target and not is_x64) or target == 'ARM_APPL_RUST_TARGET':
pass
# Don't emit if the parent is a `bool_op` expression.
isinstance(val, str) and ((len(val) == 7 and val[0] == "#") or val in enums.NamedColor)
# Errors
1<2 and 'a' or 'b'
(lambda x: x+1) and 'a' or 'b'
'a' and (lambda x: x+1) or 'orange'
val = '#0000FF'
(len(val) == 7 and val[0] == "#") or val in {'green'}
marker = 'marker'
isinstance(marker, dict) and 'field' in marker or marker in {}
def has_oranges(oranges, apples=None) -> bool:
return apples and False or oranges
[x for x in l if a and b or c]
{x: y for x in l if a and b or c}
{x for x in l if a and b or c}
new_list = [
x
for sublist in all_lists
if a and b or c
for x in sublist
if (isinstance(operator, list) and x in operator) or x != operator
]

View File

@@ -91,6 +91,9 @@ class Apples:
def __prepare__():
pass
def __mro_entries__(self, bases):
pass
def __foo_bar__(): # this is not checked by the [bad-dunder-name] rule
...

View File

@@ -49,6 +49,17 @@ def not_ok1():
pass
def not_ok1_with_comments():
if 1:
pass
else:
# inner comment
if 2:
pass
else:
pass # final pass comment
# Regression test for https://github.com/apache/airflow/blob/f1e1cdcc3b2826e68ba133f350300b5065bbca33/airflow/models/dag.py#L1737
def not_ok2():
if True:
@@ -61,3 +72,28 @@ def not_ok2():
else:
print(4)
def not_ok3():
if 1:
pass
else:
if 2: pass
else: pass
def not_ok4():
if 1:
pass
else:
if 2: pass
else:
pass
def not_ok5():
if 1:
pass
else:
if 2:
pass
else: pass

View File

@@ -6,8 +6,9 @@ dictVarBad = os.getenv("AAA", {"a", 7}) # [invalid-envvar-default]
print(os.getenv("TEST", False)) # [invalid-envvar-default]
os.getenv("AA", "GOOD")
os.getenv("AA", f"GOOD")
os.getenv("AA", "GOOD" + "BAD")
os.getenv("AA", "GOOD" + "BAR")
os.getenv("AA", "GOOD" + 1)
os.getenv("AA", "GOOD %s" % "BAD")
os.getenv("AA", "GOOD %s" % "BAR")
os.getenv("B", Z)
os.getenv("AA", "GOOD" if Z else "BAR")
os.getenv("AA", 1 if Z else "BAR") # [invalid-envvar-default]

View File

@@ -68,6 +68,9 @@ if pi_estimation == 3.141592653589793238: # [magic-value-comparison]
if pi_estimation == PI: # correct
pass
if pi_estimation == 0x3: # [magic-value-comparison]
pass
HELLO_WORLD = b"Hello, World!"
user_input = b"Hello, There!"

View File

@@ -0,0 +1,56 @@
class StudentA:
__slots__ = ("name",)
def __init__(self, name, surname):
self.name = name
self.surname = surname # [assigning-non-slot]
self.setup()
def setup(self):
pass
class StudentB:
__slots__ = ("name", "surname")
def __init__(self, name, middle_name):
self.name = name
self.middle_name = middle_name # [assigning-non-slot]
self.setup()
def setup(self):
pass
class StudentC:
__slots__ = ("name", "surname")
def __init__(self, name, surname):
self.name = name
self.surname = surname # OK
self.setup()
def setup(self):
pass
class StudentD(object):
__slots__ = ("name", "surname")
def __init__(self, name, middle_name):
self.name = name
self.middle_name = middle_name # [assigning-non-slot]
self.setup()
def setup(self):
pass
class StudentE(StudentD):
def __init__(self, name, middle_name):
self.name = name
self.middle_name = middle_name # OK
self.setup()
def setup(self):
pass

View File

@@ -0,0 +1,9 @@
print([1, 2, 3][3]) # PLE0643
print([1, 2, 3][-4]) # PLE0643
print([1, 2, 3][999999999999999999999999999999999999999999]) # PLE0643
print([1, 2, 3][-999999999999999999999999999999999999999999]) # PLE0643
print([1, 2, 3][2]) # OK
print([1, 2, 3][0]) # OK
print([1, 2, 3][-3]) # OK
print([1, 2, 3][3:]) # OK

View File

@@ -0,0 +1,21 @@
def correct_fruits(fruits) -> bool:
if len(fruits) > 1: # PLR1702
if "apple" in fruits:
if "orange" in fruits:
count = fruits["orange"]
if count % 2:
if "kiwi" in fruits:
if count == 2:
return True
return False
# Ok
def correct_fruits(fruits) -> bool:
if len(fruits) > 1:
if "apple" in fruits:
if "orange" in fruits:
count = fruits["orange"]
if count % 2:
if "kiwi" in fruits:
return True
return False

View File

@@ -135,3 +135,14 @@ def test_break_in_match():
else:
return True
return False
def test_retain_comment():
"""Retain the comment within the `else` block"""
for j in range(10):
pass
else:
# [useless-else-on-loop]
print("fat chance")
for j in range(10):
break

View File

@@ -0,0 +1,58 @@
import abc
from abc import abstractmethod, ABCMeta
# Errors
class A0(metaclass=abc.ABCMeta):
@abstractmethod
def foo(self): pass
class A1(metaclass=ABCMeta):
@abstractmethod
def foo(self): pass
class B0:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__()
class B1:
pass
class A2(B0, B1, metaclass=ABCMeta):
@abstractmethod
def foo(self): pass
class A3(B0, before_metaclass=1, metaclass=abc.ABCMeta):
pass
# OK
class Meta(type):
def __new__(cls, *args, **kwargs):
return super().__new__(cls, *args)
class A4(metaclass=Meta, no_metaclass=ABCMeta):
@abstractmethod
def foo(self): pass
class A5(metaclass=Meta):
pass
class A6(abc.ABC):
@abstractmethod
def foo(self): pass
class A7(B0, abc.ABC, B1):
@abstractmethod
def foo(self): pass

View File

@@ -67,3 +67,15 @@ class G(F):
without_annotation = []
class_variable: ClassVar[list[int]] = []
final_variable: Final[list[int]] = []
from pydantic import BaseConfig
class H(BaseModel):
class Config(BaseConfig):
mutable_default: list[int] = []
immutable_annotation: Sequence[int] = []
without_annotation = []
class_variable: ClassVar[list[int]] = []
final_variable: Final[list[int]] = []

View File

@@ -218,6 +218,38 @@ __all__ = (
__all__ = ("don't" "care" "about", "__all__" "with", "concatenated" "strings")
############################################################
# Trailing-comma edge cases that should be flagged and fixed
############################################################
__all__ = (
"loads",
"dumps",)
__all__ = [
"loads",
"dumps" , ]
__all__ = ['xp', 'yp',
'canvas'
# very strangely placed comment
,
# another strangely placed comment
]
__all__ = (
"foo"
# strange comment 1
,
# comment about bar
"bar"
# strange comment 2
,
)
###################################
# These should all not get flagged:
###################################
@@ -306,3 +338,5 @@ __all__ = (
__all__ = [
"foo", (), "bar"
]
__all__ = "foo", "an" "implicitly_concatenated_second_item", not_a_string_literal

View File

@@ -0,0 +1,265 @@
#########################
# Single-line definitions
#########################
class Klass:
__slots__ = ["d", "c", "b", "a"] # a comment that is untouched
__slots__ = ("d", "c", "b", "a")
# Quoting style is retained,
# but unnecessary parens are not
__slots__: set = {'b', "c", ((('a')))}
# Trailing commas are also not retained for single-line definitions
# (but they are in multiline definitions)
__slots__: tuple = ("b", "c", "a",)
class Klass2:
if bool():
__slots__ = {"x": "docs for x", "m": "docs for m", "a": "docs for a"}
else:
__slots__ = "foo3", "foo2", "foo1" # NB: an implicit tuple (without parens)
__slots__: list[str] = ["the", "three", "little", "pigs"]
__slots__ = ("parenthesized_item"), "in", ("an_unparenthesized_tuple")
# we use natural sort,
# not alphabetical sort or "isort-style" sort
__slots__ = {"aadvark237", "aadvark10092", "aadvark174", "aadvark532"}
############################
# Neat multiline definitions
############################
class Klass3:
__slots__ = (
"d0",
"c0", # a comment regarding 'c0'
"b0",
# a comment regarding 'a0':
"a0"
)
__slots__ = [
"d",
"c", # a comment regarding 'c'
"b",
# a comment regarding 'a':
"a"
]
##################################
# Messier multiline definitions...
##################################
class Klass4:
# comment0
__slots__ = ("d", "a", # comment1
# comment2
"f", "b",
"strangely", # comment3
# comment4
"formatted",
# comment5
) # comment6
# comment7
__slots__ = [ # comment0
# comment1
# comment2
"dx", "cx", "bx", "ax" # comment3
# comment4
# comment5
# comment6
] # comment7
# from cpython/Lib/pathlib/__init__.py
class PurePath:
__slots__ = (
# The `_raw_paths` slot stores unnormalized string paths. This is set
# in the `__init__()` method.
'_raw_paths',
# The `_drv`, `_root` and `_tail_cached` slots store parsed and
# normalized parts of the path. They are set when any of the `drive`,
# `root` or `_tail` properties are accessed for the first time. The
# three-part division corresponds to the result of
# `os.path.splitroot()`, except that the tail is further split on path
# separators (i.e. it is a list of strings), and that the root and
# tail are normalized.
'_drv', '_root', '_tail_cached',
# The `_str` slot stores the string representation of the path,
# computed from the drive, root and tail when `__str__()` is called
# for the first time. It's used to implement `_str_normcase`
'_str',
# The `_str_normcase_cached` slot stores the string path with
# normalized case. It is set when the `_str_normcase` property is
# accessed for the first time. It's used to implement `__eq__()`
# `__hash__()`, and `_parts_normcase`
'_str_normcase_cached',
# The `_parts_normcase_cached` slot stores the case-normalized
# string path after splitting on path separators. It's set when the
# `_parts_normcase` property is accessed for the first time. It's used
# to implement comparison methods like `__lt__()`.
'_parts_normcase_cached',
# The `_hash` slot stores the hash of the case-normalized string
# path. It's set when `__hash__()` is called for the first time.
'_hash',
)
# From cpython/Lib/pickletools.py
class ArgumentDescriptor(object):
__slots__ = (
# name of descriptor record, also a module global name; a string
'name',
# length of argument, in bytes; an int; UP_TO_NEWLINE and
# TAKEN_FROM_ARGUMENT{1,4,8} are negative values for variable-length
# cases
'n',
# a function taking a file-like object, reading this kind of argument
# from the object at the current position, advancing the current
# position by n bytes, and returning the value of the argument
'reader',
# human-readable docs for this arg descriptor; a string
'doc',
)
####################################
# Should be flagged, but not fixed
####################################
# from cpython/Lib/test/test_inspect.py.
# Multiline dicts are out of scope.
class SlotUser:
__slots__ = {'power': 'measured in kilowatts',
'distance': 'measured in kilometers'}
class Klass5:
__slots__ = (
"look",
(
"a_veeeeeeeeeeeeeeeeeeery_long_parenthesized_item"
),
)
__slots__ = (
"b",
((
"c"
)),
"a"
)
__slots__ = ("don't" "care" "about", "__slots__" "with", "concatenated" "strings")
############################################################
# Trailing-comma edge cases that should be flagged and fixed
############################################################
class BezierBuilder:
__slots__ = ('xp', 'yp',
'canvas',)
class BezierBuilder2:
__slots__ = {'xp', 'yp',
'canvas' , }
class BezierBuilder3:
__slots__ = ['xp', 'yp',
'canvas'
# very strangely placed comment
,
# another strangely placed comment
]
class BezierBuilder4:
__slots__ = (
"foo"
# strange comment 1
,
# comment about bar
"bar"
# strange comment 2
,
)
###################################
# These should all not get flagged:
###################################
class Klass6:
__slots__ = ()
__slots__ = []
__slots__ = ("single_item",)
__slots__ = (
"single_item_multiline",
)
__slots__ = {"single_item",}
__slots__ = {"single_item_no_trailing_comma": "docs for that"}
__slots__ = [
"single_item_multiline_no_trailing_comma"
]
__slots__ = ("not_a_tuple_just_a_string")
__slots__ = ["a", "b", "c", "d"]
__slots__ += ["e", "f", "g"]
__slots__ = ("a", "b", "c", "d")
if bool():
__slots__ += ("e", "f", "g")
else:
__slots__ += ["alpha", "omega"]
__slots__ = {"not": "sorted", "but": "includes", **a_kwarg_splat}
__slots__ = ("b", "a", "e", "d")
__slots__ = ["b", "a", "e", "d"]
__slots__ = ["foo", "bar", "antipasti"]
class Klass6:
__slots__ = (9, 8, 7)
__slots__ = ( # This is just an empty tuple,
# but,
# it's very well
) # documented
# We don't deduplicate elements;
# this just ensures that duplicate elements aren't unnecessarily
# reordered by an autofix:
__slots__ = (
"duplicate_element", # comment1
"duplicate_element", # comment3
"duplicate_element", # comment2
"duplicate_element", # comment0
)
__slots__ = "foo", "an" "implicitly_concatenated_second_item", not_a_string_literal
__slots__ =[
[]
]
__slots__ = [
()
]
__slots__ = (
()
)
__slots__ = (
[]
)
__slots__ = (
(),
)
__slots__ = (
[],
)
__slots__ = (
"foo", [], "bar"
)
__slots__ = [
"foo", (), "bar"
]

View File

@@ -0,0 +1,32 @@
pierogi_fillings = [
"cabbage",
"strawberry",
"cheese",
"blueberry",
]
# Errors.
dict.fromkeys(pierogi_fillings, [])
dict.fromkeys(pierogi_fillings, list())
dict.fromkeys(pierogi_fillings, {})
dict.fromkeys(pierogi_fillings, set())
dict.fromkeys(pierogi_fillings, {"pre": "populated!"})
dict.fromkeys(pierogi_fillings, dict())
# Okay.
dict.fromkeys(pierogi_fillings)
dict.fromkeys(pierogi_fillings, None)
dict.fromkeys(pierogi_fillings, 1)
dict.fromkeys(pierogi_fillings)
dict.fromkeys(pierogi_fillings, ("blessed", "tuples", "don't", "mutate"))
dict.fromkeys(pierogi_fillings, "neither do strings")
class MysteryBox: ...
dict.fromkeys(pierogi_fillings, MysteryBox)
bar.fromkeys(pierogi_fillings, [])
def bad_dict() -> None:
dict = MysteryBox()
dict.fromkeys(pierogi_fillings, [])

View File

@@ -0,0 +1,92 @@
# Violation cases: RUF025
def func():
numbers = [1, 2, 3]
{n: None for n in numbers} # RUF025
def func():
for key, value in {n: 1 for n in [1, 2, 3]}.items(): # RUF025
pass
def func():
{n: 1.1 for n in [1, 2, 3]} # RUF025
def func():
{n: complex(3, 5) for n in [1, 2, 3]} # RUF025
def func():
def f(data):
return data
f({c: "a" for c in "12345"}) # RUF025
def func():
{n: True for n in [1, 2, 2]} # RUF025
def func():
{n: b"hello" for n in (1, 2, 2)} # RUF025
def func():
{n: ... for n in [1, 2, 3]} # RUF025
def func():
{n: False for n in {1: "a", 2: "b"}} # RUF025
def func():
{(a, b): 1 for (a, b) in [(1, 2), (3, 4)]} # RUF025
def func():
def f():
return 1
a = f()
{n: a for n in [1, 2, 3]} # RUF025
def func():
values = ["a", "b", "c"]
[{n: values for n in [1, 2, 3]}] # RUF025
# Non-violation cases: RUF025
def func():
{n: 1 for n in [1, 2, 3, 4, 5] if n < 3} # OK
def func():
{n: 1 for c in [1, 2, 3, 4, 5] for n in [1, 2, 3] if c < 3} # OK
def func():
def f():
pass
{n: f() for n in [1, 2, 3]} # OK
def func():
{n: n for n in [1, 2, 3, 4, 5]} # OK
def func():
def f():
return {n: 1 for c in [1, 2, 3, 4, 5] for n in [1, 2, 3]} # OK
f()
def func():
{(a, b): a + b for (a, b) in [(1, 2), (3, 4)]} # OK

View File

@@ -0,0 +1,120 @@
from collections import defaultdict
# Violation cases: RUF026
def func():
defaultdict(default_factory=None) # RUF026
def func():
defaultdict(default_factory=int) # RUF026
def func():
defaultdict(default_factory=float) # RUF026
def func():
defaultdict(default_factory=dict) # RUF026
def func():
defaultdict(default_factory=list) # RUF026
def func():
defaultdict(default_factory=tuple) # RUF026
def func():
def foo():
pass
defaultdict(default_factory=foo) # RUF026
def func():
defaultdict(default_factory=lambda: 1) # RUF026
def func():
from collections import deque
defaultdict(default_factory=deque) # RUF026
def func():
class MyCallable:
def __call__(self):
pass
defaultdict(default_factory=MyCallable()) # RUF026
def func():
defaultdict(default_factory=tuple, member=1) # RUF026
def func():
defaultdict(member=1, default_factory=tuple) # RUF026
def func():
defaultdict(member=1, default_factory=tuple,) # RUF026
def func():
defaultdict(
member=1,
default_factory=tuple,
) # RUF026
def func():
defaultdict(
default_factory=tuple,
member=1,
) # RUF026
# Non-violation cases: RUF026
def func():
defaultdict(default_factory=1) # OK
def func():
defaultdict(default_factory="wdefwef") # OK
def func():
defaultdict(default_factory=[1, 2, 3]) # OK
def func():
defaultdict() # OK
def func():
defaultdict(int) # OK
def func():
defaultdict(list) # OK
def func():
defaultdict(dict) # OK
def func():
defaultdict(dict, defaultdict=list) # OK
def func():
def constant_factory(value):
return lambda: value
defaultdict(constant_factory("<missing>"))

View File

@@ -1,40 +0,0 @@
"""
Violation:
Reraise without using 'from'
"""
class MyException(Exception):
pass
def func():
try:
a = 1
except Exception:
raise MyException()
def func():
try:
a = 1
except Exception:
if True:
raise MyException()
def good():
try:
a = 1
except MyException as e:
raise e # This is verbose violation, shouldn't trigger no cause
except Exception:
raise # Just re-raising don't need 'from'
def good():
try:
from mod import f
except ImportError:
def f():
raise MyException() # Raising within a new scope is fine

View File

@@ -256,25 +256,23 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
diagnostic.set_parent(range.start());
}
if checker.settings.preview.is_enabled() {
if let Some(import) = binding.as_any_import() {
if let Some(source) = binding.source {
diagnostic.try_set_fix(|| {
let statement = checker.semantic().statement(source);
let parent = checker.semantic().parent_statement(source);
let edit = fix::edits::remove_unused_imports(
std::iter::once(import.member_name().as_ref()),
statement,
parent,
checker.locator(),
checker.stylist(),
checker.indexer(),
)?;
Ok(Fix::safe_edit(edit).isolate(Checker::isolation(
checker.semantic().parent_statement_id(source),
)))
});
}
if let Some(import) = binding.as_any_import() {
if let Some(source) = binding.source {
diagnostic.try_set_fix(|| {
let statement = checker.semantic().statement(source);
let parent = checker.semantic().parent_statement(source);
let edit = fix::edits::remove_unused_imports(
std::iter::once(import.member_name().as_ref()),
statement,
parent,
checker.locator(),
checker.stylist(),
checker.indexer(),
)?;
Ok(Fix::safe_edit(edit).isolate(Checker::isolation(
checker.semantic().parent_statement_id(source),
)))
});
}
}

View File

@@ -5,7 +5,6 @@ use crate::checkers::ast::Checker;
use crate::registry::Rule;
use crate::rules::{
flake8_bandit, flake8_blind_except, flake8_bugbear, flake8_builtins, pycodestyle, pylint,
tryceratops,
};
/// Run lint rules over an [`ExceptHandler`] syntax node.
@@ -66,9 +65,6 @@ pub(crate) fn except_handler(except_handler: &ExceptHandler, checker: &mut Check
if checker.enabled(Rule::ExceptWithNonExceptionClasses) {
flake8_bugbear::rules::except_with_non_exception_classes(checker, except_handler);
}
if checker.enabled(Rule::ReraiseNoCause) {
tryceratops::rules::reraise_no_cause(checker, body);
}
if checker.enabled(Rule::BinaryOpException) {
pylint::rules::binary_op_exception(checker, except_handler);
}

View File

@@ -16,8 +16,7 @@ use crate::rules::{
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging,
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self,
flake8_simplify, flake8_tidy_imports, flake8_trio, flake8_type_checking, flake8_use_pathlib,
flynt, numpy, pandas_vet, pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade,
refurb, ruff,
flynt, numpy, pandas_vet, pep8_naming, pycodestyle, pyflakes, pylint, pyupgrade, refurb, ruff,
};
use crate::settings::types::PythonVersion;
@@ -125,6 +124,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::SliceCopy) {
refurb::rules::slice_copy(checker, subscript);
}
if checker.enabled(Rule::PotentialIndexError) {
pylint::rules::potential_index_error(checker, value, slice);
}
pandas_vet::rules::subscript(checker, value, expr);
}
@@ -770,12 +772,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::CallDateFromtimestamp) {
flake8_datetimez::rules::call_date_fromtimestamp(checker, func, expr.range());
}
if checker.enabled(Rule::Eval) {
pygrep_hooks::rules::no_eval(checker, func);
}
if checker.enabled(Rule::DeprecatedLogWarn) {
pygrep_hooks::rules::deprecated_log_warn(checker, call);
}
if checker.enabled(Rule::UnnecessaryDirectLambdaCall) {
pylint::rules::unnecessary_direct_lambda_call(checker, expr, func);
}
@@ -974,9 +970,16 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::SslInsecureVersion) {
flake8_bandit::rules::ssl_insecure_version(checker, call);
}
if checker.enabled(Rule::MutableFromkeysValue) {
ruff::rules::mutable_fromkeys_value(checker, call);
}
if checker.enabled(Rule::UnsortedDunderAll) {
ruff::rules::sort_dunder_all_extend_call(checker, call);
}
if checker.enabled(Rule::DefaultFactoryKwarg) {
ruff::rules::default_factory_kwarg(checker, call);
}
}
Expr::Dict(dict) => {
if checker.any_enabled(&[
@@ -1416,11 +1419,17 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::UnnecessaryDictIndexLookup) {
pylint::rules::unnecessary_dict_index_lookup_comprehension(checker, expr);
}
if checker.enabled(Rule::UnnecessaryComprehension) {
flake8_comprehensions::rules::unnecessary_dict_comprehension(
checker, expr, key, value, generators,
);
}
if checker.enabled(Rule::UnnecessaryDictComprehensionForIterable) {
ruff::rules::unnecessary_dict_comprehension_for_iterable(checker, dict_comp);
}
if checker.enabled(Rule::FunctionUsesLoopVariable) {
flake8_bugbear::rules::function_uses_loop_variable(checker, &Node::Expr(expr));
}
@@ -1430,7 +1439,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
}
if checker.enabled(Rule::StaticKeyDictComprehension) {
ruff::rules::static_key_dict_comprehension(checker, dict_comp);
flake8_bugbear::rules::static_key_dict_comprehension(checker, dict_comp);
}
}
Expr::GeneratorExp(
@@ -1492,9 +1501,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::RepeatedEqualityComparison) {
pylint::rules::repeated_equality_comparison(checker, bool_op);
}
if checker.enabled(Rule::AndOrTernary) {
pylint::rules::and_or_ternary(checker, bool_op);
}
if checker.enabled(Rule::UnnecessaryKeyCheck) {
ruff::rules::unnecessary_key_check(checker, expr);
}

View File

@@ -507,12 +507,18 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::NoSlotsInNamedtupleSubclass) {
flake8_slots::rules::no_slots_in_namedtuple_subclass(checker, stmt, class_def);
}
if checker.enabled(Rule::NonSlotAssignment) {
pylint::rules::non_slot_assignment(checker, class_def);
}
if checker.enabled(Rule::SingleStringSlots) {
pylint::rules::single_string_slots(checker, class_def);
}
if checker.enabled(Rule::BadDunderMethodName) {
pylint::rules::bad_dunder_method_name(checker, body);
}
if checker.enabled(Rule::MetaClassABCMeta) {
refurb::rules::metaclass_abcmeta(checker, class_def);
}
}
Stmt::Import(ast::StmtImport { names, range: _ }) => {
if checker.enabled(Rule::MultipleImportsOnOneLine) {
@@ -1069,13 +1075,10 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
ruff::rules::sort_dunder_all_aug_assign(checker, aug_assign);
}
}
Stmt::If(
if_ @ ast::StmtIf {
test,
elif_else_clauses,
..
},
) => {
Stmt::If(if_ @ ast::StmtIf { test, .. }) => {
if checker.enabled(Rule::TooManyNestedBlocks) {
pylint::rules::too_many_nested_blocks(checker, stmt);
}
if checker.enabled(Rule::EmptyTypeCheckingBlock) {
if typing::is_type_checking_block(if_, &checker.semantic) {
flake8_type_checking::rules::empty_type_checking_block(checker, if_);
@@ -1092,7 +1095,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
);
}
if checker.enabled(Rule::IfWithSameArms) {
flake8_simplify::rules::if_with_same_arms(checker, checker.locator, if_);
flake8_simplify::rules::if_with_same_arms(checker, if_);
}
if checker.enabled(Rule::NeedlessBool) {
flake8_simplify::rules::needless_bool(checker, if_);
@@ -1117,9 +1120,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
pyupgrade::rules::outdated_version_block(checker, if_);
}
if checker.enabled(Rule::CollapsibleElseIf) {
if let Some(diagnostic) = pylint::rules::collapsible_else_if(elif_else_clauses) {
checker.diagnostics.push(diagnostic);
}
pylint::rules::collapsible_else_if(checker, stmt);
}
if checker.enabled(Rule::CheckAndRemoveFromSet) {
refurb::rules::check_and_remove_from_set(checker, if_);
@@ -1210,6 +1211,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
Stmt::With(with_stmt @ ast::StmtWith { items, body, .. }) => {
if checker.enabled(Rule::TooManyNestedBlocks) {
pylint::rules::too_many_nested_blocks(checker, stmt);
}
if checker.enabled(Rule::AssertRaisesException) {
flake8_bugbear::rules::assert_raises_exception(checker, items);
}
@@ -1237,6 +1241,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
Stmt::While(while_stmt @ ast::StmtWhile { body, orelse, .. }) => {
if checker.enabled(Rule::TooManyNestedBlocks) {
pylint::rules::too_many_nested_blocks(checker, stmt);
}
if checker.enabled(Rule::FunctionUsesLoopVariable) {
flake8_bugbear::rules::function_uses_loop_variable(checker, &Node::Stmt(stmt));
}
@@ -1260,6 +1267,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
range: _,
},
) => {
if checker.enabled(Rule::TooManyNestedBlocks) {
pylint::rules::too_many_nested_blocks(checker, stmt);
}
if checker.any_enabled(&[
Rule::EnumerateForLoop,
Rule::IncorrectDictIterator,
@@ -1324,6 +1334,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
finalbody,
..
}) => {
if checker.enabled(Rule::TooManyNestedBlocks) {
pylint::rules::too_many_nested_blocks(checker, stmt);
}
if checker.enabled(Rule::JumpStatementInFinally) {
flake8_bugbear::rules::jump_statement_in_finally(checker, finalbody);
}
@@ -1458,6 +1471,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.settings.rules.enabled(Rule::UnsortedDunderAll) {
ruff::rules::sort_dunder_all_assign(checker, assign);
}
if checker.enabled(Rule::UnsortedDunderSlots) {
ruff::rules::sort_dunder_slots_assign(checker, assign);
}
if checker.source_type.is_stub() {
if checker.any_enabled(&[
Rule::UnprefixedTypeParam,
@@ -1531,6 +1547,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.settings.rules.enabled(Rule::UnsortedDunderAll) {
ruff::rules::sort_dunder_all_ann_assign(checker, assign_stmt);
}
if checker.enabled(Rule::UnsortedDunderSlots) {
ruff::rules::sort_dunder_slots_ann_assign(checker, assign_stmt);
}
if checker.source_type.is_stub() {
if let Some(value) = value {
if checker.enabled(Rule::AssignmentDefaultInStub) {

View File

@@ -342,8 +342,7 @@ where
|| helpers::is_assignment_to_a_dunder(stmt)
|| helpers::in_nested_block(self.semantic.current_statements())
|| imports::is_matplotlib_activation(stmt, self.semantic())
|| self.settings.preview.is_enabled()
&& imports::is_sys_path_modification(stmt, self.semantic()))
|| imports::is_sys_path_modification(stmt, self.semantic()))
{
self.semantic.flags |= SemanticModelFlags::IMPORT_BOUNDARY;
}
@@ -721,6 +720,21 @@ where
AnnotationContext::RuntimeEvaluated => {
self.visit_runtime_evaluated_annotation(annotation);
}
AnnotationContext::TypingOnly
if flake8_type_checking::helpers::is_dataclass_meta_annotation(
annotation,
self.semantic(),
) =>
{
if let Expr::Subscript(subscript) = &**annotation {
// Ex) `InitVar[str]`
self.visit_runtime_required_annotation(&subscript.value);
self.visit_annotation(&subscript.slice);
} else {
// Ex) `InitVar`
self.visit_runtime_required_annotation(annotation);
}
}
AnnotationContext::TypingOnly => self.visit_annotation(annotation),
}

View File

@@ -52,6 +52,11 @@ pub enum RuleGroup {
Stable,
/// The rule is unstable, and preview mode must be enabled for usage.
Preview,
/// The rule has been deprecated, warnings will be displayed during selection in stable
/// and errors will be raised if used with preview mode enabled.
Deprecated,
/// The rule has been removed, errors will be displayed on use.
Removed,
/// Legacy category for unstable rules, supports backwards compatible selection.
#[deprecated(note = "Use `RuleGroup::Preview` for new rules instead")]
Nursery,
@@ -224,11 +229,13 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "E0116") => (RuleGroup::Stable, rules::pylint::rules::ContinueInFinally),
(Pylint, "E0117") => (RuleGroup::Stable, rules::pylint::rules::NonlocalWithoutBinding),
(Pylint, "E0118") => (RuleGroup::Stable, rules::pylint::rules::LoadBeforeGlobalDeclaration),
(Pylint, "E0237") => (RuleGroup::Stable, rules::pylint::rules::NonSlotAssignment),
(Pylint, "E0241") => (RuleGroup::Stable, rules::pylint::rules::DuplicateBases),
(Pylint, "E0302") => (RuleGroup::Stable, rules::pylint::rules::UnexpectedSpecialMethodSignature),
(Pylint, "E0307") => (RuleGroup::Stable, rules::pylint::rules::InvalidStrReturnType),
(Pylint, "E0604") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllObject),
(Pylint, "E0605") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllFormat),
(Pylint, "E0643") => (RuleGroup::Preview, rules::pylint::rules::PotentialIndexError),
(Pylint, "E0704") => (RuleGroup::Preview, rules::pylint::rules::MisplacedBareRaise),
(Pylint, "E1132") => (RuleGroup::Preview, rules::pylint::rules::RepeatedKeywordArgument),
(Pylint, "E1142") => (RuleGroup::Stable, rules::pylint::rules::AwaitOutsideAsync),
@@ -259,10 +266,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "R0916") => (RuleGroup::Preview, rules::pylint::rules::TooManyBooleanExpressions),
(Pylint, "R0917") => (RuleGroup::Preview, rules::pylint::rules::TooManyPositional),
(Pylint, "R1701") => (RuleGroup::Stable, rules::pylint::rules::RepeatedIsinstanceCalls),
(Pylint, "R1702") => (RuleGroup::Preview, rules::pylint::rules::TooManyNestedBlocks),
(Pylint, "R1704") => (RuleGroup::Preview, rules::pylint::rules::RedefinedArgumentFromLocal),
(Pylint, "R1711") => (RuleGroup::Stable, rules::pylint::rules::UselessReturn),
(Pylint, "R1714") => (RuleGroup::Stable, rules::pylint::rules::RepeatedEqualityComparison),
(Pylint, "R1706") => (RuleGroup::Preview, rules::pylint::rules::AndOrTernary),
(Pylint, "R1706") => (RuleGroup::Removed, rules::pylint::rules::AndOrTernary),
(Pylint, "R1722") => (RuleGroup::Stable, rules::pylint::rules::SysExitAlias),
(Pylint, "R1733") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDictIndexLookup),
(Pylint, "R1736") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryListIndexLookup),
@@ -303,11 +311,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Async, "102") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingOsCallInAsyncFunction),
// flake8-trio
(Flake8Trio, "100") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioTimeoutWithoutAwait),
(Flake8Trio, "105") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioSyncCall),
(Flake8Trio, "109") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioAsyncFunctionWithTimeout),
(Flake8Trio, "110") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioUnneededSleep),
(Flake8Trio, "115") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioZeroSleepCall),
(Flake8Trio, "100") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioTimeoutWithoutAwait),
(Flake8Trio, "105") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioSyncCall),
(Flake8Trio, "109") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioAsyncFunctionWithTimeout),
(Flake8Trio, "110") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioUnneededSleep),
(Flake8Trio, "115") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioZeroSleepCall),
// flake8-builtins
(Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing),
@@ -348,6 +356,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Bugbear, "032") => (RuleGroup::Stable, rules::flake8_bugbear::rules::UnintentionalTypeAnnotation),
(Flake8Bugbear, "033") => (RuleGroup::Stable, rules::flake8_bugbear::rules::DuplicateValue),
(Flake8Bugbear, "034") => (RuleGroup::Stable, rules::flake8_bugbear::rules::ReSubPositionalArgs),
(Flake8Bugbear, "035") => (RuleGroup::Stable, rules::flake8_bugbear::rules::StaticKeyDictComprehension),
(Flake8Bugbear, "904") => (RuleGroup::Stable, rules::flake8_bugbear::rules::RaiseWithoutFromInsideExcept),
(Flake8Bugbear, "905") => (RuleGroup::Stable, rules::flake8_bugbear::rules::ZipWithoutExplicitStrict),
@@ -414,14 +423,14 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Quotes, "001") => (RuleGroup::Stable, rules::flake8_quotes::rules::BadQuotesMultilineString),
(Flake8Quotes, "002") => (RuleGroup::Stable, rules::flake8_quotes::rules::BadQuotesDocstring),
(Flake8Quotes, "003") => (RuleGroup::Stable, rules::flake8_quotes::rules::AvoidableEscapedQuote),
(Flake8Quotes, "004") => (RuleGroup::Preview, rules::flake8_quotes::rules::UnnecessaryEscapedQuote),
(Flake8Quotes, "004") => (RuleGroup::Stable, rules::flake8_quotes::rules::UnnecessaryEscapedQuote),
// flake8-annotations
(Flake8Annotations, "001") => (RuleGroup::Stable, rules::flake8_annotations::rules::MissingTypeFunctionArgument),
(Flake8Annotations, "002") => (RuleGroup::Stable, rules::flake8_annotations::rules::MissingTypeArgs),
(Flake8Annotations, "003") => (RuleGroup::Stable, rules::flake8_annotations::rules::MissingTypeKwargs),
(Flake8Annotations, "101") => (RuleGroup::Stable, rules::flake8_annotations::rules::MissingTypeSelf),
(Flake8Annotations, "102") => (RuleGroup::Stable, rules::flake8_annotations::rules::MissingTypeCls),
(Flake8Annotations, "101") => (RuleGroup::Deprecated, rules::flake8_annotations::rules::MissingTypeSelf),
(Flake8Annotations, "102") => (RuleGroup::Deprecated, rules::flake8_annotations::rules::MissingTypeCls),
(Flake8Annotations, "201") => (RuleGroup::Stable, rules::flake8_annotations::rules::MissingReturnTypeUndocumentedPublicFunction),
(Flake8Annotations, "202") => (RuleGroup::Stable, rules::flake8_annotations::rules::MissingReturnTypePrivateFunction),
(Flake8Annotations, "204") => (RuleGroup::Stable, rules::flake8_annotations::rules::MissingReturnTypeSpecialMethod),
@@ -455,7 +464,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Simplify, "109") => (RuleGroup::Stable, rules::flake8_simplify::rules::CompareWithTuple),
(Flake8Simplify, "110") => (RuleGroup::Stable, rules::flake8_simplify::rules::ReimplementedBuiltin),
(Flake8Simplify, "112") => (RuleGroup::Stable, rules::flake8_simplify::rules::UncapitalizedEnvironmentVariables),
(Flake8Simplify, "113") => (RuleGroup::Preview, rules::flake8_simplify::rules::EnumerateForLoop),
(Flake8Simplify, "113") => (RuleGroup::Stable, rules::flake8_simplify::rules::EnumerateForLoop),
(Flake8Simplify, "114") => (RuleGroup::Stable, rules::flake8_simplify::rules::IfWithSameArms),
(Flake8Simplify, "115") => (RuleGroup::Stable, rules::flake8_simplify::rules::OpenFileWithContextHandler),
(Flake8Simplify, "116") => (RuleGroup::Stable, rules::flake8_simplify::rules::IfElseBlockInsteadOfDictLookup),
@@ -474,7 +483,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Simplify, "300") => (RuleGroup::Stable, rules::flake8_simplify::rules::YodaConditions),
(Flake8Simplify, "401") => (RuleGroup::Stable, rules::flake8_simplify::rules::IfElseBlockInsteadOfDictGet),
(Flake8Simplify, "910") => (RuleGroup::Stable, rules::flake8_simplify::rules::DictGetWithNoneDefault),
(Flake8Simplify, "911") => (RuleGroup::Preview, rules::flake8_simplify::rules::ZipDictKeysAndValues),
(Flake8Simplify, "911") => (RuleGroup::Stable, rules::flake8_simplify::rules::ZipDictKeysAndValues),
// flake8-copyright
#[allow(deprecated)]
@@ -519,7 +528,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pyupgrade, "038") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP604Isinstance),
(Pyupgrade, "039") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryClassParentheses),
(Pyupgrade, "040") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695TypeAlias),
(Pyupgrade, "041") => (RuleGroup::Preview, rules::pyupgrade::rules::TimeoutErrorAlias),
(Pyupgrade, "041") => (RuleGroup::Stable, rules::pyupgrade::rules::TimeoutErrorAlias),
// pydocstyle
(Pydocstyle, "100") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedPublicModule),
@@ -606,8 +615,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Bandit, "110") => (RuleGroup::Stable, rules::flake8_bandit::rules::TryExceptPass),
(Flake8Bandit, "112") => (RuleGroup::Stable, rules::flake8_bandit::rules::TryExceptContinue),
(Flake8Bandit, "113") => (RuleGroup::Stable, rules::flake8_bandit::rules::RequestWithoutTimeout),
(Flake8Bandit, "201") => (RuleGroup::Preview, rules::flake8_bandit::rules::FlaskDebugTrue),
(Flake8Bandit, "202") => (RuleGroup::Preview, rules::flake8_bandit::rules::TarfileUnsafeMembers),
(Flake8Bandit, "201") => (RuleGroup::Stable, rules::flake8_bandit::rules::FlaskDebugTrue),
(Flake8Bandit, "202") => (RuleGroup::Stable, rules::flake8_bandit::rules::TarfileUnsafeMembers),
(Flake8Bandit, "301") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousPickleUsage),
(Flake8Bandit, "302") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousMarshalUsage),
(Flake8Bandit, "303") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousInsecureHashUsage),
@@ -645,12 +654,12 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Bandit, "413") => (RuleGroup::Preview, rules::flake8_bandit::rules::SuspiciousPycryptoImport),
(Flake8Bandit, "415") => (RuleGroup::Preview, rules::flake8_bandit::rules::SuspiciousPyghmiImport),
(Flake8Bandit, "501") => (RuleGroup::Stable, rules::flake8_bandit::rules::RequestWithNoCertValidation),
(Flake8Bandit, "502") => (RuleGroup::Preview, rules::flake8_bandit::rules::SslInsecureVersion),
(Flake8Bandit, "503") => (RuleGroup::Preview, rules::flake8_bandit::rules::SslWithBadDefaults),
(Flake8Bandit, "504") => (RuleGroup::Preview, rules::flake8_bandit::rules::SslWithNoVersion),
(Flake8Bandit, "505") => (RuleGroup::Preview, rules::flake8_bandit::rules::WeakCryptographicKey),
(Flake8Bandit, "502") => (RuleGroup::Stable, rules::flake8_bandit::rules::SslInsecureVersion),
(Flake8Bandit, "503") => (RuleGroup::Stable, rules::flake8_bandit::rules::SslWithBadDefaults),
(Flake8Bandit, "504") => (RuleGroup::Stable, rules::flake8_bandit::rules::SslWithNoVersion),
(Flake8Bandit, "505") => (RuleGroup::Stable, rules::flake8_bandit::rules::WeakCryptographicKey),
(Flake8Bandit, "506") => (RuleGroup::Stable, rules::flake8_bandit::rules::UnsafeYAMLLoad),
(Flake8Bandit, "507") => (RuleGroup::Preview, rules::flake8_bandit::rules::SSHNoHostKeyVerification),
(Flake8Bandit, "507") => (RuleGroup::Stable, rules::flake8_bandit::rules::SSHNoHostKeyVerification),
(Flake8Bandit, "508") => (RuleGroup::Stable, rules::flake8_bandit::rules::SnmpInsecureVersion),
(Flake8Bandit, "509") => (RuleGroup::Stable, rules::flake8_bandit::rules::SnmpWeakCryptography),
(Flake8Bandit, "601") => (RuleGroup::Stable, rules::flake8_bandit::rules::ParamikoCall),
@@ -662,10 +671,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Bandit, "607") => (RuleGroup::Stable, rules::flake8_bandit::rules::StartProcessWithPartialPath),
(Flake8Bandit, "608") => (RuleGroup::Stable, rules::flake8_bandit::rules::HardcodedSQLExpression),
(Flake8Bandit, "609") => (RuleGroup::Stable, rules::flake8_bandit::rules::UnixCommandWildcardInjection),
(Flake8Bandit, "611") => (RuleGroup::Preview, rules::flake8_bandit::rules::DjangoRawSql),
(Flake8Bandit, "611") => (RuleGroup::Stable, rules::flake8_bandit::rules::DjangoRawSql),
(Flake8Bandit, "612") => (RuleGroup::Stable, rules::flake8_bandit::rules::LoggingConfigInsecureListen),
(Flake8Bandit, "701") => (RuleGroup::Stable, rules::flake8_bandit::rules::Jinja2AutoescapeFalse),
(Flake8Bandit, "702") => (RuleGroup::Preview, rules::flake8_bandit::rules::MakoTemplates),
(Flake8Bandit, "702") => (RuleGroup::Stable, rules::flake8_bandit::rules::MakoTemplates),
// flake8-boolean-trap
(Flake8BooleanTrap, "001") => (RuleGroup::Stable, rules::flake8_boolean_trap::rules::BooleanTypeHintPositionalArgument),
@@ -696,8 +705,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Datetimez, "012") => (RuleGroup::Stable, rules::flake8_datetimez::rules::CallDateFromtimestamp),
// pygrep-hooks
(PygrepHooks, "001") => (RuleGroup::Stable, rules::pygrep_hooks::rules::Eval),
(PygrepHooks, "002") => (RuleGroup::Stable, rules::pygrep_hooks::rules::DeprecatedLogWarn),
(PygrepHooks, "001") => (RuleGroup::Removed, rules::pygrep_hooks::rules::Eval),
(PygrepHooks, "002") => (RuleGroup::Removed, rules::pygrep_hooks::rules::DeprecatedLogWarn),
(PygrepHooks, "003") => (RuleGroup::Stable, rules::pygrep_hooks::rules::BlanketTypeIgnore),
(PygrepHooks, "004") => (RuleGroup::Stable, rules::pygrep_hooks::rules::BlanketNOQA),
(PygrepHooks, "005") => (RuleGroup::Stable, rules::pygrep_hooks::rules::InvalidMockAccess),
@@ -770,7 +779,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Pyi, "053") => (RuleGroup::Stable, rules::flake8_pyi::rules::StringOrBytesTooLong),
(Flake8Pyi, "055") => (RuleGroup::Stable, rules::flake8_pyi::rules::UnnecessaryTypeUnion),
(Flake8Pyi, "056") => (RuleGroup::Stable, rules::flake8_pyi::rules::UnsupportedMethodCallOnAll),
(Flake8Pyi, "058") => (RuleGroup::Preview, rules::flake8_pyi::rules::GeneratorReturnFromIterMethod),
(Flake8Pyi, "058") => (RuleGroup::Stable, rules::flake8_pyi::rules::GeneratorReturnFromIterMethod),
// flake8-pytest-style
(Flake8PytestStyle, "001") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestFixtureIncorrectParenthesesStyle),
@@ -832,13 +841,13 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8TypeChecking, "003") => (RuleGroup::Stable, rules::flake8_type_checking::rules::TypingOnlyStandardLibraryImport),
(Flake8TypeChecking, "004") => (RuleGroup::Stable, rules::flake8_type_checking::rules::RuntimeImportInTypeCheckingBlock),
(Flake8TypeChecking, "005") => (RuleGroup::Stable, rules::flake8_type_checking::rules::EmptyTypeCheckingBlock),
(Flake8TypeChecking, "006") => (RuleGroup::Preview, rules::flake8_type_checking::rules::RuntimeStringUnion),
(Flake8TypeChecking, "010") => (RuleGroup::Stable, rules::flake8_type_checking::rules::RuntimeStringUnion),
// tryceratops
(Tryceratops, "002") => (RuleGroup::Stable, rules::tryceratops::rules::RaiseVanillaClass),
(Tryceratops, "003") => (RuleGroup::Stable, rules::tryceratops::rules::RaiseVanillaArgs),
(Tryceratops, "004") => (RuleGroup::Stable, rules::tryceratops::rules::TypeCheckWithoutTypeError),
(Tryceratops, "200") => (RuleGroup::Stable, rules::tryceratops::rules::ReraiseNoCause),
(Tryceratops, "200") => (RuleGroup::Removed, rules::tryceratops::rules::ReraiseNoCause),
(Tryceratops, "201") => (RuleGroup::Stable, rules::tryceratops::rules::VerboseRaise),
(Tryceratops, "300") => (RuleGroup::Stable, rules::tryceratops::rules::TryConsiderElse),
(Tryceratops, "301") => (RuleGroup::Stable, rules::tryceratops::rules::RaiseWithinTry),
@@ -901,7 +910,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Numpy, "001") => (RuleGroup::Stable, rules::numpy::rules::NumpyDeprecatedTypeAlias),
(Numpy, "002") => (RuleGroup::Stable, rules::numpy::rules::NumpyLegacyRandom),
(Numpy, "003") => (RuleGroup::Stable, rules::numpy::rules::NumpyDeprecatedFunction),
(Numpy, "201") => (RuleGroup::Preview, rules::numpy::rules::Numpy2Deprecation),
(Numpy, "201") => (RuleGroup::Stable, rules::numpy::rules::Numpy2Deprecation),
// ruff
(Ruff, "001") => (RuleGroup::Stable, rules::ruff::rules::AmbiguousUnicodeCharacterString),
@@ -913,20 +922,51 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "008") => (RuleGroup::Stable, rules::ruff::rules::MutableDataclassDefault),
(Ruff, "009") => (RuleGroup::Stable, rules::ruff::rules::FunctionCallInDataclassDefaultArgument),
(Ruff, "010") => (RuleGroup::Stable, rules::ruff::rules::ExplicitFStringTypeConversion),
(Ruff, "011") => (RuleGroup::Stable, rules::ruff::rules::StaticKeyDictComprehension),
(Ruff, "011") => (RuleGroup::Removed, rules::ruff::rules::RuffStaticKeyDictComprehension),
(Ruff, "012") => (RuleGroup::Stable, rules::ruff::rules::MutableClassDefault),
(Ruff, "013") => (RuleGroup::Stable, rules::ruff::rules::ImplicitOptional),
(Ruff, "015") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryIterableAllocationForFirstElement),
(Ruff, "016") => (RuleGroup::Stable, rules::ruff::rules::InvalidIndexType),
#[allow(deprecated)]
(Ruff, "017") => (RuleGroup::Nursery, rules::ruff::rules::QuadraticListSummation),
(Ruff, "018") => (RuleGroup::Preview, rules::ruff::rules::AssignmentInAssert),
(Ruff, "019") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryKeyCheck),
(Ruff, "020") => (RuleGroup::Preview, rules::ruff::rules::NeverUnion),
(Ruff, "017") => (RuleGroup::Stable, rules::ruff::rules::QuadraticListSummation),
(Ruff, "018") => (RuleGroup::Stable, rules::ruff::rules::AssignmentInAssert),
(Ruff, "019") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryKeyCheck),
(Ruff, "020") => (RuleGroup::Stable, rules::ruff::rules::NeverUnion),
(Ruff, "021") => (RuleGroup::Preview, rules::ruff::rules::ParenthesizeChainedOperators),
(Ruff, "022") => (RuleGroup::Preview, rules::ruff::rules::UnsortedDunderAll),
(Ruff, "023") => (RuleGroup::Preview, rules::ruff::rules::UnsortedDunderSlots),
(Ruff, "024") => (RuleGroup::Preview, rules::ruff::rules::MutableFromkeysValue),
(Ruff, "025") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryDictComprehensionForIterable),
(Ruff, "026") => (RuleGroup::Preview, rules::ruff::rules::DefaultFactoryKwarg),
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
(Ruff, "200") => (RuleGroup::Stable, rules::ruff::rules::InvalidPyprojectToml),
#[cfg(feature = "test-rules")]
(Ruff, "900") => (RuleGroup::Stable, rules::ruff::rules::StableTestRule),
#[cfg(feature = "test-rules")]
(Ruff, "901") => (RuleGroup::Stable, rules::ruff::rules::StableTestRuleSafeFix),
#[cfg(feature = "test-rules")]
(Ruff, "902") => (RuleGroup::Stable, rules::ruff::rules::StableTestRuleUnsafeFix),
#[cfg(feature = "test-rules")]
(Ruff, "903") => (RuleGroup::Stable, rules::ruff::rules::StableTestRuleDisplayOnlyFix),
#[cfg(feature = "test-rules")]
(Ruff, "911") => (RuleGroup::Preview, rules::ruff::rules::PreviewTestRule),
#[cfg(feature = "test-rules")]
#[allow(deprecated)]
(Ruff, "912") => (RuleGroup::Nursery, rules::ruff::rules::NurseryTestRule),
#[cfg(feature = "test-rules")]
(Ruff, "920") => (RuleGroup::Deprecated, rules::ruff::rules::DeprecatedTestRule),
#[cfg(feature = "test-rules")]
(Ruff, "921") => (RuleGroup::Deprecated, rules::ruff::rules::AnotherDeprecatedTestRule),
#[cfg(feature = "test-rules")]
(Ruff, "930") => (RuleGroup::Removed, rules::ruff::rules::RemovedTestRule),
#[cfg(feature = "test-rules")]
(Ruff, "931") => (RuleGroup::Removed, rules::ruff::rules::AnotherRemovedTestRule),
#[cfg(feature = "test-rules")]
(Ruff, "940") => (RuleGroup::Removed, rules::ruff::rules::RedirectedFromTestRule),
#[cfg(feature = "test-rules")]
(Ruff, "950") => (RuleGroup::Stable, rules::ruff::rules::RedirectedToTestRule),
#[cfg(feature = "test-rules")]
(Ruff, "960") => (RuleGroup::Removed, rules::ruff::rules::RedirectedFromPrefixTestRule),
// flake8-django
(Flake8Django, "001") => (RuleGroup::Stable, rules::flake8_django::rules::DjangoNullableModelStringField),
@@ -994,13 +1034,14 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Refurb, "169") => (RuleGroup::Preview, rules::refurb::rules::TypeNoneComparison),
(Refurb, "171") => (RuleGroup::Preview, rules::refurb::rules::SingleItemMembershipTest),
(Refurb, "177") => (RuleGroup::Preview, rules::refurb::rules::ImplicitCwd),
(Refurb, "180") => (RuleGroup::Preview, rules::refurb::rules::MetaClassABCMeta),
(Refurb, "181") => (RuleGroup::Preview, rules::refurb::rules::HashlibDigestHex),
// flake8-logging
(Flake8Logging, "001") => (RuleGroup::Preview, rules::flake8_logging::rules::DirectLoggerInstantiation),
(Flake8Logging, "002") => (RuleGroup::Preview, rules::flake8_logging::rules::InvalidGetLoggerArgument),
(Flake8Logging, "007") => (RuleGroup::Preview, rules::flake8_logging::rules::ExceptionWithoutExcInfo),
(Flake8Logging, "009") => (RuleGroup::Preview, rules::flake8_logging::rules::UndocumentedWarn),
(Flake8Logging, "001") => (RuleGroup::Stable, rules::flake8_logging::rules::DirectLoggerInstantiation),
(Flake8Logging, "002") => (RuleGroup::Stable, rules::flake8_logging::rules::InvalidGetLoggerArgument),
(Flake8Logging, "007") => (RuleGroup::Stable, rules::flake8_logging::rules::ExceptionWithoutExcInfo),
(Flake8Logging, "009") => (RuleGroup::Stable, rules::flake8_logging::rules::UndocumentedWarn),
_ => return None,
})

View File

@@ -33,6 +33,8 @@ use crate::message::Message;
use crate::noqa::add_noqa;
use crate::registry::{AsRule, Rule, RuleSet};
use crate::rules::pycodestyle;
#[cfg(feature = "test-rules")]
use crate::rules::ruff::rules::test_rules::{self, TestRule, TEST_RULES};
use crate::settings::types::UnsafeFixes;
use crate::settings::{flags, LinterSettings};
use crate::source_kind::SourceKind;
@@ -214,6 +216,53 @@ pub fn check_path(
));
}
// Raise violations for internal test rules
#[cfg(feature = "test-rules")]
{
for test_rule in TEST_RULES {
if !settings.rules.enabled(*test_rule) {
continue;
}
let diagnostic = match test_rule {
Rule::StableTestRule => test_rules::StableTestRule::diagnostic(locator, indexer),
Rule::StableTestRuleSafeFix => {
test_rules::StableTestRuleSafeFix::diagnostic(locator, indexer)
}
Rule::StableTestRuleUnsafeFix => {
test_rules::StableTestRuleUnsafeFix::diagnostic(locator, indexer)
}
Rule::StableTestRuleDisplayOnlyFix => {
test_rules::StableTestRuleDisplayOnlyFix::diagnostic(locator, indexer)
}
Rule::NurseryTestRule => test_rules::NurseryTestRule::diagnostic(locator, indexer),
Rule::PreviewTestRule => test_rules::PreviewTestRule::diagnostic(locator, indexer),
Rule::DeprecatedTestRule => {
test_rules::DeprecatedTestRule::diagnostic(locator, indexer)
}
Rule::AnotherDeprecatedTestRule => {
test_rules::AnotherDeprecatedTestRule::diagnostic(locator, indexer)
}
Rule::RemovedTestRule => test_rules::RemovedTestRule::diagnostic(locator, indexer),
Rule::AnotherRemovedTestRule => {
test_rules::AnotherRemovedTestRule::diagnostic(locator, indexer)
}
Rule::RedirectedToTestRule => {
test_rules::RedirectedToTestRule::diagnostic(locator, indexer)
}
Rule::RedirectedFromTestRule => {
test_rules::RedirectedFromTestRule::diagnostic(locator, indexer)
}
Rule::RedirectedFromPrefixTestRule => {
test_rules::RedirectedFromPrefixTestRule::diagnostic(locator, indexer)
}
_ => unreachable!("All test rules must have an implementation"),
};
if let Some(diagnostic) = diagnostic {
diagnostics.push(diagnostic);
}
}
}
// Ignore diagnostics based on per-file-ignores.
let per_file_ignores = if !diagnostics.is_empty() && !settings.per_file_ignores.is_empty() {
fs::ignores_from_path(path, &settings.per_file_ignores)
@@ -539,7 +588,7 @@ pub fn lint_fix<'a>(
// Increment the iteration count.
iterations += 1;
// Re-run the linter pass (by avoiding the break).
// Re-run the linter pass (by avoiding the return).
continue;
}
@@ -795,6 +844,23 @@ mod tests {
Ok(())
}
#[test]
fn test_undefined_name() -> Result<(), NotebookError> {
let actual = notebook_path("undefined_name.ipynb");
let expected = notebook_path("undefined_name.ipynb");
let TestedNotebook {
messages,
source_notebook,
..
} = assert_notebook_path(
&actual,
expected,
&settings::LinterSettings::for_rule(Rule::UndefinedName),
)?;
assert_messages!(messages, actual, source_notebook);
Ok(())
}
#[test]
fn test_json_consistency() -> Result<()> {
let actual_path = notebook_path("before_fix.ipynb");

View File

@@ -8,6 +8,7 @@ use fern;
use log::Level;
use once_cell::sync::Lazy;
use ruff_python_parser::{ParseError, ParseErrorType};
use rustc_hash::FxHashSet;
use ruff_source_file::{LineIndex, OneIndexed, SourceCode, SourceLocation};
@@ -15,7 +16,7 @@ use crate::fs;
use crate::source_kind::SourceKind;
use ruff_notebook::Notebook;
pub static WARNINGS: Lazy<Mutex<Vec<&'static str>>> = Lazy::new(Mutex::default);
pub static IDENTIFIERS: Lazy<Mutex<Vec<&'static str>>> = Lazy::new(Mutex::default);
/// Warn a user once, with uniqueness determined by the given ID.
#[macro_export]
@@ -24,7 +25,7 @@ macro_rules! warn_user_once_by_id {
use colored::Colorize;
use log::warn;
if let Ok(mut states) = $crate::logging::WARNINGS.lock() {
if let Ok(mut states) = $crate::logging::IDENTIFIERS.lock() {
if !states.contains(&$id) {
let message = format!("{}", format_args!($($arg)*));
warn!("{}", message.bold());
@@ -34,6 +35,26 @@ macro_rules! warn_user_once_by_id {
};
}
pub static MESSAGES: Lazy<Mutex<FxHashSet<String>>> = Lazy::new(Mutex::default);
/// Warn a user once, if warnings are enabled, with uniqueness determined by the content of the
/// message.
#[macro_export]
macro_rules! warn_user_once_by_message {
($($arg:tt)*) => {
use colored::Colorize;
use log::warn;
if let Ok(mut states) = $crate::logging::MESSAGES.lock() {
let message = format!("{}", format_args!($($arg)*));
if !states.contains(&message) {
warn!("{}", message.bold());
states.insert(message);
}
}
};
}
/// Warn a user once, with uniqueness determined by the calling location itself.
#[macro_export]
macro_rules! warn_user_once {

View File

@@ -98,5 +98,16 @@ static REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
("T002", "FIX002"),
("T003", "FIX003"),
("T004", "FIX004"),
("RUF011", "B035"),
("TCH006", "TCH010"),
("TRY200", "B904"),
("PGH001", "S307"),
("PGH002", "G010"),
// Test redirect by exact code
#[cfg(feature = "test-rules")]
("RUF940", "RUF950"),
// Test redirect by prefix
#[cfg(feature = "test-rules")]
("RUF96", "RUF95"),
])
});

View File

@@ -44,10 +44,25 @@ impl From<Linter> for RuleSelector {
}
}
impl Ord for RuleSelector {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// TODO(zanieb): We ought to put "ALL" and "Linter" selectors
// above those that are rule specific but it's not critical for now
self.prefix_and_code().cmp(&other.prefix_and_code())
}
}
impl PartialOrd for RuleSelector {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl FromStr for RuleSelector {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// **Changes should be reflected in `parse_no_redirect` as well**
match s {
"ALL" => Ok(Self::All),
#[allow(deprecated)]
@@ -67,7 +82,6 @@ impl FromStr for RuleSelector {
return Ok(Self::Linter(linter));
}
// Does the selector select a single rule?
let prefix = RuleCodePrefix::parse(&linter, code)
.map_err(|_| ParseError::Unknown(s.to_string()))?;
@@ -172,7 +186,7 @@ impl Visitor<'_> for SelectorVisitor {
}
impl RuleSelector {
/// Return all matching rules, regardless of whether they're in preview.
/// Return all matching rules, regardless of rule group filters like preview and deprecated.
pub fn all_rules(&self) -> impl Iterator<Item = Rule> + '_ {
match self {
RuleSelector::All => RuleSelectorIter::All(Rule::iter()),
@@ -198,21 +212,30 @@ impl RuleSelector {
}
}
/// Returns rules matching the selector, taking into account preview options enabled.
/// Returns rules matching the selector, taking into account rule groups like preview and deprecated.
pub fn rules<'a>(&'a self, preview: &PreviewOptions) -> impl Iterator<Item = Rule> + 'a {
let preview_enabled = preview.mode.is_enabled();
let preview_require_explicit = preview.require_explicit;
#[allow(deprecated)]
self.all_rules().filter(move |rule| {
// Always include rules that are not in preview or the nursery
!(rule.is_preview() || rule.is_nursery())
// Always include stable rules
rule.is_stable()
// Backwards compatibility allows selection of nursery rules by exact code or dedicated group
|| ((matches!(self, RuleSelector::Rule { .. }) || matches!(self, RuleSelector::Nursery { .. })) && rule.is_nursery())
|| ((self.is_exact() || matches!(self, RuleSelector::Nursery { .. })) && rule.is_nursery())
// Enabling preview includes all preview or nursery rules unless explicit selection
// is turned on
|| (preview_enabled && (matches!(self, RuleSelector::Rule { .. }) || !preview_require_explicit))
|| ((rule.is_preview() || rule.is_nursery()) && preview_enabled && (self.is_exact() || !preview_require_explicit))
// Deprecated rules are excluded in preview mode unless explicitly selected
|| (rule.is_deprecated() && (!preview_enabled || self.is_exact()))
// Removed rules are included if explicitly selected but will error downstream
|| (rule.is_removed() && self.is_exact())
})
}
/// Returns true if this selector is exact i.e. selects a single rule by code
pub fn is_exact(&self) -> bool {
matches!(self, Self::Rule { .. })
}
}
pub enum RuleSelectorIter {
@@ -267,7 +290,6 @@ mod schema {
[
// Include the non-standard "ALL" and "NURSERY" selectors.
"ALL".to_string(),
"NURSERY".to_string(),
// Include the legacy "C" and "T" selectors.
"C".to_string(),
"T".to_string(),
@@ -289,6 +311,16 @@ mod schema {
(!prefix.is_empty()).then(|| prefix.to_string())
})),
)
.filter(|p| {
// Exclude any prefixes where all of the rules are removed
if let Ok(Self::Rule { prefix, .. } | Self::Prefix { prefix, .. }) =
RuleSelector::parse_no_redirect(p)
{
!prefix.rules().all(|rule| rule.is_removed())
} else {
true
}
})
.sorted()
.map(Value::String)
.collect(),
@@ -321,6 +353,41 @@ impl RuleSelector {
}
}
}
/// Parse [`RuleSelector`] from a string; but do not follow redirects.
pub fn parse_no_redirect(s: &str) -> Result<Self, ParseError> {
// **Changes should be reflected in `from_str` as well**
match s {
"ALL" => Ok(Self::All),
#[allow(deprecated)]
"NURSERY" => Ok(Self::Nursery),
"C" => Ok(Self::C),
"T" => Ok(Self::T),
_ => {
let (linter, code) =
Linter::parse_code(s).ok_or_else(|| ParseError::Unknown(s.to_string()))?;
if code.is_empty() {
return Ok(Self::Linter(linter));
}
let prefix = RuleCodePrefix::parse(&linter, code)
.map_err(|_| ParseError::Unknown(s.to_string()))?;
if is_single_rule_selector(&prefix) {
Ok(Self::Rule {
prefix,
redirected_from: None,
})
} else {
Ok(Self::Prefix {
prefix,
redirected_from: None,
})
}
}
}
}
}
#[derive(EnumIter, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]

View File

@@ -1,4 +1,4 @@
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_index::Indexer;
use ruff_source_file::Locator;
@@ -24,20 +24,22 @@ use super::super::detection::comment_contains_code;
/// ```
///
/// ## Options
/// - `task-tags`
/// - `lint.task-tags`
///
/// [#4845]: https://github.com/astral-sh/ruff/issues/4845
#[violation]
pub struct CommentedOutCode;
impl AlwaysFixableViolation for CommentedOutCode {
impl Violation for CommentedOutCode {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::None;
#[derive_message_formats]
fn message(&self) -> String {
format!("Found commented-out code")
}
fn fix_title(&self) -> String {
"Remove commented-out code".to_string()
fn fix_title(&self) -> Option<String> {
Some(format!("Remove commented-out code"))
}
}
@@ -65,7 +67,6 @@ pub(crate) fn commented_out_code(
// Verify that the comment is on its own line, and that it contains code.
if is_standalone_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
let mut diagnostic = Diagnostic::new(CommentedOutCode, *range);
diagnostic.set_fix(Fix::display_only_edit(Edit::range_deletion(
locator.full_lines_range(*range),
)));

View File

@@ -111,6 +111,10 @@ impl Violation for MissingTypeKwargs {
}
}
/// ## Deprecation
/// This rule is commonly disabled because type checkers can infer this type without annotation.
/// It will be removed in a future release.
///
/// ## What it does
/// Checks that instance method `self` arguments have type annotations.
///
@@ -148,6 +152,10 @@ impl Violation for MissingTypeSelf {
}
}
/// ## Deprecation
/// This rule is commonly disabled because type checkers can infer this type without annotation.
/// It will be removed in a future release.
///
/// ## What it does
/// Checks that class method `cls` arguments have type annotations.
///

View File

@@ -1,8 +1,7 @@
use ruff_python_ast::ExprCall;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::CallPath;
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::{analyze, SemanticModel};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@@ -41,37 +40,90 @@ impl Violation for OpenSleepOrSubprocessInAsyncFunction {
}
/// ASYNC101
pub(crate) fn open_sleep_or_subprocess_call(checker: &mut Checker, call: &ExprCall) {
if checker.semantic().in_async_context() {
if checker
.semantic()
.resolve_call_path(call.func.as_ref())
.as_ref()
.is_some_and(is_open_sleep_or_subprocess_call)
{
checker.diagnostics.push(Diagnostic::new(
OpenSleepOrSubprocessInAsyncFunction,
call.func.range(),
));
}
pub(crate) fn open_sleep_or_subprocess_call(checker: &mut Checker, call: &ast::ExprCall) {
if !checker.semantic().in_async_context() {
return;
}
if is_open_sleep_or_subprocess_call(&call.func, checker.semantic())
|| is_open_call_from_pathlib(call.func.as_ref(), checker.semantic())
{
checker.diagnostics.push(Diagnostic::new(
OpenSleepOrSubprocessInAsyncFunction,
call.func.range(),
));
}
}
fn is_open_sleep_or_subprocess_call(call_path: &CallPath) -> bool {
matches!(
call_path.as_slice(),
["", "open"]
| ["time", "sleep"]
| [
"subprocess",
"run"
| "Popen"
| "call"
| "check_call"
| "check_output"
| "getoutput"
| "getstatusoutput"
]
| ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"]
)
/// Returns `true` if the expression resolves to a blocking call, like `time.sleep` or
/// `subprocess.run`.
fn is_open_sleep_or_subprocess_call(func: &Expr, semantic: &SemanticModel) -> bool {
semantic.resolve_call_path(func).is_some_and(|call_path| {
matches!(
call_path.as_slice(),
["", "open"]
| ["time", "sleep"]
| [
"subprocess",
"run"
| "Popen"
| "call"
| "check_call"
| "check_output"
| "getoutput"
| "getstatusoutput"
]
| ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"]
)
})
}
/// Returns `true` if an expression resolves to a call to `pathlib.Path.open`.
fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else {
return false;
};
if attr.as_str() != "open" {
return false;
}
// First: is this an inlined call to `pathlib.Path.open`?
// ```python
// from pathlib import Path
// Path("foo").open()
// ```
if let Expr::Call(call) = value.as_ref() {
let Some(call_path) = semantic.resolve_call_path(call.func.as_ref()) else {
return false;
};
if call_path.as_slice() == ["pathlib", "Path"] {
return true;
}
}
// Second, is this a call to `pathlib.Path.open` via a variable?
// ```python
// from pathlib import Path
// path = Path("foo")
// path.open()
// ```
let Expr::Name(name) = value.as_ref() else {
return false;
};
let Some(binding_id) = semantic.resolve_name(name) else {
return false;
};
let binding = semantic.binding(binding_id);
let Some(Expr::Call(call)) = analyze::typing::find_binding_value(&name.id, binding, semantic)
else {
return false;
};
semantic
.resolve_call_path(call.func.as_ref())
.is_some_and(|call_path| call_path.as_slice() == ["pathlib", "Path"])
}

View File

@@ -1,45 +1,83 @@
---
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
---
ASYNC101.py:7:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
6 | async def foo():
7 | open("foo")
| ^^^^ ASYNC101
|
ASYNC101.py:11:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
ASYNC101.py:10:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
10 | async def foo():
11 | time.sleep(1)
9 | async def func():
10 | open("foo")
| ^^^^ ASYNC101
|
ASYNC101.py:14:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
13 | async def func():
14 | time.sleep(1)
| ^^^^^^^^^^ ASYNC101
|
ASYNC101.py:15:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
ASYNC101.py:18:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
14 | async def foo():
15 | subprocess.run("foo")
17 | async def func():
18 | subprocess.run("foo")
| ^^^^^^^^^^^^^^ ASYNC101
|
ASYNC101.py:19:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
ASYNC101.py:22:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
18 | async def foo():
19 | subprocess.call("foo")
21 | async def func():
22 | subprocess.call("foo")
| ^^^^^^^^^^^^^^^ ASYNC101
|
ASYNC101.py:27:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
ASYNC101.py:30:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
26 | async def foo():
27 | os.wait4(10)
29 | async def func():
30 | os.wait4(10)
| ^^^^^^^^ ASYNC101
|
ASYNC101.py:31:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
ASYNC101.py:34:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
30 | async def foo():
31 | os.wait(12)
33 | async def func():
34 | os.wait(12)
| ^^^^^^^ ASYNC101
|
ASYNC101.py:41:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
40 | async def func():
41 | Path("foo").open() # ASYNC101
| ^^^^^^^^^^^^^^^^ ASYNC101
|
ASYNC101.py:46:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
44 | async def func():
45 | p = Path("foo")
46 | p.open() # ASYNC101
| ^^^^^^ ASYNC101
|
ASYNC101.py:50:10: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
49 | async def func():
50 | with Path("foo").open() as f: # ASYNC101
| ^^^^^^^^^^^^^^^^ ASYNC101
51 | pass
|
ASYNC101.py:58:9: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
57 | async def bar():
58 | p.open() # ASYNC101
| ^^^^^^ ASYNC101
|
ASYNC101.py:64:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
62 | (p1, p2) = (Path("foo"), Path("bar"))
63 |
64 | p1.open() # ASYNC101
| ^^^^^^^ ASYNC101
|

View File

@@ -12,7 +12,7 @@ mod tests {
use crate::assert_messages;
use crate::registry::Rule;
use crate::settings::types::PreviewMode;
use crate::settings::LinterSettings;
use crate::test::test_path;
@@ -34,7 +34,6 @@ mod tests {
#[test_case(Rule::GetAttrWithConstant, Path::new("B009_B010.py"))]
#[test_case(Rule::JumpStatementInFinally, Path::new("B012.py"))]
#[test_case(Rule::LoopVariableOverridesIterator, Path::new("B020.py"))]
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_B008.py"))]
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_1.py"))]
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_2.py"))]
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_3.py"))]
@@ -42,6 +41,7 @@ mod tests {
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_5.py"))]
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_6.py"))]
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_7.py"))]
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_B008.py"))]
#[test_case(Rule::NoExplicitStacklevel, Path::new("B028.py"))]
#[test_case(Rule::RaiseLiteral, Path::new("B016.py"))]
#[test_case(Rule::RaiseWithoutFromInsideExcept, Path::new("B904.py"))]
@@ -50,16 +50,17 @@ mod tests {
#[test_case(Rule::ReuseOfGroupbyGenerator, Path::new("B031.py"))]
#[test_case(Rule::SetAttrWithConstant, Path::new("B009_B010.py"))]
#[test_case(Rule::StarArgUnpackingAfterKeywordArg, Path::new("B026.py"))]
#[test_case(Rule::StaticKeyDictComprehension, Path::new("B035.py"))]
#[test_case(Rule::StripWithMultiCharacters, Path::new("B005.py"))]
#[test_case(Rule::UnaryPrefixIncrementDecrement, Path::new("B002.py"))]
#[test_case(Rule::UnintentionalTypeAnnotation, Path::new("B032.py"))]
#[test_case(Rule::UnreliableCallableCheck, Path::new("B004.py"))]
#[test_case(Rule::UnusedLoopControlVariable, Path::new("B007.py"))]
#[test_case(Rule::UselessComparison, Path::new("B015.py"))]
#[test_case(Rule::UselessComparison, Path::new("B015.ipynb"))]
#[test_case(Rule::UselessComparison, Path::new("B015.py"))]
#[test_case(Rule::UselessContextlibSuppress, Path::new("B022.py"))]
#[test_case(Rule::UselessExpression, Path::new("B018.py"))]
#[test_case(Rule::UselessExpression, Path::new("B018.ipynb"))]
#[test_case(Rule::UselessExpression, Path::new("B018.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
@@ -70,24 +71,6 @@ mod tests {
Ok(())
}
#[test_case(Rule::DuplicateValue, Path::new("B033.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("flake8_bugbear").join(path).as_path(),
&LinterSettings {
preview: PreviewMode::Enabled,
..LinterSettings::for_rule(rule_code)
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
#[test]
fn zip_without_explicit_strict() -> Result<()> {
let snapshot = "B905.py";

View File

@@ -61,11 +61,9 @@ pub(crate) fn duplicate_value(checker: &mut Checker, set: &ast::ExprSet) {
elt.range(),
);
if checker.settings.preview.is_enabled() {
diagnostic.try_set_fix(|| {
remove_member(set, index, checker.locator().contents()).map(Fix::safe_edit)
});
}
diagnostic.try_set_fix(|| {
remove_member(set, index, checker.locator().contents()).map(Fix::safe_edit)
});
checker.diagnostics.push(diagnostic);
}

View File

@@ -23,11 +23,11 @@ use crate::checkers::ast::Checker;
/// calls to the function, which can lead to unexpected behaviour.
///
/// Calls can be marked as an exception to this rule with the
/// [`flake8-bugbear.extend-immutable-calls`] configuration option.
/// [`lint.flake8-bugbear.extend-immutable-calls`] configuration option.
///
/// Arguments with immutable type annotations will be ignored by this rule.
/// Types outside of the standard library can be marked as immutable with the
/// [`flake8-bugbear.extend-immutable-calls`] configuration option as well.
/// [`lint.flake8-bugbear.extend-immutable-calls`] configuration option as well.
///
/// ## Example
/// ```python
@@ -61,7 +61,7 @@ use crate::checkers::ast::Checker;
/// ```
///
/// ## Options
/// - `flake8-bugbear.extend-immutable-calls`
/// - `lint.flake8-bugbear.extend-immutable-calls`
#[violation]
pub struct FunctionCallInDefaultArgument {
name: Option<String>,

View File

@@ -22,6 +22,7 @@ pub(crate) use redundant_tuple_in_exception_handler::*;
pub(crate) use reuse_of_groupby_generator::*;
pub(crate) use setattr_with_constant::*;
pub(crate) use star_arg_unpacking_after_keyword_arg::*;
pub(crate) use static_key_dict_comprehension::*;
pub(crate) use strip_with_multi_characters::*;
pub(crate) use unary_prefix_increment_decrement::*;
pub(crate) use unintentional_type_annotation::*;
@@ -56,6 +57,7 @@ mod redundant_tuple_in_exception_handler;
mod reuse_of_groupby_generator;
mod setattr_with_constant;
mod star_arg_unpacking_after_keyword_arg;
mod static_key_dict_comprehension;
mod strip_with_multi_characters;
mod unary_prefix_increment_decrement;
mod unintentional_type_annotation;

View File

@@ -28,7 +28,7 @@ use crate::checkers::ast::Checker;
///
/// Arguments with immutable type annotations will be ignored by this rule.
/// Types outside of the standard library can be marked as immutable with the
/// [`flake8-bugbear.extend-immutable-calls`] configuration option.
/// [`lint.flake8-bugbear.extend-immutable-calls`] configuration option.
///
/// ## Known problems
/// Mutable argument defaults can be used intentionally to cache computation
@@ -61,7 +61,7 @@ use crate::checkers::ast::Checker;
/// ```
///
/// ## Options
/// - `flake8-bugbear.extend-immutable-calls`
/// - `lint.flake8-bugbear.extend-immutable-calls`
///
/// ## References
/// - [Python documentation: Default Argument Values](https://docs.python.org/3/tutorial/controlflow.html#default-argument-values)

View File

@@ -0,0 +1,91 @@
use rustc_hash::FxHashMap;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::StoredNameFinder;
use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::fix::snippet::SourceCodeSnippet;
/// ## What it does
/// Checks for dictionary comprehensions that use a static key, like a string
/// literal or a variable defined outside the comprehension.
///
/// ## Why is this bad?
/// Using a static key (like a string literal) in a dictionary comprehension
/// is usually a mistake, as it will result in a dictionary with only one key,
/// despite the comprehension iterating over multiple values.
///
/// ## Example
/// ```python
/// data = ["some", "Data"]
/// {"key": value.upper() for value in data}
/// ```
///
/// Use instead:
/// ```python
/// data = ["some", "Data"]
/// {value: value.upper() for value in data}
/// ```
#[violation]
pub struct StaticKeyDictComprehension {
key: SourceCodeSnippet,
}
impl Violation for StaticKeyDictComprehension {
#[derive_message_formats]
fn message(&self) -> String {
let StaticKeyDictComprehension { key } = self;
if let Some(key) = key.full_display() {
format!("Dictionary comprehension uses static key: `{key}`")
} else {
format!("Dictionary comprehension uses static key")
}
}
}
/// RUF011
pub(crate) fn static_key_dict_comprehension(checker: &mut Checker, dict_comp: &ast::ExprDictComp) {
// Collect the bound names in the comprehension's generators.
let names = {
let mut visitor = StoredNameFinder::default();
for generator in &dict_comp.generators {
visitor.visit_comprehension(generator);
}
visitor.names
};
if is_constant(&dict_comp.key, &names) {
checker.diagnostics.push(Diagnostic::new(
StaticKeyDictComprehension {
key: SourceCodeSnippet::from_str(checker.locator().slice(dict_comp.key.as_ref())),
},
dict_comp.key.range(),
));
}
}
/// Returns `true` if the given expression is a constant in the context of the dictionary
/// comprehension.
fn is_constant(key: &Expr, names: &FxHashMap<&str, &ast::ExprName>) -> bool {
match key {
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts.iter().all(|elt| is_constant(elt, names)),
Expr::Name(ast::ExprName { id, .. }) => !names.contains_key(id.as_str()),
Expr::Attribute(ast::ExprAttribute { value, .. }) => is_constant(value, names),
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
is_constant(value, names) && is_constant(slice, names)
}
Expr::BinOp(ast::ExprBinOp { left, right, .. }) => {
is_constant(left, names) && is_constant(right, names)
}
Expr::BoolOp(ast::ExprBoolOp { values, .. }) => {
values.iter().all(|value| is_constant(value, names))
}
Expr::UnaryOp(ast::ExprUnaryOp { operand, .. }) => is_constant(operand, names),
expr if expr.is_literal_expr() => true,
_ => false,
}
}

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
---
B033.py:4:35: B033 Sets should not contain duplicate item `"value1"`
B033.py:4:35: B033 [*] Sets should not contain duplicate item `"value1"`
|
2 | # Errors.
3 | ###
@@ -12,7 +12,17 @@ B033.py:4:35: B033 Sets should not contain duplicate item `"value1"`
|
= help: Remove duplicate item
B033.py:5:21: B033 Sets should not contain duplicate item `1`
Safe fix
1 1 | ###
2 2 | # Errors.
3 3 | ###
4 |-incorrect_set = {"value1", 23, 5, "value1"}
4 |+incorrect_set = {"value1", 23, 5}
5 5 | incorrect_set = {1, 1, 2}
6 6 | incorrect_set_multiline = {
7 7 | "value1",
B033.py:5:21: B033 [*] Sets should not contain duplicate item `1`
|
3 | ###
4 | incorrect_set = {"value1", 23, 5, "value1"}
@@ -23,7 +33,17 @@ B033.py:5:21: B033 Sets should not contain duplicate item `1`
|
= help: Remove duplicate item
B033.py:10:5: B033 Sets should not contain duplicate item `"value1"`
Safe fix
2 2 | # Errors.
3 3 | ###
4 4 | incorrect_set = {"value1", 23, 5, "value1"}
5 |-incorrect_set = {1, 1, 2}
5 |+incorrect_set = {1, 2}
6 6 | incorrect_set_multiline = {
7 7 | "value1",
8 8 | 23,
B033.py:10:5: B033 [*] Sets should not contain duplicate item `"value1"`
|
8 | 23,
9 | 5,
@@ -34,7 +54,16 @@ B033.py:10:5: B033 Sets should not contain duplicate item `"value1"`
|
= help: Remove duplicate item
B033.py:13:21: B033 Sets should not contain duplicate item `1`
Safe fix
7 7 | "value1",
8 8 | 23,
9 9 | 5,
10 |- "value1",
11 10 | # B033
12 11 | }
13 12 | incorrect_set = {1, 1}
B033.py:13:21: B033 [*] Sets should not contain duplicate item `1`
|
11 | # B033
12 | }
@@ -45,7 +74,17 @@ B033.py:13:21: B033 Sets should not contain duplicate item `1`
|
= help: Remove duplicate item
B033.py:14:21: B033 Sets should not contain duplicate item `1`
Safe fix
10 10 | "value1",
11 11 | # B033
12 12 | }
13 |-incorrect_set = {1, 1}
13 |+incorrect_set = {1}
14 14 | incorrect_set = {1, 1,}
15 15 | incorrect_set = {0, 1, 1,}
16 16 | incorrect_set = {0, 1, 1}
B033.py:14:21: B033 [*] Sets should not contain duplicate item `1`
|
12 | }
13 | incorrect_set = {1, 1}
@@ -56,7 +95,17 @@ B033.py:14:21: B033 Sets should not contain duplicate item `1`
|
= help: Remove duplicate item
B033.py:15:24: B033 Sets should not contain duplicate item `1`
Safe fix
11 11 | # B033
12 12 | }
13 13 | incorrect_set = {1, 1}
14 |-incorrect_set = {1, 1,}
14 |+incorrect_set = {1,}
15 15 | incorrect_set = {0, 1, 1,}
16 16 | incorrect_set = {0, 1, 1}
17 17 | incorrect_set = {
B033.py:15:24: B033 [*] Sets should not contain duplicate item `1`
|
13 | incorrect_set = {1, 1}
14 | incorrect_set = {1, 1,}
@@ -67,7 +116,17 @@ B033.py:15:24: B033 Sets should not contain duplicate item `1`
|
= help: Remove duplicate item
B033.py:16:24: B033 Sets should not contain duplicate item `1`
Safe fix
12 12 | }
13 13 | incorrect_set = {1, 1}
14 14 | incorrect_set = {1, 1,}
15 |-incorrect_set = {0, 1, 1,}
15 |+incorrect_set = {0, 1,}
16 16 | incorrect_set = {0, 1, 1}
17 17 | incorrect_set = {
18 18 | 0,
B033.py:16:24: B033 [*] Sets should not contain duplicate item `1`
|
14 | incorrect_set = {1, 1,}
15 | incorrect_set = {0, 1, 1,}
@@ -78,7 +137,17 @@ B033.py:16:24: B033 Sets should not contain duplicate item `1`
|
= help: Remove duplicate item
B033.py:20:5: B033 Sets should not contain duplicate item `1`
Safe fix
13 13 | incorrect_set = {1, 1}
14 14 | incorrect_set = {1, 1,}
15 15 | incorrect_set = {0, 1, 1,}
16 |-incorrect_set = {0, 1, 1}
16 |+incorrect_set = {0, 1}
17 17 | incorrect_set = {
18 18 | 0,
19 19 | 1,
B033.py:20:5: B033 [*] Sets should not contain duplicate item `1`
|
18 | 0,
19 | 1,
@@ -88,4 +157,13 @@ B033.py:20:5: B033 Sets should not contain duplicate item `1`
|
= help: Remove duplicate item
Safe fix
17 17 | incorrect_set = {
18 18 | 0,
19 19 | 1,
20 |- 1,
21 20 | }
22 21 |
23 22 | ###

View File

@@ -1,90 +1,90 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
---
RUF011.py:17:2: RUF011 Dictionary comprehension uses static key: `"key"`
B035.py:17:2: B035 Dictionary comprehension uses static key: `"key"`
|
16 | # Errors
17 | {"key": value.upper() for value in data}
| ^^^^^ RUF011
| ^^^^^ B035
18 | {True: value.upper() for value in data}
19 | {0: value.upper() for value in data}
|
RUF011.py:18:2: RUF011 Dictionary comprehension uses static key: `True`
B035.py:18:2: B035 Dictionary comprehension uses static key: `True`
|
16 | # Errors
17 | {"key": value.upper() for value in data}
18 | {True: value.upper() for value in data}
| ^^^^ RUF011
| ^^^^ B035
19 | {0: value.upper() for value in data}
20 | {(1, "a"): value.upper() for value in data} # Constant tuple
|
RUF011.py:19:2: RUF011 Dictionary comprehension uses static key: `0`
B035.py:19:2: B035 Dictionary comprehension uses static key: `0`
|
17 | {"key": value.upper() for value in data}
18 | {True: value.upper() for value in data}
19 | {0: value.upper() for value in data}
| ^ RUF011
| ^ B035
20 | {(1, "a"): value.upper() for value in data} # Constant tuple
21 | {constant: value.upper() for value in data}
|
RUF011.py:20:2: RUF011 Dictionary comprehension uses static key: `(1, "a")`
B035.py:20:2: B035 Dictionary comprehension uses static key: `(1, "a")`
|
18 | {True: value.upper() for value in data}
19 | {0: value.upper() for value in data}
20 | {(1, "a"): value.upper() for value in data} # Constant tuple
| ^^^^^^^^ RUF011
| ^^^^^^^^ B035
21 | {constant: value.upper() for value in data}
22 | {constant + constant: value.upper() for value in data}
|
RUF011.py:21:2: RUF011 Dictionary comprehension uses static key: `constant`
B035.py:21:2: B035 Dictionary comprehension uses static key: `constant`
|
19 | {0: value.upper() for value in data}
20 | {(1, "a"): value.upper() for value in data} # Constant tuple
21 | {constant: value.upper() for value in data}
| ^^^^^^^^ RUF011
| ^^^^^^^^ B035
22 | {constant + constant: value.upper() for value in data}
23 | {constant.attribute: value.upper() for value in data}
|
RUF011.py:22:2: RUF011 Dictionary comprehension uses static key: `constant + constant`
B035.py:22:2: B035 Dictionary comprehension uses static key: `constant + constant`
|
20 | {(1, "a"): value.upper() for value in data} # Constant tuple
21 | {constant: value.upper() for value in data}
22 | {constant + constant: value.upper() for value in data}
| ^^^^^^^^^^^^^^^^^^^ RUF011
| ^^^^^^^^^^^^^^^^^^^ B035
23 | {constant.attribute: value.upper() for value in data}
24 | {constant[0]: value.upper() for value in data}
|
RUF011.py:23:2: RUF011 Dictionary comprehension uses static key: `constant.attribute`
B035.py:23:2: B035 Dictionary comprehension uses static key: `constant.attribute`
|
21 | {constant: value.upper() for value in data}
22 | {constant + constant: value.upper() for value in data}
23 | {constant.attribute: value.upper() for value in data}
| ^^^^^^^^^^^^^^^^^^ RUF011
| ^^^^^^^^^^^^^^^^^^ B035
24 | {constant[0]: value.upper() for value in data}
25 | {tokens: token for token in tokens}
|
RUF011.py:24:2: RUF011 Dictionary comprehension uses static key: `constant[0]`
B035.py:24:2: B035 Dictionary comprehension uses static key: `constant[0]`
|
22 | {constant + constant: value.upper() for value in data}
23 | {constant.attribute: value.upper() for value in data}
24 | {constant[0]: value.upper() for value in data}
| ^^^^^^^^^^^ RUF011
| ^^^^^^^^^^^ B035
25 | {tokens: token for token in tokens}
|
RUF011.py:25:2: RUF011 Dictionary comprehension uses static key: `tokens`
B035.py:25:2: B035 Dictionary comprehension uses static key: `tokens`
|
23 | {constant.attribute: value.upper() for value in data}
24 | {constant[0]: value.upper() for value in data}
25 | {tokens: token for token in tokens}
| ^^^^^^ RUF011
| ^^^^^^ B035
|

View File

@@ -1,169 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
---
B033.py:4:35: B033 [*] Sets should not contain duplicate item `"value1"`
|
2 | # Errors.
3 | ###
4 | incorrect_set = {"value1", 23, 5, "value1"}
| ^^^^^^^^ B033
5 | incorrect_set = {1, 1, 2}
6 | incorrect_set_multiline = {
|
= help: Remove duplicate item
Safe fix
1 1 | ###
2 2 | # Errors.
3 3 | ###
4 |-incorrect_set = {"value1", 23, 5, "value1"}
4 |+incorrect_set = {"value1", 23, 5}
5 5 | incorrect_set = {1, 1, 2}
6 6 | incorrect_set_multiline = {
7 7 | "value1",
B033.py:5:21: B033 [*] Sets should not contain duplicate item `1`
|
3 | ###
4 | incorrect_set = {"value1", 23, 5, "value1"}
5 | incorrect_set = {1, 1, 2}
| ^ B033
6 | incorrect_set_multiline = {
7 | "value1",
|
= help: Remove duplicate item
Safe fix
2 2 | # Errors.
3 3 | ###
4 4 | incorrect_set = {"value1", 23, 5, "value1"}
5 |-incorrect_set = {1, 1, 2}
5 |+incorrect_set = {1, 2}
6 6 | incorrect_set_multiline = {
7 7 | "value1",
8 8 | 23,
B033.py:10:5: B033 [*] Sets should not contain duplicate item `"value1"`
|
8 | 23,
9 | 5,
10 | "value1",
| ^^^^^^^^ B033
11 | # B033
12 | }
|
= help: Remove duplicate item
Safe fix
7 7 | "value1",
8 8 | 23,
9 9 | 5,
10 |- "value1",
11 10 | # B033
12 11 | }
13 12 | incorrect_set = {1, 1}
B033.py:13:21: B033 [*] Sets should not contain duplicate item `1`
|
11 | # B033
12 | }
13 | incorrect_set = {1, 1}
| ^ B033
14 | incorrect_set = {1, 1,}
15 | incorrect_set = {0, 1, 1,}
|
= help: Remove duplicate item
Safe fix
10 10 | "value1",
11 11 | # B033
12 12 | }
13 |-incorrect_set = {1, 1}
13 |+incorrect_set = {1}
14 14 | incorrect_set = {1, 1,}
15 15 | incorrect_set = {0, 1, 1,}
16 16 | incorrect_set = {0, 1, 1}
B033.py:14:21: B033 [*] Sets should not contain duplicate item `1`
|
12 | }
13 | incorrect_set = {1, 1}
14 | incorrect_set = {1, 1,}
| ^ B033
15 | incorrect_set = {0, 1, 1,}
16 | incorrect_set = {0, 1, 1}
|
= help: Remove duplicate item
Safe fix
11 11 | # B033
12 12 | }
13 13 | incorrect_set = {1, 1}
14 |-incorrect_set = {1, 1,}
14 |+incorrect_set = {1,}
15 15 | incorrect_set = {0, 1, 1,}
16 16 | incorrect_set = {0, 1, 1}
17 17 | incorrect_set = {
B033.py:15:24: B033 [*] Sets should not contain duplicate item `1`
|
13 | incorrect_set = {1, 1}
14 | incorrect_set = {1, 1,}
15 | incorrect_set = {0, 1, 1,}
| ^ B033
16 | incorrect_set = {0, 1, 1}
17 | incorrect_set = {
|
= help: Remove duplicate item
Safe fix
12 12 | }
13 13 | incorrect_set = {1, 1}
14 14 | incorrect_set = {1, 1,}
15 |-incorrect_set = {0, 1, 1,}
15 |+incorrect_set = {0, 1,}
16 16 | incorrect_set = {0, 1, 1}
17 17 | incorrect_set = {
18 18 | 0,
B033.py:16:24: B033 [*] Sets should not contain duplicate item `1`
|
14 | incorrect_set = {1, 1,}
15 | incorrect_set = {0, 1, 1,}
16 | incorrect_set = {0, 1, 1}
| ^ B033
17 | incorrect_set = {
18 | 0,
|
= help: Remove duplicate item
Safe fix
13 13 | incorrect_set = {1, 1}
14 14 | incorrect_set = {1, 1,}
15 15 | incorrect_set = {0, 1, 1,}
16 |-incorrect_set = {0, 1, 1}
16 |+incorrect_set = {0, 1}
17 17 | incorrect_set = {
18 18 | 0,
19 19 | 1,
B033.py:20:5: B033 [*] Sets should not contain duplicate item `1`
|
18 | 0,
19 | 1,
20 | 1,
| ^ B033
21 | }
|
= help: Remove duplicate item
Safe fix
17 17 | incorrect_set = {
18 18 | 0,
19 19 | 1,
20 |- 1,
21 20 | }
22 21 |
23 22 | ###

View File

@@ -19,7 +19,7 @@ use super::super::helpers::shadows_builtin;
/// builtin and vice versa.
///
/// Builtins can be marked as exceptions to this rule via the
/// [`flake8-builtins.builtins-ignorelist`] configuration option.
/// [`lint.flake8-builtins.builtins-ignorelist`] configuration option.
///
/// ## Example
/// ```python
@@ -44,7 +44,7 @@ use super::super::helpers::shadows_builtin;
/// ```
///
/// ## Options
/// - `flake8-builtins.builtins-ignorelist`
/// - `lint.flake8-builtins.builtins-ignorelist`
///
/// ## References
/// - [_Is it bad practice to use a built-in function name as an attribute or method identifier?_](https://stackoverflow.com/questions/9109333/is-it-bad-practice-to-use-a-built-in-function-name-as-an-attribute-or-method-ide)

View File

@@ -37,7 +37,7 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin;
/// ```
///
/// Builtins can be marked as exceptions to this rule via the
/// [`flake8-builtins.builtins-ignorelist`] configuration option, or
/// [`lint.flake8-builtins.builtins-ignorelist`] configuration option, or
/// converted to the appropriate dunder method. Methods decorated with
/// `@typing.override` or `@typing_extensions.override` are also
/// ignored.
@@ -55,7 +55,7 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin;
/// ```
///
/// ## Options
/// - `flake8-builtins.builtins-ignorelist`
/// - `lint.flake8-builtins.builtins-ignorelist`
#[violation]
pub struct BuiltinAttributeShadowing {
kind: Kind,

View File

@@ -18,7 +18,7 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin;
/// builtin and vice versa.
///
/// Builtins can be marked as exceptions to this rule via the
/// [`flake8-builtins.builtins-ignorelist`] configuration option.
/// [`lint.flake8-builtins.builtins-ignorelist`] configuration option.
///
/// ## Example
/// ```python
@@ -41,7 +41,7 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin;
/// ```
///
/// ## Options
/// - `flake8-builtins.builtins-ignorelist`
/// - `lint.flake8-builtins.builtins-ignorelist`
///
/// ## References
/// - [_Why is it a bad idea to name a variable `id` in Python?_](https://stackoverflow.com/questions/77552/id-is-a-bad-variable-name-in-python)

View File

@@ -9,4 +9,3 @@ EXE001_1.py:1:1: EXE001 Shebang is present but file is not executable
3 | if __name__ == '__main__':
|

View File

@@ -7,5 +7,3 @@ EXE002_1.py:1:1: EXE002 The file is executable but no shebang is present
| EXE002
2 | print('I should be executable.')
|

View File

@@ -59,7 +59,7 @@ impl Violation for SingleLineImplicitStringConcatenation {
///
/// By default, this rule will only trigger if the string literal is
/// concatenated via a backslash. To disallow implicit string concatenation
/// altogether, set the [`flake8-implicit-str-concat.allow-multiline`] option
/// altogether, set the [`lint.flake8-implicit-str-concat.allow-multiline`] option
/// to `false`.
///
/// ## Example
@@ -77,7 +77,7 @@ impl Violation for SingleLineImplicitStringConcatenation {
/// ```
///
/// ## Options
/// - `flake8-implicit-str-concat.allow-multiline`
/// - `lint.flake8-implicit-str-concat.allow-multiline`
///
/// [PEP 8]: https://peps.python.org/pep-0008/#maximum-line-length
#[violation]

View File

@@ -29,7 +29,7 @@ use ruff_text_size::Ranged;
/// ```
///
/// ## Options
/// - `flake8-import-conventions.banned-aliases`
/// - `lint.flake8-import-conventions.banned-aliases`
#[violation]
pub struct BannedImportAlias {
name: String,

View File

@@ -30,7 +30,7 @@ use ruff_text_size::Ranged;
/// ```
///
/// ## Options
/// - `flake8-import-conventions.banned-from`
/// - `lint.flake8-import-conventions.banned-from`
#[violation]
pub struct BannedImportFrom {
name: String,

View File

@@ -32,8 +32,8 @@ use crate::renamer::Renamer;
/// ```
///
/// ## Options
/// - `flake8-import-conventions.aliases`
/// - `flake8-import-conventions.extend-aliases`
/// - `lint.flake8-import-conventions.aliases`
/// - `lint.flake8-import-conventions.extend-aliases`
#[violation]
pub struct UnconventionalImportAlias {
name: String,

View File

@@ -30,9 +30,9 @@ use ruff_macros::{derive_message_formats, violation};
/// - Uses of `flask.current_app.logger` (e.g., `from flask import current_app; current_app.logger.info(...)`).
/// - Objects whose name starts with `log` or ends with `logger` or `logging`,
/// when used in the same file in which they are defined (e.g., `logger = logging.getLogger(); logger.info(...)`).
/// - Imported objects marked as loggers via the [`logger-objects`] setting, which can be
/// - Imported objects marked as loggers via the [`lint.logger-objects`] setting, which can be
/// used to enforce these rules against shared logger objects (e.g., `from module import logger; logger.info(...)`,
/// when [`logger-objects`] is set to `["module.logger"]`).
/// when [`lint.logger-objects`] is set to `["module.logger"]`).
///
/// ## Example
/// ```python
@@ -68,7 +68,7 @@ use ruff_macros::{derive_message_formats, violation};
/// ```
///
/// ## Options
/// - `logger-objects`
/// - `lint.logger-objects`
///
/// ## References
/// - [Python documentation: `logging`](https://docs.python.org/3/library/logging.html)
@@ -114,9 +114,9 @@ impl Violation for LoggingStringFormat {
/// - Uses of `flask.current_app.logger` (e.g., `from flask import current_app; current_app.logger.info(...)`).
/// - Objects whose name starts with `log` or ends with `logger` or `logging`,
/// when used in the same file in which they are defined (e.g., `logger = logging.getLogger(); logger.info(...)`).
/// - Imported objects marked as loggers via the [`logger-objects`] setting, which can be
/// - Imported objects marked as loggers via the [`lint.logger-objects`] setting, which can be
/// used to enforce these rules against shared logger objects (e.g., `from module import logger; logger.info(...)`,
/// when [`logger-objects`] is set to `["module.logger"]`).
/// when [`lint.logger-objects`] is set to `["module.logger"]`).
///
/// ## Example
/// ```python
@@ -152,7 +152,7 @@ impl Violation for LoggingStringFormat {
/// ```
///
/// ## Options
/// - `logger-objects`
/// - `lint.logger-objects`
///
/// ## References
/// - [Python documentation: `logging`](https://docs.python.org/3/library/logging.html)
@@ -197,9 +197,9 @@ impl Violation for LoggingPercentFormat {
/// - Uses of `flask.current_app.logger` (e.g., `from flask import current_app; current_app.logger.info(...)`).
/// - Objects whose name starts with `log` or ends with `logger` or `logging`,
/// when used in the same file in which they are defined (e.g., `logger = logging.getLogger(); logger.info(...)`).
/// - Imported objects marked as loggers via the [`logger-objects`] setting, which can be
/// - Imported objects marked as loggers via the [`lint.logger-objects`] setting, which can be
/// used to enforce these rules against shared logger objects (e.g., `from module import logger; logger.info(...)`,
/// when [`logger-objects`] is set to `["module.logger"]`).
/// when [`lint.logger-objects`] is set to `["module.logger"]`).
///
/// ## Example
/// ```python
@@ -235,7 +235,7 @@ impl Violation for LoggingPercentFormat {
/// ```
///
/// ## Options
/// - `logger-objects`
/// - `lint.logger-objects`
///
/// ## References
/// - [Python documentation: `logging`](https://docs.python.org/3/library/logging.html)
@@ -279,9 +279,9 @@ impl Violation for LoggingStringConcat {
/// - Uses of `flask.current_app.logger` (e.g., `from flask import current_app; current_app.logger.info(...)`).
/// - Objects whose name starts with `log` or ends with `logger` or `logging`,
/// when used in the same file in which they are defined (e.g., `logger = logging.getLogger(); logger.info(...)`).
/// - Imported objects marked as loggers via the [`logger-objects`] setting, which can be
/// - Imported objects marked as loggers via the [`lint.logger-objects`] setting, which can be
/// used to enforce these rules against shared logger objects (e.g., `from module import logger; logger.info(...)`,
/// when [`logger-objects`] is set to `["module.logger"]`).
/// when [`lint.logger-objects`] is set to `["module.logger"]`).
///
/// ## Example
/// ```python
@@ -317,7 +317,7 @@ impl Violation for LoggingStringConcat {
/// ```
///
/// ## Options
/// - `logger-objects`
/// - `lint.logger-objects`
///
/// ## References
/// - [Python documentation: `logging`](https://docs.python.org/3/library/logging.html)
@@ -349,9 +349,9 @@ impl Violation for LoggingFString {
/// - Uses of `flask.current_app.logger` (e.g., `from flask import current_app; current_app.logger.info(...)`).
/// - Objects whose name starts with `log` or ends with `logger` or `logging`,
/// when used in the same file in which they are defined (e.g., `logger = logging.getLogger(); logger.info(...)`).
/// - Imported objects marked as loggers via the [`logger-objects`] setting, which can be
/// - Imported objects marked as loggers via the [`lint.logger-objects`] setting, which can be
/// used to enforce these rules against shared logger objects (e.g., `from module import logger; logger.info(...)`,
/// when [`logger-objects`] is set to `["module.logger"]`).
/// when [`lint.logger-objects`] is set to `["module.logger"]`).
///
/// ## Example
/// ```python
@@ -368,7 +368,7 @@ impl Violation for LoggingFString {
/// ```
///
/// ## Options
/// - `logger-objects`
/// - `lint.logger-objects`
///
/// ## References
/// - [Python documentation: `logging.warning`](https://docs.python.org/3/library/logging.html#logging.warning)
@@ -409,9 +409,9 @@ impl AlwaysFixableViolation for LoggingWarn {
/// - Uses of `flask.current_app.logger` (e.g., `from flask import current_app; current_app.logger.info(...)`).
/// - Objects whose name starts with `log` or ends with `logger` or `logging`,
/// when used in the same file in which they are defined (e.g., `logger = logging.getLogger(); logger.info(...)`).
/// - Imported objects marked as loggers via the [`logger-objects`] setting, which can be
/// - Imported objects marked as loggers via the [`lint.logger-objects`] setting, which can be
/// used to enforce these rules against shared logger objects (e.g., `from module import logger; logger.info(...)`,
/// when [`logger-objects`] is set to `["module.logger"]`).
/// when [`lint.logger-objects`] is set to `["module.logger"]`).
///
/// ## Example
/// ```python
@@ -436,7 +436,7 @@ impl AlwaysFixableViolation for LoggingWarn {
/// ```
///
/// ## Options
/// - `logger-objects`
/// - `lint.logger-objects`
///
/// ## References
/// - [Python documentation: LogRecord attributes](https://docs.python.org/3/library/logging.html#logrecord-attributes)
@@ -470,9 +470,9 @@ impl Violation for LoggingExtraAttrClash {
/// - Uses of `flask.current_app.logger` (e.g., `from flask import current_app; current_app.logger.info(...)`).
/// - Objects whose name starts with `log` or ends with `logger` or `logging`,
/// when used in the same file in which they are defined (e.g., `logger = logging.getLogger(); logger.info(...)`).
/// - Imported objects marked as loggers via the [`logger-objects`] setting, which can be
/// - Imported objects marked as loggers via the [`lint.logger-objects`] setting, which can be
/// used to enforce these rules against shared logger objects (e.g., `from module import logger; logger.info(...)`,
/// when [`logger-objects`] is set to `["module.logger"]`).
/// when [`lint.logger-objects`] is set to `["module.logger"]`).
///
/// ## Example
/// ```python
@@ -495,7 +495,7 @@ impl Violation for LoggingExtraAttrClash {
/// ```
///
/// ## Options
/// - `logger-objects`
/// - `lint.logger-objects`
///
/// ## References
/// - [Python documentation: `logging.exception`](https://docs.python.org/3/library/logging.html#logging.exception)
@@ -531,9 +531,9 @@ impl Violation for LoggingExcInfo {
/// - Uses of `flask.current_app.logger` (e.g., `from flask import current_app; current_app.logger.info(...)`).
/// - Objects whose name starts with `log` or ends with `logger` or `logging`,
/// when used in the same file in which they are defined (e.g., `logger = logging.getLogger(); logger.info(...)`).
/// - Imported objects marked as loggers via the [`logger-objects`] setting, which can be
/// - Imported objects marked as loggers via the [`lint.logger-objects`] setting, which can be
/// used to enforce these rules against shared logger objects (e.g., `from module import logger; logger.info(...)`,
/// when [`logger-objects`] is set to `["module.logger"]`).
/// when [`lint.logger-objects`] is set to `["module.logger"]`).
///
/// ## Example
/// ```python
@@ -556,7 +556,7 @@ impl Violation for LoggingExcInfo {
/// ```
///
/// ## Options
/// - `logger-objects`
/// - `lint.logger-objects`
///
/// ## References
/// - [Python documentation: `logging.exception`](https://docs.python.org/3/library/logging.html#logging.exception)

View File

@@ -9,7 +9,6 @@ mod tests {
use test_case::test_case;
use crate::registry::Rule;
use crate::settings::types::PreviewMode;
use crate::test::test_path;
use crate::{assert_messages, settings};
@@ -31,24 +30,4 @@ mod tests {
assert_messages!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::UnnecessaryPlaceholder, Path::new("PIE790.py"))]
#[test_case(Rule::UnnecessarySpread, Path::new("PIE800.py"))]
#[test_case(Rule::ReimplementedContainerBuiltin, Path::new("PIE807.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("flake8_pie").join(path).as_path(),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -3,6 +3,7 @@ use std::iter;
use itertools::Either::{Left, Right};
use ruff_python_semantic::{analyze, SemanticModel};
use ruff_text_size::{Ranged, TextRange};
use ruff_python_ast::{self as ast, Arguments, BoolOp, Expr, ExprContext, Identifier};
@@ -36,6 +37,14 @@ use crate::checkers::ast::Checker;
/// print("Greetings!")
/// ```
///
/// ## Fix safety
/// This rule's fix is unsafe, as in some cases, it will be unable to determine
/// whether the argument to an existing `.startswith` or `.endswith` call is a
/// tuple. For example, given `msg.startswith(x) or msg.startswith(y)`, if `x`
/// or `y` is a tuple, and the semantic model is unable to detect it as such,
/// the rule will suggest `msg.startswith((x, y))`, which will error at
/// runtime.
///
/// ## References
/// - [Python documentation: `str.startswith`](https://docs.python.org/3/library/stdtypes.html#str.startswith)
/// - [Python documentation: `str.endswith`](https://docs.python.org/3/library/stdtypes.html#str.endswith)
@@ -84,10 +93,14 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
continue;
};
if !(args.len() == 1 && keywords.is_empty()) {
if !keywords.is_empty() {
continue;
}
let [arg] = args.as_slice() else {
continue;
};
let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() else {
continue;
};
@@ -99,6 +112,13 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
continue;
};
// If the argument is bound to a tuple, skip it, since we don't want to suggest
// `startswith((x, y))` where `x` or `y` are tuples. (Tuple literals are okay, since we
// inline them below.)
if is_bound_to_tuple(arg, checker.semantic()) {
continue;
}
duplicates
.entry((attr.as_str(), arg_name.as_str()))
.or_insert_with(Vec::new)
@@ -149,7 +169,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
Right(iter::once(*value))
}
})
.map(Clone::clone)
.cloned()
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
@@ -202,3 +222,18 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
}
}
}
/// Returns `true` if the expression definitively resolves to a tuple (e.g., `x` in `x = (1, 2)`).
fn is_bound_to_tuple(arg: &Expr, semantic: &SemanticModel) -> bool {
let Expr::Name(ast::ExprName { id, .. }) = arg else {
return false;
};
let Some(binding_id) = semantic.lookup_symbol(id.as_str()) else {
return false;
};
let binding = semantic.binding(binding_id);
analyze::typing::is_tuple(binding, semantic)
}

Some files were not shown because too many files have changed in this diff Show More