Compare commits

...

60 Commits

Author SHA1 Message Date
Jane Lewis
0334133dbd Use a deprecated output format for the ecosystem check 2024-01-30 09:33:57 -08:00
Zanie Blue
b9b44a1b35 Remove the NURSERY selector from the json schema (#9695) 2024-01-30 16:51:31 +00:00
Zanie Blue
9f5a394c34 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.
2024-01-30 10:44:15 -06:00
Zanie Blue
0d543f0aaa 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.
2024-01-30 09:27:56 -06:00
Jane Lewis
b244178cd0 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.
2024-01-29 16:57:56 -08:00
Charlie Marsh
2dea20f4e9 Remove preview gating for flake8-simplify rules (#9686)
## Summary

Un-gates detecting `dict.get` rewrites in `if` expressions (rather than
just `if` statements).
2024-01-29 15:01:45 -06:00
Charlie Marsh
88522d245f 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-01-29 15:01:45 -06:00
Charlie Marsh
9a1aa29a7b 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-01-29 15:01:45 -06:00
Charlie Marsh
c0e2494b41 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.
2024-01-29 15:01:45 -06:00
Charlie Marsh
826a12c28b 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`
2024-01-29 15:01:45 -06:00
Charlie Marsh
065fa239cd [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-01-29 15:01:45 -06:00
Micha Reiser
269ac46dfe Add deprecation message for top-level lint settings (#9582) 2024-01-29 15:01:45 -06:00
Micha Reiser
b434285c85 Promote lint. settings over top-level settings (#9476) 2024-01-29 15:01:45 -06: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
306 changed files with 11057 additions and 5431 deletions

61
Cargo.lock generated
View File

@@ -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]]
@@ -330,6 +330,7 @@ dependencies = [
"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",
@@ -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",
@@ -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.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23"
checksum = "f5c9fdb6b00a489875b22efd4b78fe2b363b72265cc5f6eb2e2b9ee270e6140c"
dependencies = [
"serde",
"serde_with_macros",
@@ -2722,9 +2723,9 @@ dependencies = [
[[package]]
name = "serde_with_macros"
version = "3.4.0"
version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788"
checksum = "dbff351eb4b33600a2e138dfa0b10b65a238ea8ff8fb2387c422c5022a3e8298"
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",

View File

@@ -20,7 +20,7 @@ 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"] }
chrono = { version = "0.4.33", default-features = false, features = ["clock"] }
clap = { version = "4.4.13", features = ["derive"] }
clap_complete_command = { version = "0.5.1" }
clearscreen = { version = "2.0.0" }
@@ -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"}
@@ -69,7 +69,7 @@ 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" }
@@ -78,13 +78,13 @@ seahash = { version ="4.1.0"}
semver = { version = "1.0.21" }
serde = { version = "1.0.195", 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.5.1", 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

@@ -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 }

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,14 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
printer_flags,
);
let preview = overrides.preview.unwrap_or_default().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 +354,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 +387,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,11 @@ 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 your configuration:
- 'extend-select' -> 'lint.extend-select'
- 'ignore' -> 'lint.ignore'
"###);
Ok(())
}
@@ -546,6 +551,11 @@ 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 your configuration:
- 'extend-select' -> 'lint.extend-select'
- 'ignore' -> 'lint.ignore'
"###);
Ok(())
}

View File

@@ -31,25 +31,14 @@ fn ruff_cmd() -> Command {
}
/// Builder for `ruff check` commands.
#[derive(Debug)]
#[derive(Debug, Default)]
struct RuffCheck<'a> {
output_format: &'a str,
output_format: Option<&'a str>,
config: Option<&'a Path>,
filename: Option<&'a str>,
args: Vec<&'a str>,
}
impl<'a> Default for RuffCheck<'a> {
fn default() -> RuffCheck<'a> {
RuffCheck {
output_format: "text",
config: None,
filename: None,
args: vec![],
}
}
}
impl<'a> RuffCheck<'a> {
/// Set the `--config` option.
#[must_use]
@@ -61,7 +50,7 @@ impl<'a> RuffCheck<'a> {
/// Set the `--output-format` option.
#[must_use]
fn output_format(mut self, format: &'a str) -> Self {
self.output_format = format;
self.output_format = Some(format);
self
}
@@ -82,7 +71,11 @@ impl<'a> RuffCheck<'a> {
/// Generate a [`Command`] for the `ruff check` command.
fn build(self) -> Command {
let mut cmd = ruff_cmd();
cmd.args(["--output-format", self.output_format, "--no-cache"]);
if let Some(output_format) = self.output_format {
cmd.args(["--output-format", output_format]);
}
cmd.arg("--no-cache");
if let Some(path) = self.config {
cmd.arg("--config");
cmd.arg(path);
@@ -743,8 +736,28 @@ fn stdin_parse_error() {
}
#[test]
fn show_source() {
let mut cmd = RuffCheck::default().args(["--show-source"]).build();
fn full_output_preview() {
let mut cmd = RuffCheck::default().args(["--preview"]).build();
assert_cmd_snapshot!(cmd
.pass_stdin("l = 1"), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `l`
|
1 | l = 1
| ^ E741
|
Found 1 error.
----- stderr -----
"###);
}
#[test]
fn full_output_format() {
let mut cmd = RuffCheck::default().output_format("full").build();
assert_cmd_snapshot!(cmd
.pass_stdin("l = 1"), @r###"
success: false
@@ -839,14 +852,12 @@ fn nursery_direct() {
assert_cmd_snapshot!(cmd
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 1
exit_code: 2
----- stdout -----
-:1:2: E225 [*] Missing whitespace around operator
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
warning: Selection of nursery rule `E225` without the `--preview` flag is deprecated.
ruff failed
Cause: Selection of unstable rule `E225` without the `--preview` flag is not allowed.
"###);
}
@@ -857,15 +868,12 @@ fn nursery_group_selector() {
assert_cmd_snapshot!(cmd
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 1
exit_code: 2
----- stdout -----
-:1:1: CPY001 Missing copyright notice at top of file
-:1:2: E225 [*] Missing whitespace around operator
Found 2 errors.
[*] 1 fixable with the `--fix` option.
----- stderr -----
warning: The `NURSERY` selector has been deprecated. Use the `--preview` flag instead.
ruff failed
Cause: The `NURSERY` selector was removed. Use the `--preview` flag instead.
"###);
}
@@ -878,15 +886,12 @@ fn nursery_group_selector_preview_enabled() {
assert_cmd_snapshot!(cmd
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 1
exit_code: 2
----- stdout -----
-:1:1: CPY001 Missing copyright notice at top of file
-:1:2: E225 [*] Missing whitespace around operator
Found 2 errors.
[*] 1 fixable with the `--fix` option.
----- stderr -----
warning: The `NURSERY` selector has been deprecated.
ruff failed
Cause: The `NURSERY` selector was removed. Unstable rules should be selected individually or by their respective groups.
"###);
}
@@ -902,7 +907,18 @@ fn preview_enabled_prefix() {
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `I`
|
1 | I=42
| ^ E741
|
-:1:2: E225 [*] Missing whitespace around operator
|
1 | I=42
| ^ E225
|
= help: Add missing whitespace
Found 2 errors.
[*] 1 fixable with the `--fix` option.
@@ -921,9 +937,30 @@ fn preview_enabled_all() {
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `I`
|
1 | I=42
| ^ E741
|
-:1:1: D100 Missing docstring in public module
|
1 | I=42
| D100
|
-:1:1: CPY001 Missing copyright notice at top of file
|
1 | I=42
| CPY001
|
-:1:2: E225 [*] Missing whitespace around operator
|
1 | I=42
| ^ E225
|
= help: Add missing whitespace
Found 4 errors.
[*] 1 fixable with the `--fix` option.
@@ -945,6 +982,12 @@ fn preview_enabled_direct() {
exit_code: 1
----- stdout -----
-:1:2: E225 [*] Missing whitespace around operator
|
1 | I=42
| ^ E225
|
= help: Add missing whitespace
Found 1 error.
[*] 1 fixable with the `--fix` option.

View File

@@ -11,7 +11,7 @@ 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"];
#[test]
fn top_level_options() -> Result<()> {
@@ -44,6 +44,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 your configuration:
- 'extend-select' -> 'lint.extend-select'
- 'flake8-quotes' -> 'lint.flake8-quotes'
"###);
Ok(())
}
@@ -114,6 +119,10 @@ 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 your configuration:
- 'extend-select' -> 'lint.extend-select'
"###);
Ok(())
}
@@ -153,6 +162,10 @@ 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 your configuration:
- 'flake8-quotes' -> 'lint.flake8-quotes'
"###);
Ok(())
}
@@ -228,6 +241,10 @@ 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 your configuration:
- 'extend-select' -> 'lint.extend-select'
"###);
Ok(())
}
@@ -271,6 +288,10 @@ 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 your configuration:
- 'extend-select' -> 'lint.extend-select'
"###);
Ok(())
}
@@ -309,6 +330,11 @@ _ = "---------------------------------------------------------------------------
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 your configuration:
- 'select' -> 'lint.select'
- 'pycodestyle' -> 'lint.pycodestyle'
"###);
Ok(())
}
@@ -351,6 +377,10 @@ 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 your configuration:
- 'extend-select' -> 'lint.extend-select'
"###);
Ok(())
}
@@ -393,6 +423,10 @@ 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 your configuration:
- 'extend-select' -> 'lint.extend-select'
"###);
Ok(())
}

View File

@@ -39,6 +39,10 @@ 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 your configuration:
- '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

@@ -116,7 +116,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 +142,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 +157,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

@@ -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

@@ -131,6 +131,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:

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,23 @@ 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

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,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

@@ -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

@@ -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

@@ -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

@@ -306,3 +306,5 @@ __all__ = (
__all__ = [
"foo", (), "bar"
]
__all__ = "foo", "an" "implicitly_concatenated_second_item", not_a_string_literal

View File

@@ -0,0 +1,234 @@
#########################
# Single-line definitions
#########################
class Klass:
__slots__ = ["d", "c", "b", "a"] # a comment that is untouched
__match_args__ = ("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)
__match_args__: 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)
__match_args__: 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"
)
__match_args__ = [
"d",
"c", # a comment regarding 'c'
"b",
# a comment regarding 'a':
"a"
]
##########################################
# Messier multiline __all__ definitions...
##########################################
class Klass4:
# comment0
__slots__ = ("d", "a", # comment1
# comment2
"f", "b",
"strangely", # comment3
# comment4
"formatted",
# comment5
) # comment6
# comment7
__match_args__ = [ # 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:
__match_args__ = (
"look",
(
"a_veeeeeeeeeeeeeeeeeeery_long_parenthesized_item"
),
)
__slots__ = (
"b",
((
"c"
)),
"a"
)
__slots__ = ("don't" "care" "about", "__slots__" "with", "concatenated" "strings")
###################################
# These should all not get flagged:
###################################
class Klass6:
__slots__ = ()
__match_args__ = []
__slots__ = ("single_item",)
__match_args__ = (
"single_item_multiline",
)
__slots__ = {"single_item",}
__slots__ = {"single_item_no_trailing_comma": "docs for that"}
__match_args__ = [
"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"]
__match_args__ = ["foo", "bar", "antipasti"]
class Klass6:
__slots__ = (9, 8, 7)
__match_args__ = ( # 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__ = [
()
]
__match_args__ = (
()
)
__match_args__ = (
[]
)
__slots__ = (
(),
)
__slots__ = (
[],
)
__match_args__ = (
"foo", [], "bar"
)
__match_args__ = [
"foo", (), "bar"
]
__match_args__ = {"a", "set", "for", "__match_args__", "is invalid"}
__match_args__ = {"this": "is", "also": "invalid"}

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

@@ -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

@@ -125,6 +125,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);
}
@@ -974,9 +977,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 +1426,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 +1446,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(

View File

@@ -507,6 +507,9 @@ 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);
}
@@ -1069,13 +1072,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 +1092,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 +1117,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 +1208,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 +1238,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 +1264,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 +1331,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 +1468,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 +1544,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;
}

View File

@@ -224,11 +224,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,6 +261,7 @@ 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),
@@ -348,6 +351,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),
@@ -913,7 +917,6 @@ 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, "012") => (RuleGroup::Stable, rules::ruff::rules::MutableClassDefault),
(Ruff, "013") => (RuleGroup::Stable, rules::ruff::rules::ImplicitOptional),
(Ruff, "015") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryIterableAllocationForFirstElement),
@@ -925,6 +928,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "020") => (RuleGroup::Preview, 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),

View File

@@ -795,6 +795,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

@@ -98,5 +98,6 @@ static REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
("T002", "FIX002"),
("T003", "FIX003"),
("T004", "FIX004"),
("RUF011", "B035"),
])
});

View File

@@ -267,7 +267,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(),

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

@@ -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

@@ -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)
}

View File

@@ -8,10 +8,7 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for lambdas that can be replaced with the `list` builtin.
///
/// In [preview], this rule will also flag lambdas that can be replaced with
/// the `dict` builtin.
/// Checks for lambdas that can be replaced with the `list` or `dict` builtins.
///
/// ## Why is this bad?
/// Using container builtins are more succinct and idiomatic than wrapping
@@ -40,8 +37,6 @@ use crate::checkers::ast::Checker;
///
/// ## References
/// - [Python documentation: `list`](https://docs.python.org/3/library/functions.html#func-list)
///
/// [preview]: https://docs.astral.sh/ruff/preview/
#[violation]
pub struct ReimplementedContainerBuiltin {
container: Container,
@@ -73,11 +68,7 @@ pub(crate) fn reimplemented_container_builtin(checker: &mut Checker, expr: &Expr
if parameters.is_none() {
let container = match body.as_ref() {
Expr::List(ast::ExprList { elts, .. }) if elts.is_empty() => Some(Container::List),
Expr::Dict(ast::ExprDict { values, .. })
if values.is_empty() & checker.settings.preview.is_enabled() =>
{
Some(Container::Dict)
}
Expr::Dict(ast::ExprDict { values, .. }) if values.is_empty() => Some(Container::Dict),
_ => None,
};
if let Some(container) = container {

View File

@@ -10,11 +10,8 @@ use crate::checkers::ast::Checker;
use crate::fix;
/// ## What it does
/// Checks for unnecessary `pass` statements in functions, classes, and other
/// blocks.
///
/// In [preview], this rule also checks for unnecessary ellipsis (`...`)
/// literals.
/// Checks for unnecessary `pass` statements and ellipsis (`...`) literals in
/// functions, classes, and other blocks.
///
/// ## Why is this bad?
/// In Python, the `pass` statement and ellipsis (`...`) literal serve as
@@ -40,7 +37,7 @@ use crate::fix;
/// """Placeholder docstring."""
/// ```
///
/// In [preview]:
/// Or, given:
/// ```python
/// def func():
/// """Placeholder docstring."""
@@ -55,8 +52,6 @@ use crate::fix;
///
/// ## References
/// - [Python documentation: The `pass` statement](https://docs.python.org/3/reference/simple_stmts.html#the-pass-statement)
///
/// [preview]: https://docs.astral.sh/ruff/preview/
#[violation]
pub struct UnnecessaryPlaceholder {
kind: Placeholder,
@@ -90,10 +85,7 @@ pub(crate) fn unnecessary_placeholder(checker: &mut Checker, body: &[Stmt]) {
for stmt in body {
let kind = match stmt {
Stmt::Pass(_) => Placeholder::Pass,
Stmt::Expr(expr)
if expr.value.is_ellipsis_literal_expr()
&& checker.settings.preview.is_enabled() =>
{
Stmt::Expr(expr) if expr.value.is_ellipsis_literal_expr() => {
// Ellipses are significant in protocol methods and abstract methods. Specifically,
// Pyright uses the presence of an ellipsis to indicate that a method is a stub,
// rather than a default implementation.

View File

@@ -55,10 +55,8 @@ pub(crate) fn unnecessary_spread(checker: &mut Checker, dict: &ast::ExprDict) {
// inside a dict.
if let Expr::Dict(inner) = value {
let mut diagnostic = Diagnostic::new(UnnecessarySpread, value.range());
if checker.settings.preview.is_enabled() {
if let Some(fix) = unnecessary_spread_fix(inner, prev_end, checker.locator()) {
diagnostic.set_fix(fix);
}
if let Some(fix) = unnecessary_spread_fix(inner, prev_end, checker.locator()) {
diagnostic.set_fix(fix);
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -467,6 +467,176 @@ PIE790.py:150:5: PIE790 [*] Unnecessary `pass` statement
152 151 |
153 152 | def foo():
PIE790.py:155:5: PIE790 [*] Unnecessary `...` literal
|
153 | def foo():
154 | print("foo")
155 | ...
| ^^^ PIE790
|
= help: Remove unnecessary `...`
Safe fix
152 152 |
153 153 | def foo():
154 154 | print("foo")
155 |- ...
156 155 |
157 156 |
158 157 | def foo():
PIE790.py:161:5: PIE790 [*] Unnecessary `...` literal
|
159 | """A docstring."""
160 | print("foo")
161 | ...
| ^^^ PIE790
|
= help: Remove unnecessary `...`
Safe fix
158 158 | def foo():
159 159 | """A docstring."""
160 160 | print("foo")
161 |- ...
162 161 |
163 162 |
164 163 | for i in range(10):
PIE790.py:165:5: PIE790 [*] Unnecessary `...` literal
|
164 | for i in range(10):
165 | ...
| ^^^ PIE790
166 | ...
|
= help: Remove unnecessary `...`
Safe fix
163 163 |
164 164 | for i in range(10):
165 165 | ...
166 |- ...
167 166 |
168 167 | for i in range(10):
169 168 | ...
PIE790.py:166:5: PIE790 [*] Unnecessary `...` literal
|
164 | for i in range(10):
165 | ...
166 | ...
| ^^^ PIE790
167 |
168 | for i in range(10):
|
= help: Remove unnecessary `...`
Safe fix
163 163 |
164 164 | for i in range(10):
165 165 | ...
166 |- ...
167 166 |
168 167 | for i in range(10):
169 168 | ...
PIE790.py:169:5: PIE790 [*] Unnecessary `...` literal
|
168 | for i in range(10):
169 | ...
| ^^^ PIE790
170 |
171 | ...
|
= help: Remove unnecessary `...`
Safe fix
166 166 | ...
167 167 |
168 168 | for i in range(10):
169 |- ...
170 169 |
171 170 | ...
172 171 |
PIE790.py:171:5: PIE790 [*] Unnecessary `...` literal
|
169 | ...
170 |
171 | ...
| ^^^ PIE790
172 |
173 | for i in range(10):
|
= help: Remove unnecessary `...`
Safe fix
168 168 | for i in range(10):
169 169 | ...
170 170 |
171 |- ...
172 171 |
173 172 | for i in range(10):
174 173 | ... # comment
PIE790.py:174:5: PIE790 [*] Unnecessary `...` literal
|
173 | for i in range(10):
174 | ... # comment
| ^^^ PIE790
175 | ...
|
= help: Remove unnecessary `...`
Safe fix
171 171 | ...
172 172 |
173 173 | for i in range(10):
174 |- ... # comment
174 |+ # comment
175 175 | ...
176 176 |
177 177 | for i in range(10):
PIE790.py:175:5: PIE790 [*] Unnecessary `...` literal
|
173 | for i in range(10):
174 | ... # comment
175 | ...
| ^^^ PIE790
176 |
177 | for i in range(10):
|
= help: Remove unnecessary `...`
Safe fix
172 172 |
173 173 | for i in range(10):
174 174 | ... # comment
175 |- ...
176 175 |
177 176 | for i in range(10):
178 177 | ...
PIE790.py:178:5: PIE790 [*] Unnecessary `...` literal
|
177 | for i in range(10):
178 | ...
| ^^^ PIE790
179 | pass
|
= help: Remove unnecessary `...`
Safe fix
175 175 | ...
176 176 |
177 177 | for i in range(10):
178 |- ...
179 178 | pass
180 179 |
181 180 | from typing import Protocol
PIE790.py:179:5: PIE790 [*] Unnecessary `pass` statement
|
177 | for i in range(10):
@@ -487,4 +657,19 @@ PIE790.py:179:5: PIE790 [*] Unnecessary `pass` statement
181 180 | from typing import Protocol
182 181 |
PIE790.py:209:9: PIE790 [*] Unnecessary `...` literal
|
207 | def stub(self) -> str:
208 | """Docstring"""
209 | ...
| ^^^ PIE790
|
= help: Remove unnecessary `...`
Safe fix
206 206 |
207 207 | def stub(self) -> str:
208 208 | """Docstring"""
209 |- ...

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_pie/mod.rs
---
PIE800.py:1:14: PIE800 Unnecessary spread `**`
PIE800.py:1:14: PIE800 [*] Unnecessary spread `**`
|
1 | {"foo": 1, **{"bar": 1}} # PIE800
| ^^^^^^^^^^ PIE800
@@ -10,7 +10,14 @@ PIE800.py:1:14: PIE800 Unnecessary spread `**`
|
= help: Remove unnecessary dict
PIE800.py:3:4: PIE800 Unnecessary spread `**`
Safe fix
1 |-{"foo": 1, **{"bar": 1}} # PIE800
1 |+{"foo": 1, "bar": 1} # PIE800
2 2 |
3 3 | {**{"bar": 10}, "a": "b"} # PIE800
4 4 |
PIE800.py:3:4: PIE800 [*] Unnecessary spread `**`
|
1 | {"foo": 1, **{"bar": 1}} # PIE800
2 |
@@ -21,7 +28,16 @@ PIE800.py:3:4: PIE800 Unnecessary spread `**`
|
= help: Remove unnecessary dict
PIE800.py:5:15: PIE800 Unnecessary spread `**`
Safe fix
1 1 | {"foo": 1, **{"bar": 1}} # PIE800
2 2 |
3 |-{**{"bar": 10}, "a": "b"} # PIE800
3 |+{"bar": 10, "a": "b"} # PIE800
4 4 |
5 5 | foo({**foo, **{"bar": True}}) # PIE800
6 6 |
PIE800.py:5:15: PIE800 [*] Unnecessary spread `**`
|
3 | {**{"bar": 10}, "a": "b"} # PIE800
4 |
@@ -32,7 +48,17 @@ PIE800.py:5:15: PIE800 Unnecessary spread `**`
|
= help: Remove unnecessary dict
PIE800.py:7:11: PIE800 Unnecessary spread `**`
Safe fix
2 2 |
3 3 | {**{"bar": 10}, "a": "b"} # PIE800
4 4 |
5 |-foo({**foo, **{"bar": True}}) # PIE800
5 |+foo({**foo, "bar": True}) # PIE800
6 6 |
7 7 | {**foo, **{"bar": 10}} # PIE800
8 8 |
PIE800.py:7:11: PIE800 [*] Unnecessary spread `**`
|
5 | foo({**foo, **{"bar": True}}) # PIE800
6 |
@@ -43,7 +69,17 @@ PIE800.py:7:11: PIE800 Unnecessary spread `**`
|
= help: Remove unnecessary dict
PIE800.py:12:7: PIE800 Unnecessary spread `**`
Safe fix
4 4 |
5 5 | foo({**foo, **{"bar": True}}) # PIE800
6 6 |
7 |-{**foo, **{"bar": 10}} # PIE800
7 |+{**foo, "bar": 10} # PIE800
8 8 |
9 9 | { # PIE800
10 10 | "a": "b",
PIE800.py:12:7: PIE800 [*] Unnecessary spread `**`
|
10 | "a": "b",
11 | # Preserve
@@ -58,7 +94,23 @@ PIE800.py:12:7: PIE800 Unnecessary spread `**`
|
= help: Remove unnecessary dict
PIE800.py:19:19: PIE800 Unnecessary spread `**`
Safe fix
9 9 | { # PIE800
10 10 | "a": "b",
11 11 | # Preserve
12 |- **{
12 |+
13 13 | # all
14 |- "bar": 10, # the
14 |+ "bar": 10 # the
15 15 | # comments
16 |- },
16 |+ ,
17 17 | }
18 18 |
19 19 | {**foo, **buzz, **{bar: 10}} # PIE800
PIE800.py:19:19: PIE800 [*] Unnecessary spread `**`
|
17 | }
18 |
@@ -69,4 +121,14 @@ PIE800.py:19:19: PIE800 Unnecessary spread `**`
|
= help: Remove unnecessary dict
Safe fix
16 16 | },
17 17 | }
18 18 |
19 |-{**foo, **buzz, **{bar: 10}} # PIE800
19 |+{**foo, **buzz, bar: 10} # PIE800
20 20 |
21 21 | {**foo, "bar": True } # OK
22 22 |

View File

@@ -20,6 +20,25 @@ PIE807.py:3:44: PIE807 [*] Prefer `list` over useless lambda
5 5 |
6 6 |
PIE807.py:4:49: PIE807 [*] Prefer `dict` over useless lambda
|
2 | class Foo:
3 | foo: List[str] = field(default_factory=lambda: []) # PIE807
4 | bar: Dict[str, int] = field(default_factory=lambda: {}) # PIE807
| ^^^^^^^^^^ PIE807
|
= help: Replace with `lambda` with `dict`
Safe fix
1 1 | @dataclass
2 2 | class Foo:
3 3 | foo: List[str] = field(default_factory=lambda: []) # PIE807
4 |- bar: Dict[str, int] = field(default_factory=lambda: {}) # PIE807
4 |+ bar: Dict[str, int] = field(default_factory=dict) # PIE807
5 5 |
6 6 |
7 7 | class FooTable(BaseTable):
PIE807.py:8:36: PIE807 [*] Prefer `list` over useless lambda
|
7 | class FooTable(BaseTable):
@@ -39,6 +58,25 @@ PIE807.py:8:36: PIE807 [*] Prefer `list` over useless lambda
10 10 |
11 11 |
PIE807.py:9:36: PIE807 [*] Prefer `dict` over useless lambda
|
7 | class FooTable(BaseTable):
8 | foo = fields.ListField(default=lambda: []) # PIE807
9 | bar = fields.ListField(default=lambda: {}) # PIE807
| ^^^^^^^^^^ PIE807
|
= help: Replace with `lambda` with `dict`
Safe fix
6 6 |
7 7 | class FooTable(BaseTable):
8 8 | foo = fields.ListField(default=lambda: []) # PIE807
9 |- bar = fields.ListField(default=lambda: {}) # PIE807
9 |+ bar = fields.ListField(default=dict) # PIE807
10 10 |
11 11 |
12 12 | class FooTable(BaseTable):
PIE807.py:13:28: PIE807 [*] Prefer `list` over useless lambda
|
12 | class FooTable(BaseTable):
@@ -58,4 +96,23 @@ PIE807.py:13:28: PIE807 [*] Prefer `list` over useless lambda
15 15 |
16 16 |
PIE807.py:14:36: PIE807 [*] Prefer `dict` over useless lambda
|
12 | class FooTable(BaseTable):
13 | foo = fields.ListField(lambda: []) # PIE807
14 | bar = fields.ListField(default=lambda: {}) # PIE807
| ^^^^^^^^^^ PIE807
|
= help: Replace with `lambda` with `dict`
Safe fix
11 11 |
12 12 | class FooTable(BaseTable):
13 13 | foo = fields.ListField(lambda: []) # PIE807
14 |- bar = fields.ListField(default=lambda: {}) # PIE807
14 |+ bar = fields.ListField(default=dict) # PIE807
15 15 |
16 16 |
17 17 | @dataclass

View File

@@ -89,7 +89,7 @@ PIE810.py:10:1: PIE810 [*] Call `startswith` once with a `tuple`
10 | obj.endswith(foo) or obj.startswith(foo) or obj.startswith("foo")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PIE810
11 |
12 | # ok
12 | def func():
|
= help: Merge into a single `startswith` call
@@ -100,7 +100,47 @@ PIE810.py:10:1: PIE810 [*] Call `startswith` once with a `tuple`
10 |-obj.endswith(foo) or obj.startswith(foo) or obj.startswith("foo")
10 |+obj.endswith(foo) or obj.startswith((foo, "foo"))
11 11 |
12 12 | # ok
13 13 | obj.startswith(("foo", "bar"))
12 12 | def func():
13 13 | msg = "hello world"
PIE810.py:19:8: PIE810 [*] Call `startswith` once with a `tuple`
|
17 | z = "w"
18 |
19 | if msg.startswith(x) or msg.startswith(y) or msg.startswith(z): # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PIE810
20 | print("yes")
|
= help: Merge into a single `startswith` call
Unsafe fix
16 16 | y = ("h", "e", "l", "l", "o")
17 17 | z = "w"
18 18 |
19 |- if msg.startswith(x) or msg.startswith(y) or msg.startswith(z): # Error
19 |+ if msg.startswith((x, z)) or msg.startswith(y): # Error
20 20 | print("yes")
21 21 |
22 22 | def func():
PIE810.py:25:8: PIE810 [*] Call `startswith` once with a `tuple`
|
23 | msg = "hello world"
24 |
25 | if msg.startswith(("h", "e", "l", "l", "o")) or msg.startswith("h") or msg.startswith("w"): # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PIE810
26 | print("yes")
|
= help: Merge into a single `startswith` call
Unsafe fix
22 22 | def func():
23 23 | msg = "hello world"
24 24 |
25 |- if msg.startswith(("h", "e", "l", "l", "o")) or msg.startswith("h") or msg.startswith("w"): # Error
25 |+ if msg.startswith(("h", "e", "l", "l", "o", "h", "w")): # Error
26 26 | print("yes")
27 27 |
28 28 | # ok

View File

@@ -1,675 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_pie/mod.rs
---
PIE790.py:4:5: PIE790 [*] Unnecessary `pass` statement
|
2 | """buzz"""
3 |
4 | pass
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
1 1 | class Foo:
2 2 | """buzz"""
3 3 |
4 |- pass
5 4 |
6 5 |
7 6 | if foo:
PIE790.py:9:5: PIE790 [*] Unnecessary `pass` statement
|
7 | if foo:
8 | """foo"""
9 | pass
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
6 6 |
7 7 | if foo:
8 8 | """foo"""
9 |- pass
10 9 |
11 10 |
12 11 | def multi_statement() -> None:
PIE790.py:14:5: PIE790 [*] Unnecessary `pass` statement
|
12 | def multi_statement() -> None:
13 | """This is a function."""
14 | pass; print("hello")
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
11 11 |
12 12 | def multi_statement() -> None:
13 13 | """This is a function."""
14 |- pass; print("hello")
14 |+ print("hello")
15 15 |
16 16 |
17 17 | if foo:
PIE790.py:21:5: PIE790 [*] Unnecessary `pass` statement
|
19 | else:
20 | """bar"""
21 | pass
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
18 18 | pass
19 19 | else:
20 20 | """bar"""
21 |- pass
22 21 |
23 22 |
24 23 | while True:
PIE790.py:28:5: PIE790 [*] Unnecessary `pass` statement
|
26 | else:
27 | """bar"""
28 | pass
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
25 25 | pass
26 26 | else:
27 27 | """bar"""
28 |- pass
29 28 |
30 29 |
31 30 | for _ in range(10):
PIE790.py:35:5: PIE790 [*] Unnecessary `pass` statement
|
33 | else:
34 | """bar"""
35 | pass
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
32 32 | pass
33 33 | else:
34 34 | """bar"""
35 |- pass
36 35 |
37 36 |
38 37 | async for _ in range(10):
PIE790.py:42:5: PIE790 [*] Unnecessary `pass` statement
|
40 | else:
41 | """bar"""
42 | pass
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
39 39 | pass
40 40 | else:
41 41 | """bar"""
42 |- pass
43 42 |
44 43 |
45 44 | def foo() -> None:
PIE790.py:50:5: PIE790 [*] Unnecessary `pass` statement
|
48 | """
49 |
50 | pass
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
47 47 | buzz
48 48 | """
49 49 |
50 |- pass
51 50 |
52 51 |
53 52 | async def foo():
PIE790.py:58:5: PIE790 [*] Unnecessary `pass` statement
|
56 | """
57 |
58 | pass
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
55 55 | buzz
56 56 | """
57 57 |
58 |- pass
59 58 |
60 59 |
61 60 | try:
PIE790.py:65:5: PIE790 [*] Unnecessary `pass` statement
|
63 | buzz
64 | """
65 | pass
| ^^^^ PIE790
66 | except ValueError:
67 | pass
|
= help: Remove unnecessary `pass`
Safe fix
62 62 | """
63 63 | buzz
64 64 | """
65 |- pass
66 65 | except ValueError:
67 66 | pass
68 67 |
PIE790.py:74:5: PIE790 [*] Unnecessary `pass` statement
|
72 | except ValueError:
73 | """bar"""
74 | pass
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
71 71 | bar()
72 72 | except ValueError:
73 73 | """bar"""
74 |- pass
75 74 |
76 75 |
77 76 | for _ in range(10):
PIE790.py:79:5: PIE790 [*] Unnecessary `pass` statement
|
77 | for _ in range(10):
78 | """buzz"""
79 | pass
| ^^^^ PIE790
80 |
81 | async for _ in range(10):
|
= help: Remove unnecessary `pass`
Safe fix
76 76 |
77 77 | for _ in range(10):
78 78 | """buzz"""
79 |- pass
80 79 |
81 80 | async for _ in range(10):
82 81 | """buzz"""
PIE790.py:83:5: PIE790 [*] Unnecessary `pass` statement
|
81 | async for _ in range(10):
82 | """buzz"""
83 | pass
| ^^^^ PIE790
84 |
85 | while cond:
|
= help: Remove unnecessary `pass`
Safe fix
80 80 |
81 81 | async for _ in range(10):
82 82 | """buzz"""
83 |- pass
84 83 |
85 84 | while cond:
86 85 | """buzz"""
PIE790.py:87:5: PIE790 [*] Unnecessary `pass` statement
|
85 | while cond:
86 | """buzz"""
87 | pass
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
84 84 |
85 85 | while cond:
86 86 | """buzz"""
87 |- pass
88 87 |
89 88 |
90 89 | with bar:
PIE790.py:92:5: PIE790 [*] Unnecessary `pass` statement
|
90 | with bar:
91 | """buzz"""
92 | pass
| ^^^^ PIE790
93 |
94 | async with bar:
|
= help: Remove unnecessary `pass`
Safe fix
89 89 |
90 90 | with bar:
91 91 | """buzz"""
92 |- pass
93 92 |
94 93 | async with bar:
95 94 | """buzz"""
PIE790.py:96:5: PIE790 [*] Unnecessary `pass` statement
|
94 | async with bar:
95 | """buzz"""
96 | pass
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
93 93 |
94 94 | async with bar:
95 95 | """buzz"""
96 |- pass
97 96 |
98 97 |
99 98 | def foo() -> None:
PIE790.py:101:5: PIE790 [*] Unnecessary `pass` statement
|
99 | def foo() -> None:
100 | """buzz"""
101 | pass # bar
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
98 98 |
99 99 | def foo() -> None:
100 100 | """buzz"""
101 |- pass # bar
101 |+ # bar
102 102 |
103 103 |
104 104 | class Foo:
PIE790.py:130:5: PIE790 [*] Unnecessary `pass` statement
|
128 | def foo():
129 | print("foo")
130 | pass
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
127 127 |
128 128 | def foo():
129 129 | print("foo")
130 |- pass
131 130 |
132 131 |
133 132 | def foo():
PIE790.py:136:5: PIE790 [*] Unnecessary `pass` statement
|
134 | """A docstring."""
135 | print("foo")
136 | pass
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
133 133 | def foo():
134 134 | """A docstring."""
135 135 | print("foo")
136 |- pass
137 136 |
138 137 |
139 138 | for i in range(10):
PIE790.py:140:5: PIE790 [*] Unnecessary `pass` statement
|
139 | for i in range(10):
140 | pass
| ^^^^ PIE790
141 | pass
|
= help: Remove unnecessary `pass`
Safe fix
138 138 |
139 139 | for i in range(10):
140 140 | pass
141 |- pass
142 141 |
143 142 | for i in range(10):
144 143 | pass
PIE790.py:141:5: PIE790 [*] Unnecessary `pass` statement
|
139 | for i in range(10):
140 | pass
141 | pass
| ^^^^ PIE790
142 |
143 | for i in range(10):
|
= help: Remove unnecessary `pass`
Safe fix
138 138 |
139 139 | for i in range(10):
140 140 | pass
141 |- pass
142 141 |
143 142 | for i in range(10):
144 143 | pass
PIE790.py:144:5: PIE790 [*] Unnecessary `pass` statement
|
143 | for i in range(10):
144 | pass
| ^^^^ PIE790
145 |
146 | pass
|
= help: Remove unnecessary `pass`
Safe fix
141 141 | pass
142 142 |
143 143 | for i in range(10):
144 |- pass
145 144 |
146 145 | pass
147 146 |
PIE790.py:146:5: PIE790 [*] Unnecessary `pass` statement
|
144 | pass
145 |
146 | pass
| ^^^^ PIE790
147 |
148 | for i in range(10):
|
= help: Remove unnecessary `pass`
Safe fix
143 143 | for i in range(10):
144 144 | pass
145 145 |
146 |- pass
147 146 |
148 147 | for i in range(10):
149 148 | pass # comment
PIE790.py:149:5: PIE790 [*] Unnecessary `pass` statement
|
148 | for i in range(10):
149 | pass # comment
| ^^^^ PIE790
150 | pass
|
= help: Remove unnecessary `pass`
Safe fix
146 146 | pass
147 147 |
148 148 | for i in range(10):
149 |- pass # comment
149 |+ # comment
150 150 | pass
151 151 |
152 152 |
PIE790.py:150:5: PIE790 [*] Unnecessary `pass` statement
|
148 | for i in range(10):
149 | pass # comment
150 | pass
| ^^^^ PIE790
|
= help: Remove unnecessary `pass`
Safe fix
147 147 |
148 148 | for i in range(10):
149 149 | pass # comment
150 |- pass
151 150 |
152 151 |
153 152 | def foo():
PIE790.py:155:5: PIE790 [*] Unnecessary `...` literal
|
153 | def foo():
154 | print("foo")
155 | ...
| ^^^ PIE790
|
= help: Remove unnecessary `...`
Safe fix
152 152 |
153 153 | def foo():
154 154 | print("foo")
155 |- ...
156 155 |
157 156 |
158 157 | def foo():
PIE790.py:161:5: PIE790 [*] Unnecessary `...` literal
|
159 | """A docstring."""
160 | print("foo")
161 | ...
| ^^^ PIE790
|
= help: Remove unnecessary `...`
Safe fix
158 158 | def foo():
159 159 | """A docstring."""
160 160 | print("foo")
161 |- ...
162 161 |
163 162 |
164 163 | for i in range(10):
PIE790.py:165:5: PIE790 [*] Unnecessary `...` literal
|
164 | for i in range(10):
165 | ...
| ^^^ PIE790
166 | ...
|
= help: Remove unnecessary `...`
Safe fix
163 163 |
164 164 | for i in range(10):
165 165 | ...
166 |- ...
167 166 |
168 167 | for i in range(10):
169 168 | ...
PIE790.py:166:5: PIE790 [*] Unnecessary `...` literal
|
164 | for i in range(10):
165 | ...
166 | ...
| ^^^ PIE790
167 |
168 | for i in range(10):
|
= help: Remove unnecessary `...`
Safe fix
163 163 |
164 164 | for i in range(10):
165 165 | ...
166 |- ...
167 166 |
168 167 | for i in range(10):
169 168 | ...
PIE790.py:169:5: PIE790 [*] Unnecessary `...` literal
|
168 | for i in range(10):
169 | ...
| ^^^ PIE790
170 |
171 | ...
|
= help: Remove unnecessary `...`
Safe fix
166 166 | ...
167 167 |
168 168 | for i in range(10):
169 |- ...
170 169 |
171 170 | ...
172 171 |
PIE790.py:171:5: PIE790 [*] Unnecessary `...` literal
|
169 | ...
170 |
171 | ...
| ^^^ PIE790
172 |
173 | for i in range(10):
|
= help: Remove unnecessary `...`
Safe fix
168 168 | for i in range(10):
169 169 | ...
170 170 |
171 |- ...
172 171 |
173 172 | for i in range(10):
174 173 | ... # comment
PIE790.py:174:5: PIE790 [*] Unnecessary `...` literal
|
173 | for i in range(10):
174 | ... # comment
| ^^^ PIE790
175 | ...
|
= help: Remove unnecessary `...`
Safe fix
171 171 | ...
172 172 |
173 173 | for i in range(10):
174 |- ... # comment
174 |+ # comment
175 175 | ...
176 176 |
177 177 | for i in range(10):
PIE790.py:175:5: PIE790 [*] Unnecessary `...` literal
|
173 | for i in range(10):
174 | ... # comment
175 | ...
| ^^^ PIE790
176 |
177 | for i in range(10):
|
= help: Remove unnecessary `...`
Safe fix
172 172 |
173 173 | for i in range(10):
174 174 | ... # comment
175 |- ...
176 175 |
177 176 | for i in range(10):
178 177 | ...
PIE790.py:178:5: PIE790 [*] Unnecessary `...` literal
|
177 | for i in range(10):
178 | ...
| ^^^ PIE790
179 | pass
|
= help: Remove unnecessary `...`
Safe fix
175 175 | ...
176 176 |
177 177 | for i in range(10):
178 |- ...
179 178 | pass
180 179 |
181 180 | from typing import Protocol
PIE790.py:179:5: PIE790 [*] Unnecessary `pass` statement
|
177 | for i in range(10):
178 | ...
179 | pass
| ^^^^ PIE790
180 |
181 | from typing import Protocol
|
= help: Remove unnecessary `pass`
Safe fix
176 176 |
177 177 | for i in range(10):
178 178 | ...
179 |- pass
180 179 |
181 180 | from typing import Protocol
182 181 |
PIE790.py:209:9: PIE790 [*] Unnecessary `...` literal
|
207 | def stub(self) -> str:
208 | """Docstring"""
209 | ...
| ^^^ PIE790
|
= help: Remove unnecessary `...`
Safe fix
206 206 |
207 207 | def stub(self) -> str:
208 208 | """Docstring"""
209 |- ...

View File

@@ -1,134 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_pie/mod.rs
---
PIE800.py:1:14: PIE800 [*] Unnecessary spread `**`
|
1 | {"foo": 1, **{"bar": 1}} # PIE800
| ^^^^^^^^^^ PIE800
2 |
3 | {**{"bar": 10}, "a": "b"} # PIE800
|
= help: Remove unnecessary dict
Safe fix
1 |-{"foo": 1, **{"bar": 1}} # PIE800
1 |+{"foo": 1, "bar": 1} # PIE800
2 2 |
3 3 | {**{"bar": 10}, "a": "b"} # PIE800
4 4 |
PIE800.py:3:4: PIE800 [*] Unnecessary spread `**`
|
1 | {"foo": 1, **{"bar": 1}} # PIE800
2 |
3 | {**{"bar": 10}, "a": "b"} # PIE800
| ^^^^^^^^^^^ PIE800
4 |
5 | foo({**foo, **{"bar": True}}) # PIE800
|
= help: Remove unnecessary dict
Safe fix
1 1 | {"foo": 1, **{"bar": 1}} # PIE800
2 2 |
3 |-{**{"bar": 10}, "a": "b"} # PIE800
3 |+{"bar": 10, "a": "b"} # PIE800
4 4 |
5 5 | foo({**foo, **{"bar": True}}) # PIE800
6 6 |
PIE800.py:5:15: PIE800 [*] Unnecessary spread `**`
|
3 | {**{"bar": 10}, "a": "b"} # PIE800
4 |
5 | foo({**foo, **{"bar": True}}) # PIE800
| ^^^^^^^^^^^^^ PIE800
6 |
7 | {**foo, **{"bar": 10}} # PIE800
|
= help: Remove unnecessary dict
Safe fix
2 2 |
3 3 | {**{"bar": 10}, "a": "b"} # PIE800
4 4 |
5 |-foo({**foo, **{"bar": True}}) # PIE800
5 |+foo({**foo, "bar": True}) # PIE800
6 6 |
7 7 | {**foo, **{"bar": 10}} # PIE800
8 8 |
PIE800.py:7:11: PIE800 [*] Unnecessary spread `**`
|
5 | foo({**foo, **{"bar": True}}) # PIE800
6 |
7 | {**foo, **{"bar": 10}} # PIE800
| ^^^^^^^^^^^ PIE800
8 |
9 | { # PIE800
|
= help: Remove unnecessary dict
Safe fix
4 4 |
5 5 | foo({**foo, **{"bar": True}}) # PIE800
6 6 |
7 |-{**foo, **{"bar": 10}} # PIE800
7 |+{**foo, "bar": 10} # PIE800
8 8 |
9 9 | { # PIE800
10 10 | "a": "b",
PIE800.py:12:7: PIE800 [*] Unnecessary spread `**`
|
10 | "a": "b",
11 | # Preserve
12 | **{
| _______^
13 | | # all
14 | | "bar": 10, # the
15 | | # comments
16 | | },
| |_____^ PIE800
17 | }
|
= help: Remove unnecessary dict
Safe fix
9 9 | { # PIE800
10 10 | "a": "b",
11 11 | # Preserve
12 |- **{
12 |+
13 13 | # all
14 |- "bar": 10, # the
14 |+ "bar": 10 # the
15 15 | # comments
16 |- },
16 |+ ,
17 17 | }
18 18 |
19 19 | {**foo, **buzz, **{bar: 10}} # PIE800
PIE800.py:19:19: PIE800 [*] Unnecessary spread `**`
|
17 | }
18 |
19 | {**foo, **buzz, **{bar: 10}} # PIE800
| ^^^^^^^^^ PIE800
20 |
21 | {**foo, "bar": True } # OK
|
= help: Remove unnecessary dict
Safe fix
16 16 | },
17 17 | }
18 18 |
19 |-{**foo, **buzz, **{bar: 10}} # PIE800
19 |+{**foo, **buzz, bar: 10} # PIE800
20 20 |
21 21 | {**foo, "bar": True } # OK
22 22 |

View File

@@ -1,118 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_pie/mod.rs
---
PIE807.py:3:44: PIE807 [*] Prefer `list` over useless lambda
|
1 | @dataclass
2 | class Foo:
3 | foo: List[str] = field(default_factory=lambda: []) # PIE807
| ^^^^^^^^^^ PIE807
4 | bar: Dict[str, int] = field(default_factory=lambda: {}) # PIE807
|
= help: Replace with `lambda` with `list`
Safe fix
1 1 | @dataclass
2 2 | class Foo:
3 |- foo: List[str] = field(default_factory=lambda: []) # PIE807
3 |+ foo: List[str] = field(default_factory=list) # PIE807
4 4 | bar: Dict[str, int] = field(default_factory=lambda: {}) # PIE807
5 5 |
6 6 |
PIE807.py:4:49: PIE807 [*] Prefer `dict` over useless lambda
|
2 | class Foo:
3 | foo: List[str] = field(default_factory=lambda: []) # PIE807
4 | bar: Dict[str, int] = field(default_factory=lambda: {}) # PIE807
| ^^^^^^^^^^ PIE807
|
= help: Replace with `lambda` with `dict`
Safe fix
1 1 | @dataclass
2 2 | class Foo:
3 3 | foo: List[str] = field(default_factory=lambda: []) # PIE807
4 |- bar: Dict[str, int] = field(default_factory=lambda: {}) # PIE807
4 |+ bar: Dict[str, int] = field(default_factory=dict) # PIE807
5 5 |
6 6 |
7 7 | class FooTable(BaseTable):
PIE807.py:8:36: PIE807 [*] Prefer `list` over useless lambda
|
7 | class FooTable(BaseTable):
8 | foo = fields.ListField(default=lambda: []) # PIE807
| ^^^^^^^^^^ PIE807
9 | bar = fields.ListField(default=lambda: {}) # PIE807
|
= help: Replace with `lambda` with `list`
Safe fix
5 5 |
6 6 |
7 7 | class FooTable(BaseTable):
8 |- foo = fields.ListField(default=lambda: []) # PIE807
8 |+ foo = fields.ListField(default=list) # PIE807
9 9 | bar = fields.ListField(default=lambda: {}) # PIE807
10 10 |
11 11 |
PIE807.py:9:36: PIE807 [*] Prefer `dict` over useless lambda
|
7 | class FooTable(BaseTable):
8 | foo = fields.ListField(default=lambda: []) # PIE807
9 | bar = fields.ListField(default=lambda: {}) # PIE807
| ^^^^^^^^^^ PIE807
|
= help: Replace with `lambda` with `dict`
Safe fix
6 6 |
7 7 | class FooTable(BaseTable):
8 8 | foo = fields.ListField(default=lambda: []) # PIE807
9 |- bar = fields.ListField(default=lambda: {}) # PIE807
9 |+ bar = fields.ListField(default=dict) # PIE807
10 10 |
11 11 |
12 12 | class FooTable(BaseTable):
PIE807.py:13:28: PIE807 [*] Prefer `list` over useless lambda
|
12 | class FooTable(BaseTable):
13 | foo = fields.ListField(lambda: []) # PIE807
| ^^^^^^^^^^ PIE807
14 | bar = fields.ListField(default=lambda: {}) # PIE807
|
= help: Replace with `lambda` with `list`
Safe fix
10 10 |
11 11 |
12 12 | class FooTable(BaseTable):
13 |- foo = fields.ListField(lambda: []) # PIE807
13 |+ foo = fields.ListField(list) # PIE807
14 14 | bar = fields.ListField(default=lambda: {}) # PIE807
15 15 |
16 16 |
PIE807.py:14:36: PIE807 [*] Prefer `dict` over useless lambda
|
12 | class FooTable(BaseTable):
13 | foo = fields.ListField(lambda: []) # PIE807
14 | bar = fields.ListField(default=lambda: {}) # PIE807
| ^^^^^^^^^^ PIE807
|
= help: Replace with `lambda` with `dict`
Safe fix
11 11 |
12 12 | class FooTable(BaseTable):
13 13 | foo = fields.ListField(lambda: []) # PIE807
14 |- bar = fields.ListField(default=lambda: {}) # PIE807
14 |+ bar = fields.ListField(default=dict) # PIE807
15 15 |
16 16 |
17 17 | @dataclass

View File

@@ -1,4 +1,4 @@
use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation};
use ruff_diagnostics::{Applicability, Diagnostic, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::Imported;
use ruff_python_semantic::{Binding, BindingKind};
@@ -29,6 +29,12 @@ use crate::renamer::Renamer;
/// ```python
/// from collections.abc import Set as AbstractSet
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe for `Set` imports defined at the
/// top-level of a module. Top-level symbols are implicitly exported by the
/// module, and so renaming a top-level symbol may break downstream modules
/// that import it.
#[violation]
pub struct UnaliasedCollectionsAbcSetImport;
@@ -69,7 +75,15 @@ pub(crate) fn unaliased_collections_abc_set_import(
diagnostic.try_set_fix(|| {
let scope = &checker.semantic().scopes[binding.scope];
let (edit, rest) = Renamer::rename(name, "AbstractSet", scope, checker.semantic())?;
Ok(Fix::unsafe_edits(edit, rest))
Ok(Fix::applicable_edits(
edit,
rest,
if scope.kind.is_module() {
Applicability::Unsafe
} else {
Applicability::Safe
},
))
});
}
Some(diagnostic)

View File

@@ -116,7 +116,7 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Exp
expr.range(),
);
if checker.settings.preview.is_enabled() {
diagnostic.set_fix({
let literal = Expr::Subscript(ast::ExprSubscript {
value: Box::new(literal_subscript.clone()),
slice: Box::new(Expr::Tuple(ast::ExprTuple {
@@ -130,10 +130,10 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Exp
if other_exprs.is_empty() {
// if the union is only literals, we just replace the whole thing with a single literal
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
Fix::safe_edit(Edit::range_replacement(
checker.generator().expr(&literal),
expr.range(),
)));
))
} else {
let elts: Vec<Expr> = std::iter::once(literal)
.chain(other_exprs.into_iter().cloned())
@@ -156,12 +156,9 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Exp
checker.generator().expr(&pep_604_union(&elts))
};
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
content,
expr.range(),
)));
Fix::safe_edit(Edit::range_replacement(content, expr.range()))
}
}
});
checker.diagnostics.push(diagnostic);
}

View File

@@ -9,7 +9,7 @@ PYI025.py:10:33: PYI025 [*] Use `from collections.abc import Set as AbstractSet`
|
= help: Alias `Set` to `AbstractSet`
Unsafe fix
Safe fix
7 7 |
8 8 |
9 9 | def f():
@@ -29,7 +29,7 @@ PYI025.py:14:51: PYI025 [*] Use `from collections.abc import Set as AbstractSet`
|
= help: Alias `Set` to `AbstractSet`
Unsafe fix
Safe fix
11 11 |
12 12 |
13 13 | def f():

View File

@@ -11,7 +11,7 @@ PYI025.pyi:8:33: PYI025 [*] Use `from collections.abc import Set as AbstractSet`
|
= help: Alias `Set` to `AbstractSet`
Unsafe fix
Safe fix
5 5 | from collections.abc import Container, Sized, Set as AbstractSet, ValuesView # Ok
6 6 |
7 7 | def f():
@@ -31,7 +31,7 @@ PYI025.pyi:11:51: PYI025 [*] Use `from collections.abc import Set as AbstractSet
|
= help: Alias `Set` to `AbstractSet`
Unsafe fix
Safe fix
8 8 | from collections.abc import Set # PYI025
9 9 |
10 10 | def f():
@@ -52,7 +52,7 @@ PYI025.pyi:16:37: PYI025 [*] Use `from collections.abc import Set as AbstractSet
|
= help: Alias `Set` to `AbstractSet`
Unsafe fix
Safe fix
13 13 | def f():
14 14 | """Test: local symbol renaming."""
15 15 | if True:
@@ -130,7 +130,7 @@ PYI025.pyi:44:33: PYI025 [*] Use `from collections.abc import Set as AbstractSet
|
= help: Alias `Set` to `AbstractSet`
Unsafe fix
Safe fix
41 41 |
42 42 | def f():
43 43 | """Test: nonlocal symbol renaming."""

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI030.py:9:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
PYI030.py:9:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
8 | # Should emit for duplicate field types.
9 | field2: Literal[1] | Literal[2] # Error
@@ -11,7 +11,17 @@ PYI030.py:9:9: PYI030 Multiple literal members in a union. Use a single literal,
|
= help: Replace with a single `Literal`
PYI030.py:12:17: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
6 6 | field1: Literal[1] # OK
7 7 |
8 8 | # Should emit for duplicate field types.
9 |-field2: Literal[1] | Literal[2] # Error
9 |+field2: Literal[1, 2] # Error
10 10 |
11 11 | # Should emit for union types in arguments.
12 12 | def func1(arg1: Literal[1] | Literal[2]): # Error
PYI030.py:12:17: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
11 | # Should emit for union types in arguments.
12 | def func1(arg1: Literal[1] | Literal[2]): # Error
@@ -20,7 +30,17 @@ PYI030.py:12:17: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:17:16: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
9 9 | field2: Literal[1] | Literal[2] # Error
10 10 |
11 11 | # Should emit for union types in arguments.
12 |-def func1(arg1: Literal[1] | Literal[2]): # Error
12 |+def func1(arg1: Literal[1, 2]): # Error
13 13 | print(arg1)
14 14 |
15 15 |
PYI030.py:17:16: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
16 | # Should emit for unions in return types.
17 | def func2() -> Literal[1] | Literal[2]: # Error
@@ -29,7 +49,17 @@ PYI030.py:17:16: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:22:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
14 14 |
15 15 |
16 16 | # Should emit for unions in return types.
17 |-def func2() -> Literal[1] | Literal[2]: # Error
17 |+def func2() -> Literal[1, 2]: # Error
18 18 | return "my Literal[1]ing"
19 19 |
20 20 |
PYI030.py:22:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
21 | # Should emit in longer unions, even if not directly adjacent.
22 | field3: Literal[1] | Literal[2] | str # Error
@@ -39,7 +69,17 @@ PYI030.py:22:9: PYI030 Multiple literal members in a union. Use a single literal
|
= help: Replace with a single `Literal`
PYI030.py:23:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
19 19 |
20 20 |
21 21 | # Should emit in longer unions, even if not directly adjacent.
22 |-field3: Literal[1] | Literal[2] | str # Error
22 |+field3: Literal[1, 2] | str # Error
23 23 | field4: str | Literal[1] | Literal[2] # Error
24 24 | field5: Literal[1] | str | Literal[2] # Error
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
PYI030.py:23:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
21 | # Should emit in longer unions, even if not directly adjacent.
22 | field3: Literal[1] | Literal[2] | str # Error
@@ -50,7 +90,17 @@ PYI030.py:23:9: PYI030 Multiple literal members in a union. Use a single literal
|
= help: Replace with a single `Literal`
PYI030.py:24:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
20 20 |
21 21 | # Should emit in longer unions, even if not directly adjacent.
22 22 | field3: Literal[1] | Literal[2] | str # Error
23 |-field4: str | Literal[1] | Literal[2] # Error
23 |+field4: Literal[1, 2] | str # Error
24 24 | field5: Literal[1] | str | Literal[2] # Error
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
26 26 |
PYI030.py:24:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
22 | field3: Literal[1] | Literal[2] | str # Error
23 | field4: str | Literal[1] | Literal[2] # Error
@@ -60,7 +110,17 @@ PYI030.py:24:9: PYI030 Multiple literal members in a union. Use a single literal
|
= help: Replace with a single `Literal`
PYI030.py:25:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
21 21 | # Should emit in longer unions, even if not directly adjacent.
22 22 | field3: Literal[1] | Literal[2] | str # Error
23 23 | field4: str | Literal[1] | Literal[2] # Error
24 |-field5: Literal[1] | str | Literal[2] # Error
24 |+field5: Literal[1, 2] | str # Error
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
26 26 |
27 27 | # Should emit for non-type unions.
PYI030.py:25:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
23 | field4: str | Literal[1] | Literal[2] # Error
24 | field5: Literal[1] | str | Literal[2] # Error
@@ -71,7 +131,17 @@ PYI030.py:25:9: PYI030 Multiple literal members in a union. Use a single literal
|
= help: Replace with a single `Literal`
PYI030.py:28:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
22 22 | field3: Literal[1] | Literal[2] | str # Error
23 23 | field4: str | Literal[1] | Literal[2] # Error
24 24 | field5: Literal[1] | str | Literal[2] # Error
25 |-field6: Literal[1] | bool | Literal[2] | str # Error
25 |+field6: Literal[1, 2] | bool | str # Error
26 26 |
27 27 | # Should emit for non-type unions.
28 28 | field7 = Literal[1] | Literal[2] # Error
PYI030.py:28:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
27 | # Should emit for non-type unions.
28 | field7 = Literal[1] | Literal[2] # Error
@@ -81,7 +151,17 @@ PYI030.py:28:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:31:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
26 26 |
27 27 | # Should emit for non-type unions.
28 |-field7 = Literal[1] | Literal[2] # Error
28 |+field7 = Literal[1, 2] # Error
29 29 |
30 30 | # Should emit for parenthesized unions.
31 31 | field8: Literal[1] | (Literal[2] | str) # Error
PYI030.py:31:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
30 | # Should emit for parenthesized unions.
31 | field8: Literal[1] | (Literal[2] | str) # Error
@@ -91,7 +171,17 @@ PYI030.py:31:9: PYI030 Multiple literal members in a union. Use a single literal
|
= help: Replace with a single `Literal`
PYI030.py:34:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
28 28 | field7 = Literal[1] | Literal[2] # Error
29 29 |
30 30 | # Should emit for parenthesized unions.
31 |-field8: Literal[1] | (Literal[2] | str) # Error
31 |+field8: Literal[1, 2] | str # Error
32 32 |
33 33 | # Should handle user parentheses when fixing.
34 34 | field9: Literal[1] | (Literal[2] | str) # Error
PYI030.py:34:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
33 | # Should handle user parentheses when fixing.
34 | field9: Literal[1] | (Literal[2] | str) # Error
@@ -100,7 +190,17 @@ PYI030.py:34:9: PYI030 Multiple literal members in a union. Use a single literal
|
= help: Replace with a single `Literal`
PYI030.py:35:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
31 31 | field8: Literal[1] | (Literal[2] | str) # Error
32 32 |
33 33 | # Should handle user parentheses when fixing.
34 |-field9: Literal[1] | (Literal[2] | str) # Error
34 |+field9: Literal[1, 2] | str # Error
35 35 | field10: (Literal[1] | str) | Literal[2] # Error
36 36 |
37 37 | # Should emit for union in generic parent type.
PYI030.py:35:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
33 | # Should handle user parentheses when fixing.
34 | field9: Literal[1] | (Literal[2] | str) # Error
@@ -111,7 +211,17 @@ PYI030.py:35:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:38:15: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
32 32 |
33 33 | # Should handle user parentheses when fixing.
34 34 | field9: Literal[1] | (Literal[2] | str) # Error
35 |-field10: (Literal[1] | str) | Literal[2] # Error
35 |+field10: Literal[1, 2] | str # Error
36 36 |
37 37 | # Should emit for union in generic parent type.
38 38 | field11: dict[Literal[1] | Literal[2], str] # Error
PYI030.py:38:15: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
37 | # Should emit for union in generic parent type.
38 | field11: dict[Literal[1] | Literal[2], str] # Error
@@ -121,7 +231,17 @@ PYI030.py:38:15: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
Safe fix
35 35 | field10: (Literal[1] | str) | Literal[2] # Error
36 36 |
37 37 | # Should emit for union in generic parent type.
38 |-field11: dict[Literal[1] | Literal[2], str] # Error
38 |+field11: dict[Literal[1, 2], str] # Error
39 39 |
40 40 | # Should emit for unions with more than two cases
41 41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
PYI030.py:41:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
|
40 | # Should emit for unions with more than two cases
41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
@@ -130,7 +250,17 @@ PYI030.py:41:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:42:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
Safe fix
38 38 | field11: dict[Literal[1] | Literal[2], str] # Error
39 39 |
40 40 | # Should emit for unions with more than two cases
41 |-field12: Literal[1] | Literal[2] | Literal[3] # Error
41 |+field12: Literal[1, 2, 3] # Error
42 42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
43 43 |
44 44 | # Should emit for unions with more than two cases, even if not directly adjacent
PYI030.py:42:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
40 | # Should emit for unions with more than two cases
41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
@@ -141,7 +271,17 @@ PYI030.py:42:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:45:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
Safe fix
39 39 |
40 40 | # Should emit for unions with more than two cases
41 41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
42 |-field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
42 |+field13: Literal[1, 2, 3, 4] # Error
43 43 |
44 44 | # Should emit for unions with more than two cases, even if not directly adjacent
45 45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error
PYI030.py:45:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
|
44 | # Should emit for unions with more than two cases, even if not directly adjacent
45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error
@@ -151,7 +291,17 @@ PYI030.py:45:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:48:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo", True]`
Safe fix
42 42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
43 43 |
44 44 | # Should emit for unions with more than two cases, even if not directly adjacent
45 |-field14: Literal[1] | Literal[2] | str | Literal[3] # Error
45 |+field14: Literal[1, 2, 3] | str # Error
46 46 |
47 47 | # Should emit for unions with mixed literal internal types
48 48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error
PYI030.py:48:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo", True]`
|
47 | # Should emit for unions with mixed literal internal types
48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error
@@ -161,7 +311,17 @@ PYI030.py:48:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:51:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 1]`
Safe fix
45 45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error
46 46 |
47 47 | # Should emit for unions with mixed literal internal types
48 |-field15: Literal[1] | Literal["foo"] | Literal[True] # Error
48 |+field15: Literal[1, "foo", True] # Error
49 49 |
50 50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
51 51 | field16: Literal[1] | Literal[1] # OK
PYI030.py:51:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 1]`
|
50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
51 | field16: Literal[1] | Literal[1] # OK
@@ -171,7 +331,17 @@ PYI030.py:51:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:60:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
48 48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error
49 49 |
50 50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
51 |-field16: Literal[1] | Literal[1] # OK
51 |+field16: Literal[1, 1] # OK
52 52 |
53 53 | # Shouldn't emit if in new parent type
54 54 | field17: Literal[1] | dict[Literal[2], str] # OK
PYI030.py:60:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
59 | # Should respect name of literal type used
60 | field19: typing.Literal[1] | typing.Literal[2] # Error
@@ -181,7 +351,17 @@ PYI030.py:60:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:63:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
57 57 | field18: dict[Literal[1], Literal[2]] # OK
58 58 |
59 59 | # Should respect name of literal type used
60 |-field19: typing.Literal[1] | typing.Literal[2] # Error
60 |+field19: typing.Literal[1, 2] # Error
61 61 |
62 62 | # Should emit in cases with newlines
63 63 | field20: typing.Union[
PYI030.py:63:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
62 | # Should emit in cases with newlines
63 | field20: typing.Union[
@@ -197,7 +377,22 @@ PYI030.py:63:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:71:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
Safe fix
60 60 | field19: typing.Literal[1] | typing.Literal[2] # Error
61 61 |
62 62 | # Should emit in cases with newlines
63 |-field20: typing.Union[
64 |- Literal[
65 |- 1 # test
66 |- ],
67 |- Literal[2],
68 |-] # Error, newline and comment will not be emitted in message
63 |+field20: Literal[1, 2] # Error, newline and comment will not be emitted in message
69 64 |
70 65 | # Should handle multiple unions with multiple members
71 66 | field21: Literal[1, 2] | Literal[3, 4] # Error
PYI030.py:71:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
70 | # Should handle multiple unions with multiple members
71 | field21: Literal[1, 2] | Literal[3, 4] # Error
@@ -207,7 +402,17 @@ PYI030.py:71:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:74:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
68 68 | ] # Error, newline and comment will not be emitted in message
69 69 |
70 70 | # Should handle multiple unions with multiple members
71 |-field21: Literal[1, 2] | Literal[3, 4] # Error
71 |+field21: Literal[1, 2, 3, 4] # Error
72 72 |
73 73 | # Should emit in cases with `typing.Union` instead of `|`
74 74 | field22: typing.Union[Literal[1], Literal[2]] # Error
PYI030.py:74:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
73 | # Should emit in cases with `typing.Union` instead of `|`
74 | field22: typing.Union[Literal[1], Literal[2]] # Error
@@ -217,7 +422,17 @@ PYI030.py:74:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:77:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
71 71 | field21: Literal[1, 2] | Literal[3, 4] # Error
72 72 |
73 73 | # Should emit in cases with `typing.Union` instead of `|`
74 |-field22: typing.Union[Literal[1], Literal[2]] # Error
74 |+field22: Literal[1, 2] # Error
75 75 |
76 76 | # Should emit in cases with `typing_extensions.Literal`
77 77 | field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
PYI030.py:77:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
76 | # Should emit in cases with `typing_extensions.Literal`
77 | field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
@@ -227,7 +442,17 @@ PYI030.py:77:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:80:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
74 74 | field22: typing.Union[Literal[1], Literal[2]] # Error
75 75 |
76 76 | # Should emit in cases with `typing_extensions.Literal`
77 |-field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
77 |+field23: typing_extensions.Literal[1, 2] # Error
78 78 |
79 79 | # Should emit in cases with nested `typing.Union`
80 80 | field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
PYI030.py:80:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
79 | # Should emit in cases with nested `typing.Union`
80 | field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
@@ -237,7 +462,17 @@ PYI030.py:80:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:83:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
77 77 | field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
78 78 |
79 79 | # Should emit in cases with nested `typing.Union`
80 |-field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
80 |+field24: typing.Union[Literal[1, 2], str] # Error
81 81 |
82 82 | # Should emit in cases with mixed `typing.Union` and `|`
83 83 | field25: typing.Union[Literal[1], Literal[2] | str] # Error
PYI030.py:83:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
82 | # Should emit in cases with mixed `typing.Union` and `|`
83 | field25: typing.Union[Literal[1], Literal[2] | str] # Error
@@ -247,7 +482,17 @@ PYI030.py:83:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:86:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
Safe fix
80 80 | field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
81 81 |
82 82 | # Should emit in cases with mixed `typing.Union` and `|`
83 |-field25: typing.Union[Literal[1], Literal[2] | str] # Error
83 |+field25: typing.Union[Literal[1, 2], str] # Error
84 84 |
85 85 | # Should emit only once in cases with multiple nested `typing.Union`
86 86 | field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
PYI030.py:86:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
85 | # Should emit only once in cases with multiple nested `typing.Union`
86 | field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
@@ -257,7 +502,17 @@ PYI030.py:86:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.py:89:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
Safe fix
83 83 | field25: typing.Union[Literal[1], Literal[2] | str] # Error
84 84 |
85 85 | # Should emit only once in cases with multiple nested `typing.Union`
86 |-field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
86 |+field24: Literal[1, 2, 3, 4] # Error
87 87 |
88 88 | # Should use the first literal subscript attribute when fixing
89 89 | field25: typing.Union[typing_extensions.Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]], str] # Error
PYI030.py:89:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
88 | # Should use the first literal subscript attribute when fixing
89 | field25: typing.Union[typing_extensions.Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]], str] # Error
@@ -265,4 +520,11 @@ PYI030.py:89:10: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
Safe fix
86 86 | field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
87 87 |
88 88 | # Should use the first literal subscript attribute when fixing
89 |-field25: typing.Union[typing_extensions.Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]], str] # Error
89 |+field25: typing.Union[typing_extensions.Literal[1, 2, 3, 4], str] # Error

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI030.pyi:9:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
PYI030.pyi:9:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
8 | # Should emit for duplicate field types.
9 | field2: Literal[1] | Literal[2] # Error
@@ -11,7 +11,17 @@ PYI030.pyi:9:9: PYI030 Multiple literal members in a union. Use a single literal
|
= help: Replace with a single `Literal`
PYI030.pyi:12:17: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
6 6 | field1: Literal[1] # OK
7 7 |
8 8 | # Should emit for duplicate field types.
9 |-field2: Literal[1] | Literal[2] # Error
9 |+field2: Literal[1, 2] # Error
10 10 |
11 11 | # Should emit for union types in arguments.
12 12 | def func1(arg1: Literal[1] | Literal[2]): # Error
PYI030.pyi:12:17: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
11 | # Should emit for union types in arguments.
12 | def func1(arg1: Literal[1] | Literal[2]): # Error
@@ -20,7 +30,17 @@ PYI030.pyi:12:17: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:17:16: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
9 9 | field2: Literal[1] | Literal[2] # Error
10 10 |
11 11 | # Should emit for union types in arguments.
12 |-def func1(arg1: Literal[1] | Literal[2]): # Error
12 |+def func1(arg1: Literal[1, 2]): # Error
13 13 | print(arg1)
14 14 |
15 15 |
PYI030.pyi:17:16: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
16 | # Should emit for unions in return types.
17 | def func2() -> Literal[1] | Literal[2]: # Error
@@ -29,7 +49,17 @@ PYI030.pyi:17:16: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
14 14 |
15 15 |
16 16 | # Should emit for unions in return types.
17 |-def func2() -> Literal[1] | Literal[2]: # Error
17 |+def func2() -> Literal[1, 2]: # Error
18 18 | return "my Literal[1]ing"
19 19 |
20 20 |
PYI030.pyi:22:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
21 | # Should emit in longer unions, even if not directly adjacent.
22 | field3: Literal[1] | Literal[2] | str # Error
@@ -39,7 +69,17 @@ PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.pyi:23:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
19 19 |
20 20 |
21 21 | # Should emit in longer unions, even if not directly adjacent.
22 |-field3: Literal[1] | Literal[2] | str # Error
22 |+field3: Literal[1, 2] | str # Error
23 23 | field4: str | Literal[1] | Literal[2] # Error
24 24 | field5: Literal[1] | str | Literal[2] # Error
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
PYI030.pyi:23:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
21 | # Should emit in longer unions, even if not directly adjacent.
22 | field3: Literal[1] | Literal[2] | str # Error
@@ -50,7 +90,17 @@ PYI030.pyi:23:9: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
20 20 |
21 21 | # Should emit in longer unions, even if not directly adjacent.
22 22 | field3: Literal[1] | Literal[2] | str # Error
23 |-field4: str | Literal[1] | Literal[2] # Error
23 |+field4: Literal[1, 2] | str # Error
24 24 | field5: Literal[1] | str | Literal[2] # Error
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
26 26 |
PYI030.pyi:24:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
22 | field3: Literal[1] | Literal[2] | str # Error
23 | field4: str | Literal[1] | Literal[2] # Error
@@ -60,7 +110,17 @@ PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.pyi:25:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
21 21 | # Should emit in longer unions, even if not directly adjacent.
22 22 | field3: Literal[1] | Literal[2] | str # Error
23 23 | field4: str | Literal[1] | Literal[2] # Error
24 |-field5: Literal[1] | str | Literal[2] # Error
24 |+field5: Literal[1, 2] | str # Error
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
26 26 |
27 27 | # Should emit for non-type unions.
PYI030.pyi:25:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
23 | field4: str | Literal[1] | Literal[2] # Error
24 | field5: Literal[1] | str | Literal[2] # Error
@@ -71,7 +131,17 @@ PYI030.pyi:25:9: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.pyi:28:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
22 22 | field3: Literal[1] | Literal[2] | str # Error
23 23 | field4: str | Literal[1] | Literal[2] # Error
24 24 | field5: Literal[1] | str | Literal[2] # Error
25 |-field6: Literal[1] | bool | Literal[2] | str # Error
25 |+field6: Literal[1, 2] | bool | str # Error
26 26 |
27 27 | # Should emit for non-type unions.
28 28 | field7 = Literal[1] | Literal[2] # Error
PYI030.pyi:28:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
27 | # Should emit for non-type unions.
28 | field7 = Literal[1] | Literal[2] # Error
@@ -81,7 +151,17 @@ PYI030.pyi:28:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:31:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
26 26 |
27 27 | # Should emit for non-type unions.
28 |-field7 = Literal[1] | Literal[2] # Error
28 |+field7 = Literal[1, 2] # Error
29 29 |
30 30 | # Should emit for parenthesized unions.
31 31 | field8: Literal[1] | (Literal[2] | str) # Error
PYI030.pyi:31:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
30 | # Should emit for parenthesized unions.
31 | field8: Literal[1] | (Literal[2] | str) # Error
@@ -91,7 +171,17 @@ PYI030.pyi:31:9: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.pyi:34:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
28 28 | field7 = Literal[1] | Literal[2] # Error
29 29 |
30 30 | # Should emit for parenthesized unions.
31 |-field8: Literal[1] | (Literal[2] | str) # Error
31 |+field8: Literal[1, 2] | str # Error
32 32 |
33 33 | # Should handle user parentheses when fixing.
34 34 | field9: Literal[1] | (Literal[2] | str) # Error
PYI030.pyi:34:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
33 | # Should handle user parentheses when fixing.
34 | field9: Literal[1] | (Literal[2] | str) # Error
@@ -100,7 +190,17 @@ PYI030.pyi:34:9: PYI030 Multiple literal members in a union. Use a single litera
|
= help: Replace with a single `Literal`
PYI030.pyi:35:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
31 31 | field8: Literal[1] | (Literal[2] | str) # Error
32 32 |
33 33 | # Should handle user parentheses when fixing.
34 |-field9: Literal[1] | (Literal[2] | str) # Error
34 |+field9: Literal[1, 2] | str # Error
35 35 | field10: (Literal[1] | str) | Literal[2] # Error
36 36 |
37 37 | # Should emit for union in generic parent type.
PYI030.pyi:35:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
33 | # Should handle user parentheses when fixing.
34 | field9: Literal[1] | (Literal[2] | str) # Error
@@ -111,7 +211,17 @@ PYI030.pyi:35:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:38:15: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
32 32 |
33 33 | # Should handle user parentheses when fixing.
34 34 | field9: Literal[1] | (Literal[2] | str) # Error
35 |-field10: (Literal[1] | str) | Literal[2] # Error
35 |+field10: Literal[1, 2] | str # Error
36 36 |
37 37 | # Should emit for union in generic parent type.
38 38 | field11: dict[Literal[1] | Literal[2], str] # Error
PYI030.pyi:38:15: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
37 | # Should emit for union in generic parent type.
38 | field11: dict[Literal[1] | Literal[2], str] # Error
@@ -121,7 +231,17 @@ PYI030.pyi:38:15: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
Safe fix
35 35 | field10: (Literal[1] | str) | Literal[2] # Error
36 36 |
37 37 | # Should emit for union in generic parent type.
38 |-field11: dict[Literal[1] | Literal[2], str] # Error
38 |+field11: dict[Literal[1, 2], str] # Error
39 39 |
40 40 | # Should emit for unions with more than two cases
41 41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
PYI030.pyi:41:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
|
40 | # Should emit for unions with more than two cases
41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
@@ -130,7 +250,17 @@ PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
Safe fix
38 38 | field11: dict[Literal[1] | Literal[2], str] # Error
39 39 |
40 40 | # Should emit for unions with more than two cases
41 |-field12: Literal[1] | Literal[2] | Literal[3] # Error
41 |+field12: Literal[1, 2, 3] # Error
42 42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
43 43 |
44 44 | # Should emit for unions with more than two cases, even if not directly adjacent
PYI030.pyi:42:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
40 | # Should emit for unions with more than two cases
41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
@@ -141,7 +271,17 @@ PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
Safe fix
39 39 |
40 40 | # Should emit for unions with more than two cases
41 41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
42 |-field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
42 |+field13: Literal[1, 2, 3, 4] # Error
43 43 |
44 44 | # Should emit for unions with more than two cases, even if not directly adjacent
45 45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error
PYI030.pyi:45:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
|
44 | # Should emit for unions with more than two cases, even if not directly adjacent
45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error
@@ -151,7 +291,17 @@ PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo", True]`
Safe fix
42 42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
43 43 |
44 44 | # Should emit for unions with more than two cases, even if not directly adjacent
45 |-field14: Literal[1] | Literal[2] | str | Literal[3] # Error
45 |+field14: Literal[1, 2, 3] | str # Error
46 46 |
47 47 | # Should emit for unions with mixed literal internal types
48 48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error
PYI030.pyi:48:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo", True]`
|
47 | # Should emit for unions with mixed literal internal types
48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error
@@ -161,7 +311,17 @@ PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:51:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 1]`
Safe fix
45 45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error
46 46 |
47 47 | # Should emit for unions with mixed literal internal types
48 |-field15: Literal[1] | Literal["foo"] | Literal[True] # Error
48 |+field15: Literal[1, "foo", True] # Error
49 49 |
50 50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
51 51 | field16: Literal[1] | Literal[1] # OK
PYI030.pyi:51:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 1]`
|
50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
51 | field16: Literal[1] | Literal[1] # OK
@@ -171,7 +331,17 @@ PYI030.pyi:51:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:60:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
48 48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error
49 49 |
50 50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
51 |-field16: Literal[1] | Literal[1] # OK
51 |+field16: Literal[1, 1] # OK
52 52 |
53 53 | # Shouldn't emit if in new parent type
54 54 | field17: Literal[1] | dict[Literal[2], str] # OK
PYI030.pyi:60:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
59 | # Should respect name of literal type used
60 | field19: typing.Literal[1] | typing.Literal[2] # Error
@@ -181,7 +351,17 @@ PYI030.pyi:60:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:63:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
57 57 | field18: dict[Literal[1], Literal[2]] # OK
58 58 |
59 59 | # Should respect name of literal type used
60 |-field19: typing.Literal[1] | typing.Literal[2] # Error
60 |+field19: typing.Literal[1, 2] # Error
61 61 |
62 62 | # Should emit in cases with newlines
63 63 | field20: typing.Union[
PYI030.pyi:63:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
62 | # Should emit in cases with newlines
63 | field20: typing.Union[
@@ -197,7 +377,22 @@ PYI030.pyi:63:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:71:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
Safe fix
60 60 | field19: typing.Literal[1] | typing.Literal[2] # Error
61 61 |
62 62 | # Should emit in cases with newlines
63 |-field20: typing.Union[
64 |- Literal[
65 |- 1 # test
66 |- ],
67 |- Literal[2],
68 |-] # Error, newline and comment will not be emitted in message
63 |+field20: Literal[1, 2] # Error, newline and comment will not be emitted in message
69 64 |
70 65 | # Should handle multiple unions with multiple members
71 66 | field21: Literal[1, 2] | Literal[3, 4] # Error
PYI030.pyi:71:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
70 | # Should handle multiple unions with multiple members
71 | field21: Literal[1, 2] | Literal[3, 4] # Error
@@ -207,7 +402,17 @@ PYI030.pyi:71:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:74:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
68 68 | ] # Error, newline and comment will not be emitted in message
69 69 |
70 70 | # Should handle multiple unions with multiple members
71 |-field21: Literal[1, 2] | Literal[3, 4] # Error
71 |+field21: Literal[1, 2, 3, 4] # Error
72 72 |
73 73 | # Should emit in cases with `typing.Union` instead of `|`
74 74 | field22: typing.Union[Literal[1], Literal[2]] # Error
PYI030.pyi:74:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
73 | # Should emit in cases with `typing.Union` instead of `|`
74 | field22: typing.Union[Literal[1], Literal[2]] # Error
@@ -217,7 +422,17 @@ PYI030.pyi:74:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:77:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
71 71 | field21: Literal[1, 2] | Literal[3, 4] # Error
72 72 |
73 73 | # Should emit in cases with `typing.Union` instead of `|`
74 |-field22: typing.Union[Literal[1], Literal[2]] # Error
74 |+field22: Literal[1, 2] # Error
75 75 |
76 76 | # Should emit in cases with `typing_extensions.Literal`
77 77 | field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
PYI030.pyi:77:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
76 | # Should emit in cases with `typing_extensions.Literal`
77 | field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
@@ -227,7 +442,17 @@ PYI030.pyi:77:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:80:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
74 74 | field22: typing.Union[Literal[1], Literal[2]] # Error
75 75 |
76 76 | # Should emit in cases with `typing_extensions.Literal`
77 |-field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
77 |+field23: typing_extensions.Literal[1, 2] # Error
78 78 |
79 79 | # Should emit in cases with nested `typing.Union`
80 80 | field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
PYI030.pyi:80:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
79 | # Should emit in cases with nested `typing.Union`
80 | field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
@@ -237,7 +462,17 @@ PYI030.pyi:80:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:83:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Safe fix
77 77 | field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
78 78 |
79 79 | # Should emit in cases with nested `typing.Union`
80 |-field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
80 |+field24: typing.Union[Literal[1, 2], str] # Error
81 81 |
82 82 | # Should emit in cases with mixed `typing.Union` and `|`
83 83 | field25: typing.Union[Literal[1], Literal[2] | str] # Error
PYI030.pyi:83:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
82 | # Should emit in cases with mixed `typing.Union` and `|`
83 | field25: typing.Union[Literal[1], Literal[2] | str] # Error
@@ -247,7 +482,17 @@ PYI030.pyi:83:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:86:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
Safe fix
80 80 | field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
81 81 |
82 82 | # Should emit in cases with mixed `typing.Union` and `|`
83 |-field25: typing.Union[Literal[1], Literal[2] | str] # Error
83 |+field25: typing.Union[Literal[1, 2], str] # Error
84 84 |
85 85 | # Should emit only once in cases with multiple nested `typing.Union`
86 86 | field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
PYI030.pyi:86:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
85 | # Should emit only once in cases with multiple nested `typing.Union`
86 | field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
@@ -257,7 +502,17 @@ PYI030.pyi:86:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
PYI030.pyi:89:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
Safe fix
83 83 | field25: typing.Union[Literal[1], Literal[2] | str] # Error
84 84 |
85 85 | # Should emit only once in cases with multiple nested `typing.Union`
86 |-field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
86 |+field24: Literal[1, 2, 3, 4] # Error
87 87 |
88 88 | # Should use the first literal subscript attribute when fixing
89 89 | field25: typing.Union[typing_extensions.Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]], str] # Error
PYI030.pyi:89:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
88 | # Should use the first literal subscript attribute when fixing
89 | field25: typing.Union[typing_extensions.Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]], str] # Error
@@ -265,4 +520,11 @@ PYI030.pyi:89:10: PYI030 Multiple literal members in a union. Use a single liter
|
= help: Replace with a single `Literal`
Safe fix
86 86 | field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
87 87 |
88 88 | # Should use the first literal subscript attribute when fixing
89 |-field25: typing.Union[typing_extensions.Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]], str] # Error
89 |+field25: typing.Union[typing_extensions.Literal[1, 2, 3, 4], str] # Error

View File

@@ -24,7 +24,7 @@ use super::helpers::{
/// ## What it does
/// Checks for argument-free `@pytest.fixture()` decorators with or without
/// parentheses, depending on the `flake8-pytest-style.fixture-parentheses`
/// parentheses, depending on the [`lint.flake8-pytest-style.fixture-parentheses`]
/// setting.
///
/// ## Why is this bad?
@@ -55,7 +55,7 @@ use super::helpers::{
/// ```
///
/// ## Options
/// - `flake8-pytest-style.fixture-parentheses`
/// - `lint.flake8-pytest-style.fixture-parentheses`
///
/// ## References
/// - [`pytest` documentation: API Reference: Fixtures](https://docs.pytest.org/en/latest/reference/reference.html#fixtures-api)

View File

@@ -11,7 +11,7 @@ use super::helpers::get_mark_decorators;
/// ## What it does
/// Checks for argument-free `@pytest.mark.<marker>()` decorators with or
/// without parentheses, depending on the `flake8-pytest-style.mark-parentheses`
/// without parentheses, depending on the [`lint.flake8-pytest-style.mark-parentheses`]
/// setting.
///
/// ## Why is this bad?
@@ -42,7 +42,7 @@ use super::helpers::get_mark_decorators;
/// ```
///
/// ## Options
/// - `flake8-pytest-style.mark-parentheses`
/// - `lint.flake8-pytest-style.mark-parentheses`
///
/// ## References
/// - [`pytest` documentation: Marks](https://docs.pytest.org/en/latest/reference/reference.html#marks)

View File

@@ -26,7 +26,7 @@ use super::helpers::{is_pytest_parametrize, split_names};
/// The `argnames` argument of `pytest.mark.parametrize` takes a string or
/// a sequence of strings. For a single parameter, it's preferable to use a
/// string. For multiple parameters, it's preferable to use the style
/// configured via the [`flake8-pytest-style.parametrize-names-type`] setting.
/// configured via the [`lint.flake8-pytest-style.parametrize-names-type`] setting.
///
/// ## Example
/// ```python
@@ -67,7 +67,7 @@ use super::helpers::{is_pytest_parametrize, split_names};
/// ```
///
/// ## Options
/// - `flake8-pytest-style.parametrize-names-type`
/// - `lint.flake8-pytest-style.parametrize-names-type`
///
/// ## References
/// - [`pytest` documentation: How to parametrize fixtures and test functions](https://docs.pytest.org/en/latest/how-to/parametrize.html#pytest-mark-parametrize)
@@ -103,17 +103,17 @@ impl Violation for PytestParametrizeNamesWrongType {
/// of values.
///
/// The style for the list of values rows can be configured via the
/// the [`flake8-pytest-style.parametrize-values-type`] setting, while the
/// the [`lint.flake8-pytest-style.parametrize-values-type`] setting, while the
/// style for each row of values can be configured via the
/// the [`flake8-pytest-style.parametrize-values-row-type`] setting.
/// the [`lint.flake8-pytest-style.parametrize-values-row-type`] setting.
///
/// For example, [`flake8-pytest-style.parametrize-values-type`] will lead to
/// For example, [`lint.flake8-pytest-style.parametrize-values-type`] will lead to
/// the following expectations:
///
/// - `tuple`: `@pytest.mark.parametrize("value", ("a", "b", "c"))`
/// - `list`: `@pytest.mark.parametrize("value", ["a", "b", "c"])`
///
/// Similarly, [`flake8-pytest-style.parametrize-values-row-type`] will lead to
/// Similarly, [`lint.flake8-pytest-style.parametrize-values-row-type`] will lead to
/// the following expectations:
///
/// - `tuple`: `@pytest.mark.parametrize(("key", "value"), [("a", "b"), ("c", "d")])`
@@ -170,8 +170,8 @@ impl Violation for PytestParametrizeNamesWrongType {
/// ```
///
/// ## Options
/// - `flake8-pytest-style.parametrize-values-type`
/// - `flake8-pytest-style.parametrize-values-row-type`
/// - `lint.flake8-pytest-style.parametrize-values-type`
/// - `lint.flake8-pytest-style.parametrize-values-row-type`
///
/// ## References
/// - [`pytest` documentation: How to parametrize fixtures and test functions](https://docs.pytest.org/en/latest/how-to/parametrize.html#pytest-mark-parametrize)
@@ -226,6 +226,10 @@ impl Violation for PytestParametrizeValuesWrongType {
/// ...
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as tests that rely on mutable global
/// state may be affected by removing duplicate test cases.
///
/// ## References
/// - [`pytest` documentation: How to parametrize fixtures and test functions](https://docs.pytest.org/en/latest/how-to/parametrize.html#pytest-mark-parametrize)
#[violation]

View File

@@ -64,8 +64,8 @@ impl Violation for PytestRaisesWithMultipleStatements {
/// unrelated to the code under test. To avoid this, `pytest.raises` should be
/// called with a `match` parameter. The exception names that require a `match`
/// parameter can be configured via the
/// `flake8-pytest-style.raises-require-match-for` and
/// `flake8-pytest-style.raises-extend-require-match-for` settings.
/// [`lint.flake8-pytest-style.raises-require-match-for`] and
/// [`lint.flake8-pytest-style.raises-extend-require-match-for`] settings.
///
/// ## Example
/// ```python
@@ -92,8 +92,8 @@ impl Violation for PytestRaisesWithMultipleStatements {
/// ```
///
/// ## Options
/// - `flake8-pytest-style.raises-require-match-for`
/// - `flake8-pytest-style.raises-extend-require-match-for`
/// - `lint.flake8-pytest-style.raises-require-match-for`
/// - `lint.flake8-pytest-style.raises-extend-require-match-for`
///
/// ## References
/// - [`pytest` documentation: `pytest.raises`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-raises)

View File

@@ -14,7 +14,7 @@ use super::super::settings::Quote;
/// ## What it does
/// Checks for inline strings that use single quotes or double quotes,
/// depending on the value of the [`flake8-quotes.inline-quotes`] option.
/// depending on the value of the [`lint.flake8-quotes.inline-quotes`] option.
///
/// ## Why is this bad?
/// Consistency is good. Use either single or double quotes for inline
@@ -31,7 +31,7 @@ use super::super::settings::Quote;
/// ```
///
/// ## Options
/// - `flake8-quotes.inline-quotes`
/// - `lint.flake8-quotes.inline-quotes`
///
/// ## Formatter compatibility
/// We recommend against using this rule alongside the [formatter]. The
@@ -65,7 +65,7 @@ impl AlwaysFixableViolation for BadQuotesInlineString {
/// ## What it does
/// Checks for multiline strings that use single quotes or double quotes,
/// depending on the value of the [`flake8-quotes.multiline-quotes`]
/// depending on the value of the [`lint.flake8-quotes.multiline-quotes`]
/// setting.
///
/// ## Why is this bad?
@@ -87,7 +87,7 @@ impl AlwaysFixableViolation for BadQuotesInlineString {
/// ```
///
/// ## Options
/// - `flake8-quotes.multiline-quotes`
/// - `lint.flake8-quotes.multiline-quotes`
///
/// ## Formatter compatibility
/// We recommend against using this rule alongside the [formatter]. The
@@ -121,7 +121,7 @@ impl AlwaysFixableViolation for BadQuotesMultilineString {
/// ## What it does
/// Checks for docstrings that use single quotes or double quotes, depending
/// on the value of the [`flake8-quotes.docstring-quotes`] setting.
/// on the value of the [`lint.flake8-quotes.docstring-quotes`] setting.
///
/// ## Why is this bad?
/// Consistency is good. Use either single or double quotes for docstring
@@ -142,7 +142,7 @@ impl AlwaysFixableViolation for BadQuotesMultilineString {
/// ```
///
/// ## Options
/// - `flake8-quotes.docstring-quotes`
/// - `lint.flake8-quotes.docstring-quotes`
///
/// ## Formatter compatibility
/// We recommend against using this rule alongside the [formatter]. The

View File

@@ -11,10 +11,11 @@ mod tests {
use anyhow::Result;
use test_case::test_case;
use crate::assert_messages;
use crate::registry::Rule;
use crate::settings::types::PreviewMode;
use crate::settings::LinterSettings;
use crate::test::test_path;
use crate::{assert_messages, settings};
#[test_case(Rule::UnnecessaryReturnNone, Path::new("RET501.py"))]
#[test_case(Rule::ImplicitReturnValue, Path::new("RET502.py"))]
@@ -33,4 +34,25 @@ mod tests {
assert_messages!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::SuperfluousElseReturn, Path::new("RET505.py"))]
#[test_case(Rule::SuperfluousElseRaise, Path::new("RET506.py"))]
#[test_case(Rule::SuperfluousElseContinue, Path::new("RET507.py"))]
#[test_case(Rule::SuperfluousElseBreak, Path::new("RET508.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_return").join(path).as_path(),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -1,9 +1,10 @@
use anyhow::Result;
use std::ops::Add;
use ruff_python_ast::{self as ast, ElifElseClause, Expr, Stmt};
use ruff_text_size::{Ranged, TextRange, TextSize};
use ruff_diagnostics::{AlwaysFixableViolation, Violation};
use ruff_diagnostics::{AlwaysFixableViolation, FixAvailability, Violation};
use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
@@ -11,13 +12,17 @@ use ruff_python_ast::helpers::{is_const_false, is_const_true};
use ruff_python_ast::stmt_if::elif_else_range;
use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::whitespace::indentation;
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_semantic::SemanticModel;
use ruff_python_trivia::is_python_whitespace;
use ruff_python_trivia::{is_python_whitespace, SimpleTokenKind, SimpleTokenizer};
use ruff_source_file::Locator;
use crate::checkers::ast::Checker;
use crate::fix::edits;
use crate::registry::{AsRule, Rule};
use crate::rules::flake8_return::helpers::end_of_last_statement;
use crate::rules::pyupgrade::fixes::adjust_indentation;
use super::super::branch::Branch;
use super::super::helpers::result_exists;
@@ -210,11 +215,17 @@ pub struct SuperfluousElseReturn {
}
impl Violation for SuperfluousElseReturn {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let SuperfluousElseReturn { branch } = self;
format!("Unnecessary `{branch}` after `return` statement")
}
fn fix_title(&self) -> Option<String> {
let SuperfluousElseReturn { branch } = self;
Some(format!("Remove unnecessary `{branch}`"))
}
}
/// ## What it does
@@ -248,11 +259,17 @@ pub struct SuperfluousElseRaise {
}
impl Violation for SuperfluousElseRaise {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let SuperfluousElseRaise { branch } = self;
format!("Unnecessary `{branch}` after `raise` statement")
}
fn fix_title(&self) -> Option<String> {
let SuperfluousElseRaise { branch } = self;
Some(format!("Remove unnecessary `{branch}`"))
}
}
/// ## What it does
@@ -288,11 +305,17 @@ pub struct SuperfluousElseContinue {
}
impl Violation for SuperfluousElseContinue {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let SuperfluousElseContinue { branch } = self;
format!("Unnecessary `{branch}` after `continue` statement")
}
fn fix_title(&self) -> Option<String> {
let SuperfluousElseContinue { branch } = self;
Some(format!("Remove unnecessary `{branch}`"))
}
}
/// ## What it does
@@ -328,11 +351,17 @@ pub struct SuperfluousElseBreak {
}
impl Violation for SuperfluousElseBreak {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let SuperfluousElseBreak { branch } = self;
format!("Unnecessary `{branch}` after `break` statement")
}
fn fix_title(&self) -> Option<String> {
let SuperfluousElseBreak { branch } = self;
Some(format!("Remove unnecessary `{branch}`"))
}
}
/// RET501
@@ -368,9 +397,11 @@ fn implicit_return_value(checker: &mut Checker, stack: &Stack) {
}
}
/// Return `true` if the `func` is a known function that never returns.
/// Return `true` if the `func` appears to be non-returning.
fn is_noreturn_func(func: &Expr, semantic: &SemanticModel) -> bool {
semantic.resolve_call_path(func).is_some_and(|call_path| {
// First, look for known functions that never return from the standard library and popular
// libraries.
if semantic.resolve_call_path(func).is_some_and(|call_path| {
matches!(
call_path.as_slice(),
["" | "builtins" | "sys" | "_thread" | "pytest", "exit"]
@@ -379,7 +410,32 @@ fn is_noreturn_func(func: &Expr, semantic: &SemanticModel) -> bool {
| ["_winapi", "ExitProcess"]
| ["pytest", "fail" | "skip" | "xfail"]
) || semantic.match_typing_call_path(&call_path, "assert_never")
})
}) {
return true;
}
// Second, look for `NoReturn` annotations on the return type.
let Some(func_binding) = semantic.lookup_attribute(func) else {
return false;
};
let Some(node_id) = semantic.binding(func_binding).source else {
return false;
};
let Stmt::FunctionDef(ast::StmtFunctionDef { returns, .. }) = semantic.statement(node_id)
else {
return false;
};
let Some(returns) = returns.as_ref() else {
return false;
};
let Some(call_path) = semantic.resolve_call_path(returns) else {
return false;
};
semantic.match_typing_call_path(&call_path, "NoReturn")
}
/// RET503
@@ -575,42 +631,82 @@ fn superfluous_else_node(
};
for child in if_elif_body {
if child.is_return_stmt() {
let diagnostic = Diagnostic::new(
let mut diagnostic = Diagnostic::new(
SuperfluousElseReturn { branch },
elif_else_range(elif_else, checker.locator().contents())
.unwrap_or_else(|| elif_else.range()),
);
if checker.enabled(diagnostic.kind.rule()) {
if checker.settings.preview.is_enabled() {
diagnostic.try_set_fix(|| {
remove_else(
elif_else,
checker.locator(),
checker.indexer(),
checker.stylist(),
)
});
}
checker.diagnostics.push(diagnostic);
}
return true;
} else if child.is_break_stmt() {
let diagnostic = Diagnostic::new(
let mut diagnostic = Diagnostic::new(
SuperfluousElseBreak { branch },
elif_else_range(elif_else, checker.locator().contents())
.unwrap_or_else(|| elif_else.range()),
);
if checker.enabled(diagnostic.kind.rule()) {
if checker.settings.preview.is_enabled() {
diagnostic.try_set_fix(|| {
remove_else(
elif_else,
checker.locator(),
checker.indexer(),
checker.stylist(),
)
});
}
checker.diagnostics.push(diagnostic);
}
return true;
} else if child.is_raise_stmt() {
let diagnostic = Diagnostic::new(
let mut diagnostic = Diagnostic::new(
SuperfluousElseRaise { branch },
elif_else_range(elif_else, checker.locator().contents())
.unwrap_or_else(|| elif_else.range()),
);
if checker.enabled(diagnostic.kind.rule()) {
if checker.settings.preview.is_enabled() {
diagnostic.try_set_fix(|| {
remove_else(
elif_else,
checker.locator(),
checker.indexer(),
checker.stylist(),
)
});
}
checker.diagnostics.push(diagnostic);
}
return true;
} else if child.is_continue_stmt() {
let diagnostic = Diagnostic::new(
let mut diagnostic = Diagnostic::new(
SuperfluousElseContinue { branch },
elif_else_range(elif_else, checker.locator().contents())
.unwrap_or_else(|| elif_else.range()),
);
if checker.enabled(diagnostic.kind.rule()) {
if checker.settings.preview.is_enabled() {
diagnostic.try_set_fix(|| {
remove_else(
elif_else,
checker.locator(),
checker.indexer(),
checker.stylist(),
)
});
}
checker.diagnostics.push(diagnostic);
}
return true;
@@ -641,7 +737,7 @@ pub(crate) fn function(checker: &mut Checker, body: &[Stmt], returns: Option<&Ex
// Traverse the function body, to collect the stack.
let stack = {
let mut visitor = ReturnVisitor::default();
let mut visitor = ReturnVisitor::new(checker.semantic());
for stmt in body {
visitor.visit_stmt(stmt);
}
@@ -688,3 +784,83 @@ pub(crate) fn function(checker: &mut Checker, body: &[Stmt], returns: Option<&Ex
}
}
}
/// Generate a [`Fix`] to remove an `else` or `elif` clause.
fn remove_else(
elif_else: &ElifElseClause,
locator: &Locator,
indexer: &Indexer,
stylist: &Stylist,
) -> Result<Fix> {
if elif_else.test.is_some() {
// Ex) `elif` -> `if`
Ok(Fix::safe_edit(Edit::deletion(
elif_else.start(),
elif_else.start() + TextSize::from(2),
)))
} else {
// the start of the line where the `else`` is
let else_line_start = locator.line_start(elif_else.start());
// making a tokenizer to find the Colon for the `else`, not always on the same line!
let mut else_line_tokenizer =
SimpleTokenizer::starts_at(elif_else.start(), locator.contents());
// find the Colon for the `else`
let Some(else_colon) =
else_line_tokenizer.find(|token| token.kind == SimpleTokenKind::Colon)
else {
return Err(anyhow::anyhow!("Cannot find `:` in `else` statement"));
};
// get the indentation of the `else`, since that is the indent level we want to end with
let Some(desired_indentation) = indentation(locator, elif_else) else {
return Err(anyhow::anyhow!("Compound statement cannot be inlined"));
};
// If the statement is on the same line as the `else`, just remove the `else: `.
// Ex) `else: return True` -> `return True`
let Some(first) = elif_else.body.first() else {
return Err(anyhow::anyhow!("`else` statement has no body"));
};
if indexer.in_multi_statement_line(first, locator) {
return Ok(Fix::safe_edit(Edit::deletion(
elif_else.start(),
first.start(),
)));
}
// we're deleting the `else`, and it's Colon, and the rest of the line(s) they're on,
// so here we get the last position of the line the Colon is on
let else_colon_end = locator.full_line_end(else_colon.end());
// if there is a comment on the same line as the Colon, let's keep it
// and give it the proper indentation once we unindent it
let else_comment_after_colon = else_line_tokenizer
.find(|token| token.kind.is_comment())
.and_then(|token| {
if token.kind == SimpleTokenKind::Comment && token.start() < else_colon_end {
return Some(format!(
"{desired_indentation}{}{}",
locator.slice(token),
stylist.line_ending().as_str(),
));
}
None
})
.unwrap_or(String::new());
let indented = adjust_indentation(
TextRange::new(else_colon_end, elif_else.end()),
desired_indentation,
locator,
stylist,
)?;
Ok(Fix::safe_edit(Edit::replacement(
format!("{else_comment_after_colon}{indented}"),
else_line_start,
elif_else.end(),
)))
}
}

View File

@@ -1,403 +1,456 @@
---
source: crates/ruff_linter/src/rules/flake8_return/mod.rs
---
RET503.py:20:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:21:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
18 | # if/elif/else
19 | def x(y):
20 | if not y:
19 | # if/elif/else
20 | def x(y):
21 | if not y:
| _____^
21 | | return 1
22 | | return 1
| |________________^ RET503
22 | # error
23 | # error
|
= help: Add explicit `return` statement
Unsafe fix
19 19 | def x(y):
20 20 | if not y:
21 21 | return 1
22 |+ return None
22 23 | # error
23 24 |
20 20 | def x(y):
21 21 | if not y:
22 22 | return 1
23 |+ return None
23 24 | # error
24 25 |
25 26 |
RET503.py:27:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:28:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
25 | def x(y):
26 | if not y:
27 | print() # error
26 | def x(y):
27 | if not y:
28 | print() # error
| ^^^^^^^ RET503
28 | else:
29 | return 2
29 | else:
30 | return 2
|
= help: Add explicit `return` statement
Unsafe fix
25 25 | def x(y):
26 26 | if not y:
27 27 | print() # error
28 |+ return None
28 29 | else:
29 30 | return 2
30 31 |
26 26 | def x(y):
27 27 | if not y:
28 28 | print() # error
29 |+ return None
29 30 | else:
30 31 | return 2
31 32 |
RET503.py:36:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:37:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
34 | return 1
35 |
36 | print() # error
35 | return 1
36 |
37 | print() # error
| ^^^^^^^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
34 34 | return 1
35 35 |
36 36 | print() # error
37 |+ return None
37 38 |
35 35 | return 1
36 36 |
37 37 | print() # error
38 |+ return None
38 39 |
39 40 | # for
39 40 |
40 41 | # for
RET503.py:41:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:42:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
39 | # for
40 | def x(y):
41 | for i in range(10):
40 | # for
41 | def x(y):
42 | for i in range(10):
| _____^
42 | | if i > 10:
43 | | return i
43 | | if i > 10:
44 | | return i
| |____________________^ RET503
44 | # error
45 | # error
|
= help: Add explicit `return` statement
Unsafe fix
41 41 | for i in range(10):
42 42 | if i > 10:
43 43 | return i
44 |+ return None
44 45 | # error
45 46 |
42 42 | for i in range(10):
43 43 | if i > 10:
44 44 | return i
45 |+ return None
45 46 | # error
46 47 |
47 48 |
RET503.py:52:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:53:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
50 | return i
51 | else:
52 | print() # error
51 | return i
52 | else:
53 | print() # error
| ^^^^^^^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
50 50 | return i
51 51 | else:
52 52 | print() # error
53 |+ return None
53 54 |
51 51 | return i
52 52 | else:
53 53 | print() # error
54 |+ return None
54 55 |
55 56 | # A nonexistent function
55 56 |
56 57 | # A nonexistent function
RET503.py:59:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:60:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
57 | if x > 0:
58 | return False
59 | no_such_function() # error
58 | if x > 0:
59 | return False
60 | no_such_function() # error
| ^^^^^^^^^^^^^^^^^^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
57 57 | if x > 0:
58 58 | return False
59 59 | no_such_function() # error
60 |+ return None
60 61 |
58 58 | if x > 0:
59 59 | return False
60 60 | no_such_function() # error
61 |+ return None
61 62 |
62 63 | # A function that does return the control
62 63 |
63 64 | # A function that does return the control
RET503.py:66:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:67:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
64 | if x > 0:
65 | return False
66 | print("", end="") # error
65 | if x > 0:
66 | return False
67 | print("", end="") # error
| ^^^^^^^^^^^^^^^^^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
64 64 | if x > 0:
65 65 | return False
66 66 | print("", end="") # error
67 |+ return None
67 68 |
65 65 | if x > 0:
66 66 | return False
67 67 | print("", end="") # error
68 |+ return None
68 69 |
69 70 | ###
69 70 |
70 71 | ###
RET503.py:82:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:83:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
80 | # last line in while loop
81 | def x(y):
82 | while i > 0:
81 | # last line in while loop
82 | def x(y):
83 | while i > 0:
| _____^
83 | | if y > 0:
84 | | return 1
85 | | y += 1
84 | | if y > 0:
85 | | return 1
86 | | y += 1
| |______________^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
83 83 | if y > 0:
84 84 | return 1
85 85 | y += 1
86 |+ return None
86 87 |
84 84 | if y > 0:
85 85 | return 1
86 86 | y += 1
87 |+ return None
87 88 |
88 89 | # exclude empty functions
88 89 |
89 90 | # exclude empty functions
RET503.py:113:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:114:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
111 | # return value within loop
112 | def bar1(x, y, z):
113 | for i in x:
112 | # return value within loop
113 | def bar1(x, y, z):
114 | for i in x:
| _____^
114 | | if i > y:
115 | | break
116 | | return z
115 | | if i > y:
116 | | break
117 | | return z
| |________________^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
114 114 | if i > y:
115 115 | break
116 116 | return z
117 |+ return None
117 118 |
115 115 | if i > y:
116 116 | break
117 117 | return z
118 |+ return None
118 119 |
119 120 | def bar3(x, y, z):
119 120 |
120 121 | def bar3(x, y, z):
RET503.py:120:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:121:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
119 | def bar3(x, y, z):
120 | for i in x:
120 | def bar3(x, y, z):
121 | for i in x:
| _____^
121 | | if i > y:
122 | | if z:
123 | | break
124 | | else:
125 | | return z
126 | | return None
122 | | if i > y:
123 | | if z:
124 | | break
125 | | else:
126 | | return z
127 | | return None
| |___________________^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
124 124 | else:
125 125 | return z
126 126 | return None
127 |+ return None
127 128 |
125 125 | else:
126 126 | return z
127 127 | return None
128 |+ return None
128 129 |
129 130 | def bar1(x, y, z):
129 130 |
130 131 | def bar1(x, y, z):
RET503.py:130:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:131:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
129 | def bar1(x, y, z):
130 | for i in x:
130 | def bar1(x, y, z):
131 | for i in x:
| _____^
131 | | if i < y:
132 | | continue
133 | | return z
132 | | if i < y:
133 | | continue
134 | | return z
| |________________^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
131 131 | if i < y:
132 132 | continue
133 133 | return z
134 |+ return None
134 135 |
132 132 | if i < y:
133 133 | continue
134 134 | return z
135 |+ return None
135 136 |
136 137 | def bar3(x, y, z):
136 137 |
137 138 | def bar3(x, y, z):
RET503.py:137:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:138:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
136 | def bar3(x, y, z):
137 | for i in x:
137 | def bar3(x, y, z):
138 | for i in x:
| _____^
138 | | if i < y:
139 | | if z:
140 | | continue
141 | | else:
142 | | return z
143 | | return None
139 | | if i < y:
140 | | if z:
141 | | continue
142 | | else:
143 | | return z
144 | | return None
| |___________________^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
141 141 | else:
142 142 | return z
143 143 | return None
144 |+ return None
144 145 |
142 142 | else:
143 143 | return z
144 144 | return None
145 |+ return None
145 146 |
146 147 | def prompts(self, foo):
146 147 |
147 148 | def prompts(self, foo):
RET503.py:274:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:275:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
272 | return False
273 |
274 | for value in values:
273 | return False
274 |
275 | for value in values:
| _____^
275 | | print(value)
276 | | print(value)
| |____________________^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
273 273 |
274 274 | for value in values:
275 275 | print(value)
276 |+ return None
276 277 |
274 274 |
275 275 | for value in values:
276 276 | print(value)
277 |+ return None
277 278 |
278 279 | def while_true():
278 279 |
279 280 | def while_true():
RET503.py:291:13: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:292:13: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
289 | return 1
290 | case 1:
291 | print() # error
290 | return 1
291 | case 1:
292 | print() # error
| ^^^^^^^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
289 289 | return 1
290 290 | case 1:
291 291 | print() # error
292 |+ return None
292 293 |
290 290 | return 1
291 291 | case 1:
292 292 | print() # error
293 |+ return None
293 294 |
294 295 | def foo(baz: str) -> str:
294 295 |
295 296 | def foo(baz: str) -> str:
RET503.py:300:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:301:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
298 | def end_of_statement():
299 | def example():
300 | if True:
299 | def end_of_statement():
300 | def example():
301 | if True:
| _________^
301 | | return ""
302 | | return ""
| |_____________________^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
299 299 | def example():
300 300 | if True:
301 301 | return ""
302 |+ return None
302 303 |
300 300 | def example():
301 301 | if True:
302 302 | return ""
303 |+ return None
303 304 |
304 305 | def example():
304 305 |
305 306 | def example():
RET503.py:305:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:306:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
304 | def example():
305 | if True:
305 | def example():
306 | if True:
| _________^
306 | | return ""
307 | | return ""
| |_____________________^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
304 304 | def example():
305 305 | if True:
306 306 | return ""
307 |+ return None
307 308 |
305 305 | def example():
306 306 | if True:
307 307 | return ""
308 |+ return None
308 309 |
309 310 | def example():
309 310 |
310 311 | def example():
RET503.py:310:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:311:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
309 | def example():
310 | if True:
310 | def example():
311 | if True:
| _________^
311 | | return "" # type: ignore
312 | | return "" # type: ignore
| |_____________________^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
309 309 | def example():
310 310 | if True:
311 311 | return "" # type: ignore
312 |+ return None
312 313 |
310 310 | def example():
311 311 | if True:
312 312 | return "" # type: ignore
313 |+ return None
313 314 |
314 315 | def example():
314 315 |
315 316 | def example():
RET503.py:315:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:316:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
314 | def example():
315 | if True:
315 | def example():
316 | if True:
| _________^
316 | | return "" ;
317 | | return "" ;
| |_____________________^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
314 314 | def example():
315 315 | if True:
316 316 | return "" ;
317 |+ return None
317 318 |
315 315 | def example():
316 316 | if True:
317 317 | return "" ;
318 |+ return None
318 319 |
319 320 | def example():
319 320 |
320 321 | def example():
RET503.py:320:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:321:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
319 | def example():
320 | if True:
320 | def example():
321 | if True:
| _________^
321 | | return "" \
322 | | return "" \
| |_____________________^ RET503
322 | ; # type: ignore
323 | ; # type: ignore
|
= help: Add explicit `return` statement
Unsafe fix
320 320 | if True:
321 321 | return "" \
322 322 | ; # type: ignore
323 |+ return None
323 324 |
321 321 | if True:
322 322 | return "" \
323 323 | ; # type: ignore
324 |+ return None
324 325 |
325 326 | def end_of_file():
325 326 |
326 327 | def end_of_file():
RET503.py:328:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:329:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
326 | if False:
327 | return 1
328 | x = 2 \
327 | if False:
328 | return 1
329 | x = 2 \
| ^^^^^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
326 326 | if False:
327 327 | return 1
328 328 | x = 2 \
329 |+
330 |+ return None
328 328 | return 1
329 329 | x = 2 \
330 330 |
331 |+ return None
331 332 |
332 333 |
333 334 | # function return type annotation NoReturn
RET503.py:339:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
337 | if x == 5:
338 | return 5
339 | bar()
| ^^^^^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
337 337 | if x == 5:
338 338 | return 5
339 339 | bar()
340 |+ return None
340 341 |
341 342 |
342 343 | def foo(string: str) -> str:
RET503.py:354:13: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
352 | return "third"
353 | case _:
354 | raises(string)
| ^^^^^^^^^^^^^^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
352 352 | return "third"
353 353 | case _:
354 354 | raises(string)
355 |+ return None
355 356 |
356 357 |
357 358 | def foo() -> int:
RET503.py:370:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
368 | if baz() > 3:
369 | return 1
370 | bar()
| ^^^^^ RET503
|
= help: Add explicit `return` statement
Unsafe fix
368 368 | if baz() > 3:
369 369 | return 1
370 370 | bar()
371 |+ return None

View File

@@ -217,5 +217,28 @@ RET504.py:365:12: RET504 [*] Unnecessary assignment to `D` before `return` state
364 |- D=0.4853881 + 3.6006116*P - 0.0117368*(P-1.3822)**2
365 |- return D
364 |+ return 0.4853881 + 3.6006116*P - 0.0117368*(P-1.3822)**2
366 365 |
367 366 |
368 367 | # contextlib suppress in with statement
RET504.py:400:12: RET504 [*] Unnecessary assignment to `y` before `return` statement
|
398 | x = 1
399 | y = y + 2
400 | return y # RET504
| ^ RET504
|
= help: Remove unnecessary assignment
Unsafe fix
396 396 | x = 2
397 397 | with contextlib.suppress(Exception):
398 398 | x = 1
399 |- y = y + 2
400 |- return y # RET504
399 |+ return y + 2
401 400 |
402 401 |
403 402 | def foo():

View File

@@ -10,6 +10,7 @@ RET505.py:8:5: RET505 Unnecessary `elif` after `return` statement
9 | b = 2
10 | return w
|
= help: Remove unnecessary `elif`
RET505.py:23:5: RET505 Unnecessary `elif` after `return` statement
|
@@ -20,6 +21,7 @@ RET505.py:23:5: RET505 Unnecessary `elif` after `return` statement
24 | c = 2
25 | else:
|
= help: Remove unnecessary `elif`
RET505.py:41:5: RET505 Unnecessary `elif` after `return` statement
|
@@ -30,6 +32,7 @@ RET505.py:41:5: RET505 Unnecessary `elif` after `return` statement
42 | b = 2
43 | return w
|
= help: Remove unnecessary `elif`
RET505.py:53:5: RET505 Unnecessary `else` after `return` statement
|
@@ -40,6 +43,7 @@ RET505.py:53:5: RET505 Unnecessary `else` after `return` statement
54 | b = 2
55 | return z
|
= help: Remove unnecessary `else`
RET505.py:64:9: RET505 Unnecessary `else` after `return` statement
|
@@ -50,6 +54,7 @@ RET505.py:64:9: RET505 Unnecessary `else` after `return` statement
65 | c = 3
66 | return x
|
= help: Remove unnecessary `else`
RET505.py:79:5: RET505 Unnecessary `else` after `return` statement
|
@@ -60,6 +65,7 @@ RET505.py:79:5: RET505 Unnecessary `else` after `return` statement
80 | c = 3
81 | return
|
= help: Remove unnecessary `else`
RET505.py:89:9: RET505 Unnecessary `else` after `return` statement
|
@@ -70,6 +76,7 @@ RET505.py:89:9: RET505 Unnecessary `else` after `return` statement
90 | b = 2
91 | else:
|
= help: Remove unnecessary `else`
RET505.py:99:5: RET505 Unnecessary `else` after `return` statement
|
@@ -80,5 +87,68 @@ RET505.py:99:5: RET505 Unnecessary `else` after `return` statement
100 | try:
101 | return False
|
= help: Remove unnecessary `else`
RET505.py:137:5: RET505 Unnecessary `else` after `return` statement
|
135 | if True:
136 | return
137 | else:
| ^^^^ RET505
138 | # comment
139 | pass
|
= help: Remove unnecessary `else`
RET505.py:145:5: RET505 Unnecessary `else` after `return` statement
|
143 | if True:
144 | return
145 | else: # comment
| ^^^^ RET505
146 | pass
|
= help: Remove unnecessary `else`
RET505.py:152:5: RET505 Unnecessary `else` after `return` statement
|
150 | if True:
151 | return
152 | else\
| ^^^^ RET505
153 | :\
154 | # comment
|
= help: Remove unnecessary `else`
RET505.py:161:5: RET505 Unnecessary `else` after `return` statement
|
159 | if True:
160 | return
161 | else\
| ^^^^ RET505
162 | : # comment
163 | pass
|
= help: Remove unnecessary `else`
RET505.py:169:5: RET505 Unnecessary `else` after `return` statement
|
167 | if True:
168 | return
169 | else: pass
| ^^^^ RET505
|
= help: Remove unnecessary `else`
RET505.py:175:5: RET505 Unnecessary `else` after `return` statement
|
173 | if True:
174 | return
175 | else:\
| ^^^^ RET505
176 | pass
|
= help: Remove unnecessary `else`

View File

@@ -10,6 +10,7 @@ RET506.py:8:5: RET506 Unnecessary `elif` after `raise` statement
9 | b = 2
10 | raise Exception(w)
|
= help: Remove unnecessary `elif`
RET506.py:23:5: RET506 Unnecessary `elif` after `raise` statement
|
@@ -20,6 +21,7 @@ RET506.py:23:5: RET506 Unnecessary `elif` after `raise` statement
24 | raise Exception(y)
25 | else:
|
= help: Remove unnecessary `elif`
RET506.py:34:5: RET506 Unnecessary `else` after `raise` statement
|
@@ -30,6 +32,7 @@ RET506.py:34:5: RET506 Unnecessary `else` after `raise` statement
35 | b = 2
36 | raise Exception(z)
|
= help: Remove unnecessary `else`
RET506.py:45:9: RET506 Unnecessary `else` after `raise` statement
|
@@ -40,6 +43,7 @@ RET506.py:45:9: RET506 Unnecessary `else` after `raise` statement
46 | c = 3
47 | raise Exception(x)
|
= help: Remove unnecessary `else`
RET506.py:60:5: RET506 Unnecessary `else` after `raise` statement
|
@@ -50,6 +54,7 @@ RET506.py:60:5: RET506 Unnecessary `else` after `raise` statement
61 | c = 3
62 | raise Exception(y)
|
= help: Remove unnecessary `else`
RET506.py:70:9: RET506 Unnecessary `else` after `raise` statement
|
@@ -60,6 +65,7 @@ RET506.py:70:9: RET506 Unnecessary `else` after `raise` statement
71 | b = 2
72 | else:
|
= help: Remove unnecessary `else`
RET506.py:80:5: RET506 Unnecessary `else` after `raise` statement
|
@@ -70,5 +76,6 @@ RET506.py:80:5: RET506 Unnecessary `else` after `raise` statement
81 | try:
82 | raise Exception(False)
|
= help: Remove unnecessary `else`

View File

@@ -10,6 +10,7 @@ RET507.py:8:9: RET507 Unnecessary `elif` after `continue` statement
9 | continue
10 | else:
|
= help: Remove unnecessary `elif`
RET507.py:22:9: RET507 Unnecessary `elif` after `continue` statement
|
@@ -20,6 +21,7 @@ RET507.py:22:9: RET507 Unnecessary `elif` after `continue` statement
23 | c = 2
24 | else:
|
= help: Remove unnecessary `elif`
RET507.py:36:9: RET507 Unnecessary `else` after `continue` statement
|
@@ -29,6 +31,7 @@ RET507.py:36:9: RET507 Unnecessary `else` after `continue` statement
| ^^^^ RET507
37 | a = z
|
= help: Remove unnecessary `else`
RET507.py:47:13: RET507 Unnecessary `else` after `continue` statement
|
@@ -39,6 +42,7 @@ RET507.py:47:13: RET507 Unnecessary `else` after `continue` statement
48 | c = 3
49 | continue
|
= help: Remove unnecessary `else`
RET507.py:63:9: RET507 Unnecessary `else` after `continue` statement
|
@@ -49,6 +53,7 @@ RET507.py:63:9: RET507 Unnecessary `else` after `continue` statement
64 | c = 3
65 | continue
|
= help: Remove unnecessary `else`
RET507.py:74:13: RET507 Unnecessary `else` after `continue` statement
|
@@ -59,6 +64,7 @@ RET507.py:74:13: RET507 Unnecessary `else` after `continue` statement
75 | b = 2
76 | else:
|
= help: Remove unnecessary `else`
RET507.py:85:9: RET507 Unnecessary `else` after `continue` statement
|
@@ -69,5 +75,6 @@ RET507.py:85:9: RET507 Unnecessary `else` after `continue` statement
86 | try:
87 | return
|
= help: Remove unnecessary `else`

View File

@@ -10,6 +10,7 @@ RET508.py:8:9: RET508 Unnecessary `elif` after `break` statement
9 | break
10 | else:
|
= help: Remove unnecessary `elif`
RET508.py:22:9: RET508 Unnecessary `elif` after `break` statement
|
@@ -20,6 +21,7 @@ RET508.py:22:9: RET508 Unnecessary `elif` after `break` statement
23 | c = 2
24 | else:
|
= help: Remove unnecessary `elif`
RET508.py:33:9: RET508 Unnecessary `else` after `break` statement
|
@@ -29,6 +31,7 @@ RET508.py:33:9: RET508 Unnecessary `else` after `break` statement
| ^^^^ RET508
34 | a = z
|
= help: Remove unnecessary `else`
RET508.py:44:13: RET508 Unnecessary `else` after `break` statement
|
@@ -39,6 +42,7 @@ RET508.py:44:13: RET508 Unnecessary `else` after `break` statement
45 | c = 3
46 | break
|
= help: Remove unnecessary `else`
RET508.py:60:9: RET508 Unnecessary `else` after `break` statement
|
@@ -49,6 +53,7 @@ RET508.py:60:9: RET508 Unnecessary `else` after `break` statement
61 | c = 3
62 | break
|
= help: Remove unnecessary `else`
RET508.py:71:13: RET508 Unnecessary `else` after `break` statement
|
@@ -59,6 +64,7 @@ RET508.py:71:13: RET508 Unnecessary `else` after `break` statement
72 | b = 2
73 | else:
|
= help: Remove unnecessary `else`
RET508.py:82:9: RET508 Unnecessary `else` after `break` statement
|
@@ -69,6 +75,7 @@ RET508.py:82:9: RET508 Unnecessary `else` after `break` statement
83 | try:
84 | return
|
= help: Remove unnecessary `else`
RET508.py:158:13: RET508 Unnecessary `else` after `break` statement
|
@@ -78,5 +85,6 @@ RET508.py:158:13: RET508 Unnecessary `else` after `break` statement
| ^^^^ RET508
159 | a = z
|
= help: Remove unnecessary `else`

View File

@@ -0,0 +1,322 @@
---
source: crates/ruff_linter/src/rules/flake8_return/mod.rs
---
RET505.py:8:5: RET505 [*] Unnecessary `elif` after `return` statement
|
6 | a = 1
7 | return y
8 | elif z:
| ^^^^ RET505
9 | b = 2
10 | return w
|
= help: Remove unnecessary `elif`
Safe fix
5 5 | if x: # [no-else-return]
6 6 | a = 1
7 7 | return y
8 |- elif z:
8 |+ if z:
9 9 | b = 2
10 10 | return w
11 11 | else:
RET505.py:23:5: RET505 [*] Unnecessary `elif` after `return` statement
|
21 | b = 2
22 | return
23 | elif z:
| ^^^^ RET505
24 | c = 2
25 | else:
|
= help: Remove unnecessary `elif`
Safe fix
20 20 | else:
21 21 | b = 2
22 22 | return
23 |- elif z:
23 |+ if z:
24 24 | c = 2
25 25 | else:
26 26 | c = 3
RET505.py:41:5: RET505 [*] Unnecessary `elif` after `return` statement
|
39 | a = 1
40 | return y
41 | elif z:
| ^^^^ RET505
42 | b = 2
43 | return w
|
= help: Remove unnecessary `elif`
Safe fix
38 38 | if x: # [no-else-return]
39 39 | a = 1
40 40 | return y
41 |- elif z:
41 |+ if z:
42 42 | b = 2
43 43 | return w
44 44 | else:
RET505.py:53:5: RET505 [*] Unnecessary `else` after `return` statement
|
51 | a = 1
52 | return y
53 | else:
| ^^^^ RET505
54 | b = 2
55 | return z
|
= help: Remove unnecessary `else`
Safe fix
50 50 | if x: # [no-else-return]
51 51 | a = 1
52 52 | return y
53 |- else:
54 |- b = 2
55 |- return z
53 |+ b = 2
54 |+ return z
56 55 |
57 56 |
58 57 | def foo3(x, y, z):
RET505.py:64:9: RET505 [*] Unnecessary `else` after `return` statement
|
62 | b = 2
63 | return y
64 | else:
| ^^^^ RET505
65 | c = 3
66 | return x
|
= help: Remove unnecessary `else`
Safe fix
61 61 | if y: # [no-else-return]
62 62 | b = 2
63 63 | return y
64 |- else:
65 |- c = 3
66 |- return x
64 |+ c = 3
65 |+ return x
67 66 | else:
68 67 | d = 4
69 68 | return z
RET505.py:79:5: RET505 [*] Unnecessary `else` after `return` statement
|
77 | b = 2
78 | return
79 | else:
| ^^^^ RET505
80 | c = 3
81 | return
|
= help: Remove unnecessary `else`
Safe fix
76 76 | else:
77 77 | b = 2
78 78 | return
79 |- else:
80 |- c = 3
79 |+ c = 3
81 80 | return
82 81 |
83 82 |
RET505.py:89:9: RET505 [*] Unnecessary `else` after `return` statement
|
87 | a = 4
88 | return
89 | else:
| ^^^^ RET505
90 | b = 2
91 | else:
|
= help: Remove unnecessary `else`
Safe fix
86 86 | if y: # [no-else-return]
87 87 | a = 4
88 88 | return
89 |- else:
90 |- b = 2
89 |+ b = 2
91 90 | else:
92 91 | c = 3
93 92 | return
RET505.py:99:5: RET505 [*] Unnecessary `else` after `return` statement
|
97 | if x: # [no-else-return]
98 | return True
99 | else:
| ^^^^ RET505
100 | try:
101 | return False
|
= help: Remove unnecessary `else`
Safe fix
96 96 | def bar4(x):
97 97 | if x: # [no-else-return]
98 98 | return True
99 |- else:
100 |- try:
101 |- return False
102 |- except ValueError:
103 |- return None
99 |+ try:
100 |+ return False
101 |+ except ValueError:
102 |+ return None
104 103 |
105 104 |
106 105 | ###
RET505.py:137:5: RET505 [*] Unnecessary `else` after `return` statement
|
135 | if True:
136 | return
137 | else:
| ^^^^ RET505
138 | # comment
139 | pass
|
= help: Remove unnecessary `else`
Safe fix
134 134 | def bar4(x):
135 135 | if True:
136 136 | return
137 |- else:
138 |- # comment
139 |- pass
137 |+ # comment
138 |+ pass
140 139 |
141 140 |
142 141 | def bar5():
RET505.py:145:5: RET505 [*] Unnecessary `else` after `return` statement
|
143 | if True:
144 | return
145 | else: # comment
| ^^^^ RET505
146 | pass
|
= help: Remove unnecessary `else`
Safe fix
142 142 | def bar5():
143 143 | if True:
144 144 | return
145 |- else: # comment
146 |- pass
145 |+ # comment
146 |+ pass
147 147 |
148 148 |
149 149 | def bar6():
RET505.py:152:5: RET505 [*] Unnecessary `else` after `return` statement
|
150 | if True:
151 | return
152 | else\
| ^^^^ RET505
153 | :\
154 | # comment
|
= help: Remove unnecessary `else`
Safe fix
149 149 | def bar6():
150 150 | if True:
151 151 | return
152 |- else\
153 |- :\
154 |- # comment
155 |- pass
152 |+ # comment
153 |+ pass
156 154 |
157 155 |
158 156 | def bar7():
RET505.py:161:5: RET505 [*] Unnecessary `else` after `return` statement
|
159 | if True:
160 | return
161 | else\
| ^^^^ RET505
162 | : # comment
163 | pass
|
= help: Remove unnecessary `else`
Safe fix
158 158 | def bar7():
159 159 | if True:
160 160 | return
161 |- else\
162 |- : # comment
163 |- pass
161 |+ # comment
162 |+ pass
164 163 |
165 164 |
166 165 | def bar8():
RET505.py:169:5: RET505 [*] Unnecessary `else` after `return` statement
|
167 | if True:
168 | return
169 | else: pass
| ^^^^ RET505
|
= help: Remove unnecessary `else`
Safe fix
166 166 | def bar8():
167 167 | if True:
168 168 | return
169 |- else: pass
169 |+ pass
170 170 |
171 171 |
172 172 | def bar9():
RET505.py:175:5: RET505 [*] Unnecessary `else` after `return` statement
|
173 | if True:
174 | return
175 | else:\
| ^^^^ RET505
176 | pass
|
= help: Remove unnecessary `else`
Safe fix
172 172 | def bar9():
173 173 | if True:
174 174 | return
175 |- else:\
176 |- pass
175 |+ pass
177 176 |
178 177 |
179 178 | x = 0

View File

@@ -0,0 +1,166 @@
---
source: crates/ruff_linter/src/rules/flake8_return/mod.rs
---
RET506.py:8:5: RET506 [*] Unnecessary `elif` after `raise` statement
|
6 | a = 1
7 | raise Exception(y)
8 | elif z:
| ^^^^ RET506
9 | b = 2
10 | raise Exception(w)
|
= help: Remove unnecessary `elif`
Safe fix
5 5 | if x: # [no-else-raise]
6 6 | a = 1
7 7 | raise Exception(y)
8 |- elif z:
8 |+ if z:
9 9 | b = 2
10 10 | raise Exception(w)
11 11 | else:
RET506.py:23:5: RET506 [*] Unnecessary `elif` after `raise` statement
|
21 | b = 2
22 | raise Exception(x)
23 | elif z:
| ^^^^ RET506
24 | raise Exception(y)
25 | else:
|
= help: Remove unnecessary `elif`
Safe fix
20 20 | else:
21 21 | b = 2
22 22 | raise Exception(x)
23 |- elif z:
23 |+ if z:
24 24 | raise Exception(y)
25 25 | else:
26 26 | c = 3
RET506.py:34:5: RET506 [*] Unnecessary `else` after `raise` statement
|
32 | a = 1
33 | raise Exception(y)
34 | else:
| ^^^^ RET506
35 | b = 2
36 | raise Exception(z)
|
= help: Remove unnecessary `else`
Safe fix
31 31 | if x: # [no-else-raise]
32 32 | a = 1
33 33 | raise Exception(y)
34 |- else:
35 |- b = 2
36 |- raise Exception(z)
34 |+ b = 2
35 |+ raise Exception(z)
37 36 |
38 37 |
39 38 | def foo3(x, y, z):
RET506.py:45:9: RET506 [*] Unnecessary `else` after `raise` statement
|
43 | b = 2
44 | raise Exception(y)
45 | else:
| ^^^^ RET506
46 | c = 3
47 | raise Exception(x)
|
= help: Remove unnecessary `else`
Safe fix
42 42 | if y: # [no-else-raise]
43 43 | b = 2
44 44 | raise Exception(y)
45 |- else:
46 |- c = 3
47 |- raise Exception(x)
45 |+ c = 3
46 |+ raise Exception(x)
48 47 | else:
49 48 | d = 4
50 49 | raise Exception(z)
RET506.py:60:5: RET506 [*] Unnecessary `else` after `raise` statement
|
58 | b = 2
59 | raise Exception(x)
60 | else:
| ^^^^ RET506
61 | c = 3
62 | raise Exception(y)
|
= help: Remove unnecessary `else`
Safe fix
57 57 | else:
58 58 | b = 2
59 59 | raise Exception(x)
60 |- else:
61 |- c = 3
60 |+ c = 3
62 61 | raise Exception(y)
63 62 |
64 63 |
RET506.py:70:9: RET506 [*] Unnecessary `else` after `raise` statement
|
68 | a = 4
69 | raise Exception(x)
70 | else:
| ^^^^ RET506
71 | b = 2
72 | else:
|
= help: Remove unnecessary `else`
Safe fix
67 67 | if y: # [no-else-raise]
68 68 | a = 4
69 69 | raise Exception(x)
70 |- else:
71 |- b = 2
70 |+ b = 2
72 71 | else:
73 72 | c = 3
74 73 | raise Exception(y)
RET506.py:80:5: RET506 [*] Unnecessary `else` after `raise` statement
|
78 | if x: # [no-else-raise]
79 | raise Exception(True)
80 | else:
| ^^^^ RET506
81 | try:
82 | raise Exception(False)
|
= help: Remove unnecessary `else`
Safe fix
77 77 | def bar4(x):
78 78 | if x: # [no-else-raise]
79 79 | raise Exception(True)
80 |- else:
81 |- try:
82 |- raise Exception(False)
83 |- except ValueError:
84 |- raise Exception(None)
80 |+ try:
81 |+ raise Exception(False)
82 |+ except ValueError:
83 |+ raise Exception(None)
85 84 |
86 85 |
87 86 | ###

View File

@@ -0,0 +1,163 @@
---
source: crates/ruff_linter/src/rules/flake8_return/mod.rs
---
RET507.py:8:9: RET507 [*] Unnecessary `elif` after `continue` statement
|
6 | if i < y: # [no-else-continue]
7 | continue
8 | elif i < w:
| ^^^^ RET507
9 | continue
10 | else:
|
= help: Remove unnecessary `elif`
Safe fix
5 5 | for i in x:
6 6 | if i < y: # [no-else-continue]
7 7 | continue
8 |- elif i < w:
8 |+ if i < w:
9 9 | continue
10 10 | else:
11 11 | a = z
RET507.py:22:9: RET507 [*] Unnecessary `elif` after `continue` statement
|
20 | b = 2
21 | continue
22 | elif z:
| ^^^^ RET507
23 | c = 2
24 | else:
|
= help: Remove unnecessary `elif`
Safe fix
19 19 | else:
20 20 | b = 2
21 21 | continue
22 |- elif z:
22 |+ if z:
23 23 | c = 2
24 24 | else:
25 25 | c = 3
RET507.py:36:9: RET507 [*] Unnecessary `else` after `continue` statement
|
34 | if i < y: # [no-else-continue]
35 | continue
36 | else:
| ^^^^ RET507
37 | a = z
|
= help: Remove unnecessary `else`
Safe fix
33 33 | for i in x:
34 34 | if i < y: # [no-else-continue]
35 35 | continue
36 |- else:
37 |- a = z
36 |+ a = z
38 37 |
39 38 |
40 39 | def foo3(x, y, z):
RET507.py:47:13: RET507 [*] Unnecessary `else` after `continue` statement
|
45 | b = 2
46 | continue
47 | else:
| ^^^^ RET507
48 | c = 3
49 | continue
|
= help: Remove unnecessary `else`
Safe fix
44 44 | if z: # [no-else-continue]
45 45 | b = 2
46 46 | continue
47 |- else:
48 |- c = 3
49 |- continue
47 |+ c = 3
48 |+ continue
50 49 | else:
51 50 | d = 4
52 51 | continue
RET507.py:63:9: RET507 [*] Unnecessary `else` after `continue` statement
|
61 | b = 2
62 | continue
63 | else:
| ^^^^ RET507
64 | c = 3
65 | continue
|
= help: Remove unnecessary `else`
Safe fix
60 60 | else:
61 61 | b = 2
62 62 | continue
63 |- else:
64 |- c = 3
63 |+ c = 3
65 64 | continue
66 65 |
67 66 |
RET507.py:74:13: RET507 [*] Unnecessary `else` after `continue` statement
|
72 | a = 4
73 | continue
74 | else:
| ^^^^ RET507
75 | b = 2
76 | else:
|
= help: Remove unnecessary `else`
Safe fix
71 71 | if y: # [no-else-continue]
72 72 | a = 4
73 73 | continue
74 |- else:
75 |- b = 2
74 |+ b = 2
76 75 | else:
77 76 | c = 3
78 77 | continue
RET507.py:85:9: RET507 [*] Unnecessary `else` after `continue` statement
|
83 | if x: # [no-else-continue]
84 | continue
85 | else:
| ^^^^ RET507
86 | try:
87 | return
|
= help: Remove unnecessary `else`
Safe fix
82 82 | for i in range(10):
83 83 | if x: # [no-else-continue]
84 84 | continue
85 |- else:
86 |- try:
87 |- return
88 |- except ValueError:
89 |- continue
85 |+ try:
86 |+ return
87 |+ except ValueError:
88 |+ continue
90 89 |
91 90 |
92 91 | def bar1(x, y, z):

View File

@@ -0,0 +1,181 @@
---
source: crates/ruff_linter/src/rules/flake8_return/mod.rs
---
RET508.py:8:9: RET508 [*] Unnecessary `elif` after `break` statement
|
6 | if i > y: # [no-else-break]
7 | break
8 | elif i > w:
| ^^^^ RET508
9 | break
10 | else:
|
= help: Remove unnecessary `elif`
Safe fix
5 5 | for i in x:
6 6 | if i > y: # [no-else-break]
7 7 | break
8 |- elif i > w:
8 |+ if i > w:
9 9 | break
10 10 | else:
11 11 | a = z
RET508.py:22:9: RET508 [*] Unnecessary `elif` after `break` statement
|
20 | b = 2
21 | break
22 | elif z:
| ^^^^ RET508
23 | c = 2
24 | else:
|
= help: Remove unnecessary `elif`
Safe fix
19 19 | else:
20 20 | b = 2
21 21 | break
22 |- elif z:
22 |+ if z:
23 23 | c = 2
24 24 | else:
25 25 | c = 3
RET508.py:33:9: RET508 [*] Unnecessary `else` after `break` statement
|
31 | if i > y: # [no-else-break]
32 | break
33 | else:
| ^^^^ RET508
34 | a = z
|
= help: Remove unnecessary `else`
Safe fix
30 30 | for i in x:
31 31 | if i > y: # [no-else-break]
32 32 | break
33 |- else:
34 |- a = z
33 |+ a = z
35 34 |
36 35 |
37 36 | def foo3(x, y, z):
RET508.py:44:13: RET508 [*] Unnecessary `else` after `break` statement
|
42 | b = 2
43 | break
44 | else:
| ^^^^ RET508
45 | c = 3
46 | break
|
= help: Remove unnecessary `else`
Safe fix
41 41 | if z: # [no-else-break]
42 42 | b = 2
43 43 | break
44 |- else:
45 |- c = 3
46 |- break
44 |+ c = 3
45 |+ break
47 46 | else:
48 47 | d = 4
49 48 | break
RET508.py:60:9: RET508 [*] Unnecessary `else` after `break` statement
|
58 | b = 2
59 | break
60 | else:
| ^^^^ RET508
61 | c = 3
62 | break
|
= help: Remove unnecessary `else`
Safe fix
57 57 | else:
58 58 | b = 2
59 59 | break
60 |- else:
61 |- c = 3
60 |+ c = 3
62 61 | break
63 62 |
64 63 |
RET508.py:71:13: RET508 [*] Unnecessary `else` after `break` statement
|
69 | a = 4
70 | break
71 | else:
| ^^^^ RET508
72 | b = 2
73 | else:
|
= help: Remove unnecessary `else`
Safe fix
68 68 | if y: # [no-else-break]
69 69 | a = 4
70 70 | break
71 |- else:
72 |- b = 2
71 |+ b = 2
73 72 | else:
74 73 | c = 3
75 74 | break
RET508.py:82:9: RET508 [*] Unnecessary `else` after `break` statement
|
80 | if x: # [no-else-break]
81 | break
82 | else:
| ^^^^ RET508
83 | try:
84 | return
|
= help: Remove unnecessary `else`
Safe fix
79 79 | for i in range(10):
80 80 | if x: # [no-else-break]
81 81 | break
82 |- else:
83 |- try:
84 |- return
85 |- except ValueError:
86 |- break
82 |+ try:
83 |+ return
84 |+ except ValueError:
85 |+ break
87 86 |
88 87 |
89 88 | ###
RET508.py:158:13: RET508 [*] Unnecessary `else` after `break` statement
|
156 | if i > w:
157 | break
158 | else:
| ^^^^ RET508
159 | a = z
|
= help: Remove unnecessary `else`
Safe fix
155 155 | else:
156 156 | if i > w:
157 157 | break
158 |- else:
159 |- a = z
158 |+ a = z

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