Compare commits

...

50 Commits

Author SHA1 Message Date
konstin
e7fb73500d Try thin lto
Let's see what impact on performance this has
2023-11-07 17:01:45 +01:00
Aarni Koskela
7dabc4598b Allow RUFF_NO_CACHE environment variable (like RUFF_CACHE_DIR) (#8538)
## Summary

Being able to set `--no-cache` without touching the command line makes
comparing formatter speed with e.g. Hyperfine a lot easier; Black allows
one to set `BLACK_CACHE_DIR=/dev/null`, but setting
`RUFF_CACHE_DIR=/dev/null` has Ruff choke:

```
error: Failed to initialize cache at /dev/null: Not a directory (os error 20)
error: Failed to initialize cache at /dev/null: Not a directory (os error 20)
warning: Failed to open cache file '/dev/null/0.1.4/18160934645386409287': Not a directory (os error 20)
```

Alternately, we could make a `/dev/null` (or `nul` on Windows) cache
directory imply `--no-cache`?

## Test Plan

None yet.
2023-11-07 08:35:28 -06:00
Andrew Gallant
6a1fa4778f Reject more syntactically invalid Python programs (#8524)
## Summary

This commit adds some additional error checking to the parser such that
assignments that are invalid syntax are rejected. This covers the
obvious cases like `5 = 3` and some not so obvious cases like `x + y =
42`.

This does add an additional recursive call to the parser for the cases
handling assignments. I had initially been concerned about doing this,
but `set_context` is already doing recursion during assignments, so I
didn't feel as though this was changing any fundamental performance
characteristics of the parser. (Also, in practice, I would expect any
such recursion here to be quite shallow since the recursion is done on
the target of an assignment. Such things are rarely nested much in
practice.)

Fixes #6895

## Test Plan

I've added unit tests covering every case that is detected as invalid on
an `Expr`.
2023-11-07 07:16:06 -05:00
Charlie Marsh
c3d6d5d006 Add singleton escape hatch to B008 documentation (#8501)
## Summary:

Closes: https://github.com/astral-sh/ruff/issues/8378.
2023-11-07 04:53:45 +00:00
qdegraaf
9a8400a287 Avoid raising TRIO115 violations for trio.sleep(...) calls with non-number values (#8532)
## Summary

Fixes bug in `TRIO115` where it would not `return` for values that were
not a `NumberLiteral` so
```python
x = "bla"
trio.sleep(x)
```
would set off a false positive

## Test Plan

Added test case to fixture
2023-11-06 16:49:12 -06:00
Juan Orduz
d71c65d0c8 Add PyMC Marketing to Users (#8529)
Add [PyMC-Marketing](https://github.com/pymc-labs/pymc-marketing) to
users. See https://github.com/pymc-labs/pymc-marketing/pull/424
2023-11-06 16:21:49 -06:00
doolio
7f92bfbc4a docs: Add missing toml config tabs (#8512) 2023-11-06 21:12:38 +00:00
Charlie Marsh
37301375c8 Make SIM118 fix as safe when the expression is a known dictionary (#8525)
## Summary

Given `key in obj.keys()`, `obj` _could_ be a dictionary, or it could be
another type that defines
a `.keys()` method. In the latter case, removing the `.keys()` attribute
could lead to a runtime error.

Previously, we marked all `SIM118` fixes as unsafe for this reason;
however, in preview, we now mark them as safe if we can
infer that the expression is a dictionary.

## Test Plan

Added a preview fixture.
2023-11-06 21:06:33 +00:00
Aarni Koskela
c07947bfac Add Pillow to Ruff users (#8523)
## Summary

See https://github.com/python-pillow/Pillow/pull/6966 :)

## Test Plan

Looked at the Markdown preview!
2023-11-06 12:59:06 -06:00
T-256
72964529a5 Skip ecosystem check when no changes detected (#8520)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

For example, https://github.com/astral-sh/ruff/pull/8512 doesn't need
ecosystem check
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

<!-- How was it tested? -->
2023-11-06 12:18:20 -06:00
Charlie Marsh
eab8ca4d7e Add dedicated method to find typed binding (#8517)
## Summary

We have this pattern in a bunch of places, where we find the _only_
binding to a name (and return `None`) if it's bound multiple times. This
PR DRYs it up into a method on `SemanticModel`.
2023-11-06 11:25:32 -05:00
Zanie Blue
5b3e922050 Upgrade pre-commit dependencies (#8518) 2023-11-06 10:08:22 -06:00
Zanie Blue
311a7751f9 Ensure ecosystem project errors are properly fenced (#8516)
Fixes bug where errors could be unfenced resulting in hidden remaining
content

e.g. https://github.com/astral-sh/ruff/pull/8508#issuecomment-1794960132
2023-11-06 09:35:07 -06:00
Charlie Marsh
5e2bb8ca07 Add a Fix constructor that takes Applicability as an argument (#8514)
## Summary

If you want to create an edit with dynamic applicability, you have to
branch and repeat the edit entirely between the two branches. If you
further need the edit itself to be dynamic (e.g., perhaps you have a
single edit in one case, vs. multiple in another), you suddenly have
four branches. This PR just adds an alternate constructor that takes
applicability as an argument, as an escape hatch.
2023-11-06 09:45:10 -05:00
konsti
3c8d9d45fb Recommend project.requires-python over target-version (#8513)
**Summary** Recommend the standardized, shared `project.requires-python`
over ruff's custom `target-version`. See
https://mastodon.social/deck/@davidism@mas.to/111347072204727710

**Test Plan** Docs only change
2023-11-06 14:35:32 +00:00
dependabot[bot]
82c3c513d2 Bump codspeed-criterion-compat from 2.3.0 to 2.3.1 (#8508) 2023-11-06 14:32:40 +00:00
dependabot[bot]
f2dc01e3aa Bump bitflags from 2.4.0 to 2.4.1 (#8511) 2023-11-06 09:20:39 -05:00
dependabot[bot]
5349143fca Bump serde_json from 1.0.107 to 1.0.108 (#8510) 2023-11-06 09:20:30 -05:00
dependabot[bot]
b6f23d57aa Bump syn from 2.0.38 to 2.0.39 (#8509) 2023-11-06 09:19:47 -05:00
dependabot[bot]
b7b6e0136e Bump serde-wasm-bindgen from 0.6.0 to 0.6.1 (#8507) 2023-11-06 09:19:30 -05:00
Ofek Lev
218f517487 Fix typo in example (#8506) 2023-11-06 12:52:14 +05:30
Dhruv Manilawala
75c669a007 Fix tab configuration docs (#8502)
Otherwise it doesn't render as expected.
2023-11-06 03:02:45 +00:00
Shantanu
2d5ce4532a Flag all comparisons against builtin types in E721 (#8491)
See #8483. Generalised fix on top of #8485

Based on the output of `print("\n".join(k for k, v in
builtins.__dict__.items() if isinstance(v, type)))`
2023-11-05 21:28:47 -05:00
qdegraaf
f3e2d12609 [TRIO] Add TRIO115: TrioZeroSleepCall (#8486)
## Summary

Adds `TRIO115` from the [flake8-trio
plugin](https://github.com/Zac-HD/flake8-trio).

## Test Plan

Added a new fixture, based on [the one from upstream
plugin](https://github.com/Zac-HD/flake8-trio/blob/main/tests/eval_files/trio115.py)

## Issue link

Relates to: https://github.com/astral-sh/ruff/issues/8451
2023-11-06 01:19:46 +00:00
Tom Kuson
de2d7e97b1 [refurb] Implement type-none-comparison (FURB169) (#8487)
## Summary

Implement
[`no-is-type-none`](https://github.com/dosisod/refurb/blob/master/refurb/checks/builtin/no_is_type_none.py)
as `type-none-comparison` (`FURB169`).

Auto-fixes comparisons that use `type` to compare the type of an object
to `type(None)` to a `None` identity check. For example,

```python
type(foo) is type(None)
```

becomes

```python
foo is None
```

Related to #1348.

## Test Plan

`cargo test`
2023-11-06 00:56:20 +00:00
Charlie Marsh
bcb737dd80 Add notes on fix safety to a few rules (#8500) 2023-11-06 00:48:57 +00:00
Charlie Marsh
8c146bbf11 Allow collapsed-ellipsis bodies in other statements (#8499)
## Summary

Black and Ruff's preview styles now collapse statements like:

```python
from contextlib import nullcontext

ctx = nullcontext()
with ctx: ...
```

Historically, we made an exception here for classes
(https://github.com/astral-sh/ruff/pull/2837). This PR extends it to
other statement kinds for consistency with the formatter.

Closes https://github.com/astral-sh/ruff/issues/8496.
2023-11-05 19:42:34 -05:00
qdegraaf
4170ef0508 [TRIO] Add TRIO105: SyncTrioCall (#8490)
## Summary

Adds `TRIO105` from the [flake8-trio
plugin](https://github.com/Zac-HD/flake8-trio). The `MethodName` logic
mirrors that of `TRIO100` to stay consistent within the plugin.

It is at 95% parity with the exception of upstream also checking for a
slightly more complex scenario where a call to `start()` on a
`trio.Nursery` context should also be immediately awaited. Upstream
plugin appears to just check for anything named `nursery` judging from
[the relevant issue](https://github.com/Zac-HD/flake8-trio/issues/56).

Unsure if we want to do so something similar or, alternatively, if there
is some capability in ruff to check for calls made on this context some
other way

## Test Plan

Added a new fixture, based on [the one from upstream
plugin](https://github.com/Zac-HD/flake8-trio/blob/main/tests/eval_files/trio105.py)

## Issue link

Refers: https://github.com/astral-sh/ruff/issues/8451
2023-11-05 19:56:10 +00:00
Chris Rose
72ebde8d38 Add instructions for configuration of Emacs (#8488)
## Summary

Add editor integration docs for `ruff format` in Emacs by way of the
Apheleia formatter library

Depends on:  https://github.com/radian-software/apheleia/issues/233
2023-11-05 17:15:59 +00:00
trag1c
1672a3d3b7 Added tabs for configuration files in the documentation (#8480)
## Summary

Closes #8384.

## Test Plan

Checked whether it renders properly on the `mkdocs serve` preview.
2023-11-05 17:10:29 +00:00
Tom Kuson
8c0d65c98e Fix F841 false negative on assignment to multiple variables (#8489)
## Summary

Closes #8441 behind preview feature flag.

## Test Plan

`cargo test`
2023-11-05 12:01:10 -05:00
Dhruv Manilawala
b3c2935fa5 Avoid D301 autofix for u prefixed strings (#8495)
This PR avoids creating the fix for `D301` if the string is prefixed
with `u` i.e., it's a unicode string. The reason being that `u` and `r`
cannot be used together as it's a syntax error.

Refer:
https://github.com/astral-sh/ruff/issues/8402#issuecomment-1788783287
2023-11-05 09:45:49 -05:00
Micha Reiser
e57bccd500 Fix multiline lambda expression statement formating (#8466)
## Summary

This PR fixes a bug in our formatter where a multiline lambda expression
statement was formatted over multiple lines without adding parentheses.

The PR "fixes" the problem by not splitting the lambda parameters if it
is not parenthesized

## Test Plan

Added test
2023-11-05 09:35:23 -05:00
qdegraaf
75c9be099f [E721] Flag comparisons to memoryview (#8485)
## Summary

Adds `memoryview` to the list of typeclasses that `fn is_type()` uses
for type comparison checks so that it raises a violation if `is`, `is
not` or `isinstance()` are not used.

## Test Plan

Added examples to existing fixture

## Issue Link

Closes: https://github.com/astral-sh/ruff/issues/8483
2023-11-04 13:41:58 +00:00
Charlie Marsh
c4889196e7 Add missing pyupgrade entry to changelog (#8479)
This got merged after the changelog was generated, but is part of the
release.
2023-11-03 20:57:19 +00:00
Charlie Marsh
6e635e99f4 Add changelog for v0.1.4 (#8478) 2023-11-03 20:11:21 +00:00
Charlie Marsh
260ea41975 Bump version to v0.1.4 (#8477) 2023-11-03 14:52:56 -04:00
Hugo van Kemenade
65effc6666 Add pyupgrade UP041 to replace TimeoutError aliases (#8476)
## Summary

Add UP041 to replace `TimeoutError` aliases:

* Python 3.10+: `socket.timeout`
* Python 3.11+: `asyncio.TimeoutError`

Re:

* https://github.com/asottile/pyupgrade#timeouterror-aliases
*
https://docs.python.org/3/library/asyncio-exceptions.html#asyncio.TimeoutError
* https://docs.python.org/3/library/socket.html#socket.timeout

Based on `os_error_alias.rs`.

## Test Plan

<!-- How was it tested? -->

By running:

```
cargo clippy --workspace --all-targets --all-features -- -D warnings  # Rust linting
RUFF_UPDATE_SCHEMA=1 cargo test  # Rust testing and updating ruff.schema.json
pre-commit run --all-files --show-diff-on-failure  # Rust and Python formatting, Markdown and Python linting, etc.
cargo insta review
```

And also running with different `--target-version` values:

```sh
cargo run -p ruff_cli -- check crates/ruff_linter/resources/test/fixtures/pyupgrade/UP041.py --no-cache --select UP041 --target-version py37 --diff
cargo run -p ruff_cli -- check crates/ruff_linter/resources/test/fixtures/pyupgrade/UP041.py --no-cache --select UP041 --target-version py310 --diff
cargo run -p ruff_cli -- check crates/ruff_linter/resources/test/fixtures/pyupgrade/UP041.py --no-cache --select UP041 --target-version py311 --diff
```
2023-11-03 17:24:47 +00:00
T-256
4982694b54 D300: prevent autofix when both triples are in body (#8462)
## Summary
Addresses
https://github.com/astral-sh/ruff/issues/8402#issuecomment-1788782750

## Test Plan

Added associated test
2023-11-03 12:49:50 -04:00
Charlie Marsh
536ac550ed Remove trailing periods from NumPy 2.0 code actions (#8475)
Very minor consistency thing with other rules. For code actions, we tend
to say `Replace with {X}` rathern than `Use {X} instead.`
2023-11-03 16:28:06 +00:00
Charlie Marsh
f2335fe692 Make Unicode-to-Unicode confusables a preview change (#8473) 2023-11-03 12:17:28 -04:00
Charlie Marsh
b0f9a14d9a Mark byte_bounds as a non-backwards-compatible NumPy 2.0 change (#8474)
This is the one refactor in the NumPy 2.0 upgrade rule that isn't
compatible with earlier versions of NumPy, so I'm marking it as unsafe
and adding a dedicated message.
2023-11-03 12:14:57 -04:00
Deepyaman Datta
f56bc1983b Place 'r' prefix before 'f' for raw format strings (#8464)
## Summary

Currently, `UP032` applied to raw strings results in format strings with
the prefix 'fr'. This gets changed to 'rf' by Ruff format (or Black). In
order to avoid that, this PR uses the prefix 'rf' to begin with.

## Test Plan

Updated the expectation on an existing test.
2023-11-03 10:56:21 -04:00
Charlie Marsh
7c12eaf322 Use characters instead of u32 in confusable map (#8463) 2023-11-03 09:57:47 -04:00
Dhruv Manilawala
41e538a748 Provide example for exclusive linting or formatting Notebooks (#8461)
Reference screenshot: https://github.com/astral-sh/ruff/assets/67177269/eef5ab79-77e9-4ced-be7b-a61b7bb20ecd
2023-11-03 16:56:20 +05:30
Micha Reiser
dd2d8cb579 Avoid parenthesizing unsplittable because of comments (#8431) 2023-11-03 05:12:59 +00:00
Dhruv Manilawala
a08c5b7fa7 Upgrade PyYAML to 6.0.1 to avoid build error (#8460)
Refer: https://github.com/yaml/pyyaml/pull/702
2023-11-03 10:41:30 +05:30
Christopher Covington
9f30ccc1f4 Autoformat confusable units (#4430)
I've seen errors crop up from using the different micro and mu
characters. Follow matching recommendations on which character to prefer
for micro, ohm, and angstrom. References:
* Section 22.2 Letterlike Symbols, subsection Unit Symbols, page 877 of
[The Unicode Standard, Version 15.0

](https://www.unicode.org/versions/Unicode15.0.0/UnicodeStandard-15.0.pdf)
* Section 2.5 Duplicated Characters of [Unicode Technical Report
25](https://www.unicode.org/reports/tr25/)
* [SI
brochure](https://www.bipm.org/documents/20126/41483022/SI-Brochure-9-EN.pdf)
*
https://github.com/unicode-org/icu/blob/main/icu4c/source/data/unidata/confusables.txt
2023-11-03 04:58:43 +00:00
Charlie Marsh
31286e1c95 Re-run scripts/update_ambiguous_characters.py (#8459)
These weren't formatted consistently, and when I re-ran, the formatting
changed a bit, so I'm editing the script to keep that file constant.
2023-11-03 04:50:10 +00:00
Charlie Marsh
b9994dc495 Use fixedOverflowWidgets for playground popover (#8458)
After some Googling...

<img width="656" alt="Screen Shot 2023-11-03 at 12 23 09 AM"
src="https://github.com/astral-sh/ruff/assets/1309177/be6aaa3d-0068-4bad-a27f-01785179567d">

Closes https://github.com/astral-sh/ruff/issues/8442.
2023-11-03 04:29:37 +00:00
147 changed files with 8620 additions and 2871 deletions

View File

@@ -184,7 +184,11 @@ jobs:
- cargo-test-linux
- determine_changes
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
if: github.event_name == 'pull_request'
# Ecosystem check needs linter and/or formatter changes.
if: github.event_name == 'pull_request' && ${{
needs.determine_changes.outputs.linter == 'true' ||
needs.determine_changes.outputs.formatter == 'true'
}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4

View File

@@ -13,12 +13,12 @@ exclude: |
repos:
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.12.1
rev: v0.15
hooks:
- id: validate-pyproject
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.16
rev: 0.7.17
hooks:
- id: mdformat
additional_dependencies:
@@ -26,16 +26,22 @@ repos:
- mdformat-admon
exclude: |
(?x)^(
docs/formatter/black.md
docs/formatter/black\.md
| docs/\w+\.md
)$
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.33.0
rev: v0.37.0
hooks:
- id: markdownlint-fix
exclude: |
(?x)^(
docs/formatter/black\.md
| docs/\w+\.md
)$
- repo: https://github.com/crate-ci/typos
rev: v1.14.12
rev: v1.16.22
hooks:
- id: typos
@@ -49,7 +55,7 @@ repos:
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.3
rev: v0.1.4
hooks:
- id: ruff-format
- id: ruff
@@ -64,7 +70,7 @@ repos:
# Prettier
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.0
rev: v3.0.3
hooks:
- id: prettier
types: [yaml]

View File

@@ -1,5 +1,66 @@
# Changelog
## 0.1.4
### Preview features
- \[`flake8-trio`\] Implement `timeout-without-await` (`TRIO001`) ([#8439](https://github.com/astral-sh/ruff/pull/8439))
- \[`numpy`\] Implement NumPy 2.0 migration rule (`NPY200`) ([#7702](https://github.com/astral-sh/ruff/pull/7702))
- \[`pylint`\] Implement `bad-open-mode` (`W1501`) ([#8294](https://github.com/astral-sh/ruff/pull/8294))
- \[`pylint`\] Implement `import-outside-toplevel` (`C0415`) rule ([#5180](https://github.com/astral-sh/ruff/pull/5180))
- \[`pylint`\] Implement `useless-with-lock` (`W2101`) ([#8321](https://github.com/astral-sh/ruff/pull/8321))
- \[`pyupgrade`\] Implement `timeout-error-alias` (`UP041`) ([#8476](https://github.com/astral-sh/ruff/pull/8476))
- \[`refurb`\] Implement `isinstance-type-none` (`FURB168`) ([#8308](https://github.com/astral-sh/ruff/pull/8308))
- Detect confusable Unicode-to-Unicode units in `RUF001`, `RUF002`, and `RUF003` ([#4430](https://github.com/astral-sh/ruff/pull/4430))
- Add newline after module docstrings in preview style ([#8283](https://github.com/astral-sh/ruff/pull/8283))
### Formatter
- Add a note on line-too-long to the formatter docs ([#8314](https://github.com/astral-sh/ruff/pull/8314))
- Preserve trailing statement semicolons when using `fmt: skip` ([#8273](https://github.com/astral-sh/ruff/pull/8273))
- Preserve trailing semicolons when using `fmt: off` ([#8275](https://github.com/astral-sh/ruff/pull/8275))
- Avoid duplicating linter-formatter compatibility warnings ([#8292](https://github.com/astral-sh/ruff/pull/8292))
- Avoid inserting a newline after function docstrings ([#8375](https://github.com/astral-sh/ruff/pull/8375))
- Insert newline between docstring and following own line comment ([#8216](https://github.com/astral-sh/ruff/pull/8216))
- Split tuples in return positions by comma first ([#8280](https://github.com/astral-sh/ruff/pull/8280))
- Avoid treating byte strings as docstrings ([#8350](https://github.com/astral-sh/ruff/pull/8350))
- Add `--line-length` option to `format` command ([#8363](https://github.com/astral-sh/ruff/pull/8363))
- Avoid parenthesizing unsplittable because of comments ([#8431](https://github.com/astral-sh/ruff/pull/8431))
### CLI
- Add `--output-format` to `ruff rule` and `ruff linter` ([#8203](https://github.com/astral-sh/ruff/pull/8203))
### Bug fixes
- Respect `--force-exclude` in `lint.exclude` and `format.exclude` ([#8393](https://github.com/astral-sh/ruff/pull/8393))
- Respect `--extend-per-file-ignores` on the CLI ([#8329](https://github.com/astral-sh/ruff/pull/8329))
- Extend `bad-dunder-method-name` to permit `__index__` ([#8300](https://github.com/astral-sh/ruff/pull/8300))
- Fix panic with 8 in octal escape ([#8356](https://github.com/astral-sh/ruff/pull/8356))
- Avoid raising `D300` when both triple quote styles are present ([#8462](https://github.com/astral-sh/ruff/pull/8462))
- Consider unterminated f-strings in `FStringRanges` ([#8154](https://github.com/astral-sh/ruff/pull/8154))
- Avoid including literal `shell=True` for truthy, non-`True` diagnostics ([#8359](https://github.com/astral-sh/ruff/pull/8359))
- Avoid triggering single-element test for starred expressions ([#8433](https://github.com/astral-sh/ruff/pull/8433))
- Detect and ignore Jupyter automagics ([#8398](https://github.com/astral-sh/ruff/pull/8398))
- Fix invalid E231 error with f-strings ([#8369](https://github.com/astral-sh/ruff/pull/8369))
- Avoid triggering `NamedTuple` rewrite with starred annotation ([#8434](https://github.com/astral-sh/ruff/pull/8434))
- Avoid un-setting bracket flag in logical lines ([#8380](https://github.com/astral-sh/ruff/pull/8380))
- Place 'r' prefix before 'f' for raw format strings ([#8464](https://github.com/astral-sh/ruff/pull/8464))
- Remove trailing periods from NumPy 2.0 code actions ([#8475](https://github.com/astral-sh/ruff/pull/8475))
- Fix bug where `PLE1307` was raised when formatting `%c` with characters ([#8407](https://github.com/astral-sh/ruff/pull/8407))
- Remove unicode flag from comparable ([#8440](https://github.com/astral-sh/ruff/pull/8440))
- Improve B015 message ([#8295](https://github.com/astral-sh/ruff/pull/8295))
- Use `fixedOverflowWidgets` for playground popover ([#8458](https://github.com/astral-sh/ruff/pull/8458))
- Mark `byte_bounds` as a non-backwards-compatible NumPy 2.0 change ([#8474](https://github.com/astral-sh/ruff/pull/8474))
### Internals
- Add a dedicated cache directory per Ruff version ([#8333](https://github.com/astral-sh/ruff/pull/8333))
- Allow selective caching for `--fix` and `--diff` ([#8316](https://github.com/astral-sh/ruff/pull/8316))
- Improve performance of comment parsing ([#8193](https://github.com/astral-sh/ruff/pull/8193))
- Improve performance of string parsing ([#8227](https://github.com/astral-sh/ruff/pull/8227))
- Use a dedicated sort key for isort import sorting ([#7963](https://github.com/astral-sh/ruff/pull/7963))
## 0.1.3
This release includes a variety of improvements to the Ruff formatter, removing several known and

View File

@@ -72,7 +72,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
charlie.r.marsh@gmail.com.
<charlie.r.marsh@gmail.com>.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the

82
Cargo.lock generated
View File

@@ -210,9 +210,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "bstr"
@@ -383,7 +383,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -407,9 +407,9 @@ dependencies = [
[[package]]
name = "codspeed"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d680ccd1eedd2dd7c7a3649a78c7d06e0f16b191b30d81cc58e7bc906488d344"
checksum = "918b13a0f1a32460ab3bd5debd56b5a27a7071fa5ff5dfeb3a5cf291a85b174b"
dependencies = [
"colored",
"libc",
@@ -418,9 +418,9 @@ dependencies = [
[[package]]
name = "codspeed-criterion-compat"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58b48b6c8e890d7d4ad0ed85e9ab4949bf7023198c006000ef6338ba84cf5b71"
checksum = "c683c7fef2b873fbbdf4062782914c652309951244bf0bd362fe608b7d6f901c"
dependencies = [
"codspeed",
"colored",
@@ -608,7 +608,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -619,7 +619,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -810,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.1.3"
version = "0.1.4"
dependencies = [
"anyhow",
"clap",
@@ -1129,7 +1129,7 @@ dependencies = [
"pmutil 0.6.1",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -1440,7 +1440,7 @@ version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"crossbeam-channel",
"filetime",
"fsevent-sys",
@@ -1707,7 +1707,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -2060,14 +2060,14 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.1.3"
version = "0.1.4"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
"argfile",
"assert_cmd",
"bincode",
"bitflags 2.4.0",
"bitflags 2.4.1",
"cachedir",
"chrono",
"clap",
@@ -2196,12 +2196,12 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.1.3"
version = "0.1.4"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.1",
"anyhow",
"bitflags 2.4.0",
"bitflags 2.4.1",
"chrono",
"clap",
"colored",
@@ -2267,7 +2267,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -2293,7 +2293,7 @@ dependencies = [
name = "ruff_python_ast"
version = "0.0.0"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"insta",
"is-macro",
"itertools 0.11.0",
@@ -2325,7 +2325,7 @@ name = "ruff_python_formatter"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.4.0",
"bitflags 2.4.1",
"clap",
"countme",
"insta",
@@ -2369,7 +2369,7 @@ dependencies = [
name = "ruff_python_literal"
version = "0.0.0"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"hexf-parse",
"is-macro",
"itertools 0.11.0",
@@ -2383,7 +2383,7 @@ name = "ruff_python_parser"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.4.0",
"bitflags 2.4.1",
"insta",
"is-macro",
"itertools 0.11.0",
@@ -2413,7 +2413,7 @@ dependencies = [
name = "ruff_python_semantic"
version = "0.0.0"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"is-macro",
"ruff_index",
"ruff_python_ast",
@@ -2447,7 +2447,7 @@ dependencies = [
[[package]]
name = "ruff_shrinking"
version = "0.1.3"
version = "0.1.4"
dependencies = [
"anyhow",
"clap",
@@ -2563,7 +2563,7 @@ version = "0.38.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"errno",
"libc",
"linux-raw-sys",
@@ -2682,9 +2682,9 @@ dependencies = [
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30c9933e5689bd420dc6c87b7a1835701810cbc10cd86a26e4da45b73e6b1d78"
checksum = "17ba92964781421b6cef36bf0d7da26d201e96d84e1b10e7ae6ed416e516906d"
dependencies = [
"js-sys",
"serde",
@@ -2699,7 +2699,7 @@ checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -2715,9 +2715,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.107"
version = "1.0.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
dependencies = [
"itoa",
"ryu",
@@ -2761,7 +2761,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -2856,7 +2856,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -2872,9 +2872,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.38"
version = "2.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
dependencies = [
"proc-macro2",
"quote",
@@ -2961,7 +2961,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -2973,7 +2973,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
"test-case-core",
]
@@ -2994,7 +2994,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -3131,7 +3131,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -3349,7 +3349,7 @@ checksum = "3d8c6bba9b149ee82950daefc9623b32bb1dacbfb1890e352f6b887bd582adaf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -3443,7 +3443,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
"wasm-bindgen-shared",
]
@@ -3477,7 +3477,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@@ -13,7 +13,7 @@ license = "MIT"
[workspace.dependencies]
anyhow = { version = "1.0.69" }
bitflags = { version = "2.3.1" }
bitflags = { version = "2.4.1" }
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
clap = { version = "4.4.7", features = ["derive"] }
colored = { version = "2.0.0" }
@@ -35,14 +35,14 @@ regex = { version = "1.10.2" }
rustc-hash = { version = "1.1.0" }
schemars = { version = "0.8.15" }
serde = { version = "1.0.190", features = ["derive"] }
serde_json = { version = "1.0.107" }
serde_json = { version = "1.0.108" }
shellexpand = { version = "3.0.0" }
similar = { version = "2.3.0", features = ["inline"] }
smallvec = { version = "1.11.1" }
static_assertions = "1.1.0"
strum = { version = "0.25.0", features = ["strum_macros"] }
strum_macros = { version = "0.25.3" }
syn = { version = "2.0.38" }
syn = { version = "2.0.39" }
test-case = { version = "3.2.1" }
thiserror = { version = "1.0.50" }
toml = { version = "0.7.8" }
@@ -56,7 +56,7 @@ uuid = { version = "1.5.0", features = ["v4", "fast-rng", "macro-diagnostics", "
wsl = { version = "0.1.0" }
[profile.release]
lto = "fat"
lto = "thin"
codegen-units = 1
[profile.dev.package.insta]

View File

@@ -148,10 +148,9 @@ ruff format @arguments.txt # Format using an input file, treating its
Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff-pre-commit`](https://github.com/astral-sh/ruff-pre-commit):
```yaml
# Run the Ruff linter.
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.1.3
rev: v0.1.4
hooks:
# Run the Ruff linter.
- id: ruff
@@ -416,6 +415,7 @@ Ruff is used by a number of major open-source projects and companies, including:
- [PDM](https://github.com/pdm-project/pdm)
- [PaddlePaddle](https://github.com/PaddlePaddle/Paddle)
- [Pandas](https://github.com/pandas-dev/pandas)
- [Pillow](https://github.com/python-pillow/Pillow)
- [Poetry](https://github.com/python-poetry/poetry)
- [Polars](https://github.com/pola-rs/polars)
- [PostHog](https://github.com/PostHog/posthog)
@@ -424,6 +424,7 @@ Ruff is used by a number of major open-source projects and companies, including:
- [PyTorch](https://github.com/pytorch/pytorch)
- [Pydantic](https://github.com/pydantic/pydantic)
- [Pylint](https://github.com/PyCQA/pylint)
- [PyMC-Marketing](https://github.com/pymc-labs/pymc-marketing)
- [Reflex](https://github.com/reflex-dev/reflex)
- [Rippling](https://rippling.com)
- [Robyn](https://github.com/sansyrox/robyn)

View File

@@ -1,5 +1,6 @@
[files]
extend-exclude = ["resources", "snapshots"]
# https://github.com/crate-ci/typos/issues/868
extend-exclude = ["**/resources/**/*", "**/snapshots/**/*"]
[default.extend-words]
hel = "hel"

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.1.3"
version = "0.1.4"
description = """
Convert Flake8 configuration files to Ruff configuration files.
"""

View File

@@ -37,7 +37,7 @@ serde_json.workspace = true
url = "2.3.1"
ureq = "2.8.0"
criterion = { version = "0.5.1", default-features = false }
codspeed-criterion-compat = { version="2.3.0", default-features = false, optional = true}
codspeed-criterion-compat = { version="2.3.1", default-features = false, optional = true}
[dev-dependencies]
ruff_linter.path = "../ruff_linter"

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_cli"
version = "0.1.3"
version = "0.1.4"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -278,7 +278,7 @@ pub struct CheckCommand {
#[arg(long, help_heading = "Rule configuration", hide = true)]
pub dummy_variable_rgx: Option<Regex>,
/// Disable cache reads.
#[arg(short, long, help_heading = "Miscellaneous")]
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
pub no_cache: bool,
/// Ignore all configuration files.
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
@@ -374,7 +374,7 @@ pub struct FormatCommand {
pub config: Option<PathBuf>,
/// Disable cache reads.
#[arg(short, long, help_heading = "Miscellaneous")]
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
pub no_cache: bool,
/// Path to the cache directory.
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]

View File

@@ -3,6 +3,7 @@
//! Used for <https://docs.astral.sh/ruff/settings/>.
use std::fmt::Write;
use ruff_python_trivia::textwrap;
use ruff_workspace::options::Options;
use ruff_workspace::options_base::{OptionField, OptionSet, OptionsMetadata, Visit};
@@ -125,22 +126,57 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
output.push('\n');
output.push_str(&format!("**Type**: `{}`\n", field.value_type));
output.push('\n');
output.push_str(&format!(
"**Example usage**:\n\n```toml\n[tool.ruff{}]\n{}\n```\n",
if let Some(set_name) = parent_set.name() {
if set_name == "format" {
String::from(".format")
} else {
format!(".lint.{set_name}")
}
} else {
String::new()
},
field.example
output.push_str("**Example usage**:\n\n");
output.push_str(&format_tab(
"pyproject.toml",
&format_header(parent_set, ConfigurationFile::PyprojectToml),
field.example,
));
output.push_str(&format_tab(
"ruff.toml",
&format_header(parent_set, ConfigurationFile::RuffToml),
field.example,
));
output.push('\n');
}
fn format_tab(tab_name: &str, header: &str, content: &str) -> String {
format!(
"=== \"{}\"\n\n ```toml\n {}\n{}\n ```\n",
tab_name,
header,
textwrap::indent(content, " ")
)
}
fn format_header(parent_set: &Set, configuration: ConfigurationFile) -> String {
let fmt = if let Some(set_name) = parent_set.name() {
if set_name == "format" {
String::from(".format")
} else {
format!(".lint.{set_name}")
}
} else {
String::new()
};
match configuration {
ConfigurationFile::PyprojectToml => format!("[tool.ruff{fmt}]"),
ConfigurationFile::RuffToml => {
if fmt.is_empty() {
String::new()
} else {
format!("[{}]", fmt.strip_prefix('.').unwrap())
}
}
}
}
#[derive(Debug, Copy, Clone)]
enum ConfigurationFile {
PyprojectToml,
RuffToml,
}
#[derive(Default)]
struct CollectOptionsVisitor {
groups: Vec<(String, OptionSet)>,

View File

@@ -107,6 +107,30 @@ impl Fix {
}
}
/// Create a new [`Fix`] with the specified [`Applicability`] to apply an [`Edit`] element.
pub fn applicable_edit(edit: Edit, applicability: Applicability) -> Self {
Self {
edits: vec![edit],
applicability,
isolation_level: IsolationLevel::default(),
}
}
/// Create a new [`Fix`] with the specified [`Applicability`] to apply multiple [`Edit`] elements.
pub fn applicable_edits(
edit: Edit,
rest: impl IntoIterator<Item = Edit>,
applicability: Applicability,
) -> Self {
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
edits.sort_by_key(|edit| (edit.start(), edit.end()));
Self {
edits,
applicability,
isolation_level: IsolationLevel::default(),
}
}
/// Return the [`TextSize`] of the first [`Edit`] in the [`Fix`].
pub fn min_start(&self) -> Option<TextSize> {
self.edits.first().map(Edit::start)

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.1.3"
version = "0.1.4"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -1,3 +1,5 @@
obj = {}
key in obj.keys() # SIM118
key not in obj.keys() # SIM118

View File

@@ -0,0 +1,64 @@
import trio
async def func() -> None:
trio.run(foo) # OK, not async
# OK
await trio.aclose_forcefully(foo)
await trio.open_file(foo)
await trio.open_ssl_over_tcp_listeners(foo, foo)
await trio.open_ssl_over_tcp_stream(foo, foo)
await trio.open_tcp_listeners(foo)
await trio.open_tcp_stream(foo, foo)
await trio.open_unix_socket(foo)
await trio.run_process(foo)
await trio.sleep(5)
await trio.sleep_until(5)
await trio.lowlevel.cancel_shielded_checkpoint()
await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint_if_cancelled()
await trio.lowlevel.open_process(foo)
await trio.lowlevel.permanently_detach_coroutine_object(foo)
await trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
await trio.lowlevel.temporarily_detach_coroutine_object(foo)
await trio.lowlevel.wait_readable(foo)
await trio.lowlevel.wait_task_rescheduled(foo)
await trio.lowlevel.wait_writable(foo)
# TRIO105
trio.aclose_forcefully(foo)
trio.open_file(foo)
trio.open_ssl_over_tcp_listeners(foo, foo)
trio.open_ssl_over_tcp_stream(foo, foo)
trio.open_tcp_listeners(foo)
trio.open_tcp_stream(foo, foo)
trio.open_unix_socket(foo)
trio.run_process(foo)
trio.serve_listeners(foo, foo)
trio.serve_ssl_over_tcp(foo, foo, foo)
trio.serve_tcp(foo, foo)
trio.sleep(foo)
trio.sleep_forever()
trio.sleep_until(foo)
trio.lowlevel.cancel_shielded_checkpoint()
trio.lowlevel.checkpoint()
trio.lowlevel.checkpoint_if_cancelled()
trio.lowlevel.open_process()
trio.lowlevel.permanently_detach_coroutine_object(foo)
trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
trio.lowlevel.temporarily_detach_coroutine_object(foo)
trio.lowlevel.wait_readable(foo)
trio.lowlevel.wait_task_rescheduled(foo)
trio.lowlevel.wait_writable(foo)
async with await trio.open_file(foo): # Ok
pass
async with trio.open_file(foo): # TRIO105
pass
def func() -> None:
# TRIO105 (without fix)
trio.open_file(foo)

View File

@@ -0,0 +1,28 @@
import trio
from trio import sleep
async def func():
await trio.sleep(0) # TRIO115
await trio.sleep(1) # OK
await trio.sleep(0, 1) # OK
await trio.sleep(...) # OK
await trio.sleep() # OK
trio.sleep(0) # TRIO115
foo = 0
trio.sleep(foo) # TRIO115
trio.sleep(1) # OK
time.sleep(0) # OK
sleep(0) # TRIO115
bar = "bar"
trio.sleep(bar)
trio.sleep(0) # TRIO115
def func():
trio.run(trio.sleep(0)) # TRIO115

View File

@@ -69,3 +69,5 @@ while 1:
#: E701:2:3
a = \
5;
#:
with x(y) as z: ...

View File

@@ -4,6 +4,9 @@ if type(res) == type(42):
#: E721
if type(res) != type(""):
pass
#: E721
if type(res) == memoryview:
pass
#: Okay
import types
@@ -47,6 +50,14 @@ if isinstance(res, str):
pass
if isinstance(res, types.MethodType):
pass
if isinstance(res, memoryview):
pass
#: Okay
if type(res) is type:
pass
#: E721
if type(res) == type:
pass
#: Okay
def func_histype(a, b, c):
pass

View File

@@ -8,3 +8,19 @@ def ends_in_quote():
def contains_quote():
'Sum"\\mary.'
# OK
def contains_triples(t):
"""('''|\""")"""
# OK
def contains_triples(t):
'''(\'''|""")'''
# TODO: here should raise D300 for using dobule quotes instead,
# because escaped double quote does allow us.
def contains_triples(t):
'''(\""")'''

View File

@@ -31,3 +31,7 @@ def make_unique_pod_id(pod_id: str) -> str | None:
:param pod_id: requested pod name
:return: ``str`` valid Pod name of appropriate length
"""
def shouldnt_add_raw_here2():
u"Sum\\mary."

View File

@@ -1,5 +1,5 @@
def f(tup):
x, y = tup # this does NOT trigger F841
x, y = tup
def f():
@@ -7,17 +7,17 @@ def f():
def f():
(x, y) = coords = 1, 2 # this does NOT trigger F841
(x, y) = coords = 1, 2
if x > 1:
print(coords)
def f():
(x, y) = coords = 1, 2 # this triggers F841 on coords
(x, y) = coords = 1, 2
def f():
coords = (x, y) = 1, 2 # this triggers F841 on coords
coords = (x, y) = 1, 2
def f():

View File

@@ -0,0 +1,32 @@
"""Test fix for issue #8441.
Ref: https://github.com/astral-sh/ruff/issues/8441
"""
def foo():
...
def bar():
a = foo()
b, c = foo()
def baz():
d, _e = foo()
print(d)
def qux():
f, _ = foo()
print(f)
def quux():
g, h = foo()
print(g, h)
def quuz():
_i, _j = foo()

View File

@@ -0,0 +1,68 @@
import asyncio, socket
# These should be fixed
try:
pass
except asyncio.TimeoutError:
pass
try:
pass
except socket.timeout:
pass
# Should NOT be in parentheses when replaced
try:
pass
except (asyncio.TimeoutError,):
pass
try:
pass
except (socket.timeout,):
pass
try:
pass
except (asyncio.TimeoutError, socket.timeout,):
pass
# Should be kept in parentheses (because multiple)
try:
pass
except (asyncio.TimeoutError, socket.timeout, KeyError, TimeoutError):
pass
# First should change, second should not
from .mmap import error
try:
pass
except (asyncio.TimeoutError, error):
pass
# These should not change
from foo import error
try:
pass
except (TimeoutError, error):
pass
try:
pass
except:
pass
try:
pass
except TimeoutError:
pass
try:
pass
except (TimeoutError, KeyError):
pass

View File

@@ -43,6 +43,12 @@ def yes_four(x: Dict[int, str]):
del x[:]
def yes_five(x: Dict[int, str]):
# FURB131
del x[:]
x = 1
# these should not
del names["key"]

View File

@@ -0,0 +1,67 @@
foo = None
# Error.
type(foo) is type(None)
type(None) is type(foo)
type(None) is type(None)
type(foo) is not type(None)
type(None) is not type(foo)
type(None) is not type(None)
type(foo) == type(None)
type(None) == type(foo)
type(None) == type(None)
type(foo) != type(None)
type(None) != type(foo)
type(None) != type(None)
# Ok.
foo is None
foo is not None
None is foo
None is not foo
None is None
None is not None
foo is type(None)
type(foo) is None
type(None) is None
foo is not type(None)
type(foo) is not None
type(None) is not None
foo == type(None)
type(foo) == None
type(None) == None
foo != type(None)
type(foo) != None
type(None) != None
type(foo) > type(None)

View File

@@ -45,3 +45,11 @@ x = f"string { # And here's a comment with an unusual parenthesis:
# And here's a comment with a greek alpha:
foo # And here's a comment with an unusual punctuation mark:
}"
# At runtime the attribute will be stored as Greek small letter mu instead of
# micro sign because of PEP 3131's NFKC normalization
class Labware:
µL = 1.5
assert getattr(Labware(), "µL") == 1.5

View File

@@ -15,8 +15,8 @@ use crate::rules::{
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging,
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self,
flake8_simplify, flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet,
pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
flake8_simplify, flake8_tidy_imports, flake8_trio, flake8_use_pathlib, flynt, numpy,
pandas_vet, pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
};
use crate::settings::types::PythonVersion;
@@ -466,6 +466,11 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::OSErrorAlias) {
pyupgrade::rules::os_error_alias_call(checker, func);
}
if checker.enabled(Rule::TimeoutErrorAlias) {
if checker.settings.target_version >= PythonVersion::Py310 {
pyupgrade::rules::timeout_error_alias_call(checker, func);
}
}
if checker.enabled(Rule::NonPEP604Isinstance) {
if checker.settings.target_version >= PythonVersion::Py310 {
pyupgrade::rules::use_pep604_isinstance(checker, expr, func, args);
@@ -921,6 +926,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::ImplicitCwd) {
refurb::rules::no_implicit_cwd(checker, call);
}
if checker.enabled(Rule::TrioSyncCall) {
flake8_trio::rules::sync_call(checker, call);
}
if checker.enabled(Rule::TrioZeroSleepCall) {
flake8_trio::rules::zero_sleep_call(checker, call);
}
}
Expr::Dict(
dict @ ast::ExprDict {
@@ -1230,6 +1241,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
comparators,
);
}
if checker.enabled(Rule::TypeNoneComparison) {
refurb::rules::type_none_comparison(checker, compare);
}
if checker.enabled(Rule::SingleItemMembershipTest) {
refurb::rules::single_item_membership_test(checker, expr, left, ops, comparators);
}

View File

@@ -1006,6 +1006,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
pyupgrade::rules::os_error_alias_raise(checker, item);
}
}
if checker.enabled(Rule::TimeoutErrorAlias) {
if checker.settings.target_version >= PythonVersion::Py310 {
if let Some(item) = exc {
pyupgrade::rules::timeout_error_alias_raise(checker, item);
}
}
}
if checker.enabled(Rule::RaiseVanillaClass) {
if let Some(expr) = exc {
tryceratops::rules::raise_vanilla_class(checker, expr);
@@ -1304,6 +1311,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::OSErrorAlias) {
pyupgrade::rules::os_error_alias_handlers(checker, handlers);
}
if checker.enabled(Rule::TimeoutErrorAlias) {
if checker.settings.target_version >= PythonVersion::Py310 {
pyupgrade::rules::timeout_error_alias_handlers(checker, handlers);
}
}
if checker.enabled(Rule::PytestAssertInExcept) {
flake8_pytest_style::rules::assert_in_exception_handler(checker, handlers);
}

View File

@@ -292,6 +292,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
// flake8-trio
(Flake8Trio, "100") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioTimeoutWithoutAwait),
(Flake8Trio, "105") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioSyncCall),
(Flake8Trio, "115") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioZeroSleepCall),
// flake8-builtins
(Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing),
@@ -500,6 +502,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pyupgrade, "038") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP604Isinstance),
(Pyupgrade, "039") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryClassParentheses),
(Pyupgrade, "040") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695TypeAlias),
(Pyupgrade, "041") => (RuleGroup::Preview, rules::pyupgrade::rules::TimeoutErrorAlias),
// pydocstyle
(Pydocstyle, "100") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedPublicModule),
@@ -943,6 +946,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy),
(Refurb, "148") => (RuleGroup::Preview, rules::refurb::rules::UnnecessaryEnumerate),
(Refurb, "168") => (RuleGroup::Preview, rules::refurb::rules::IsinstanceTypeNone),
(Refurb, "169") => (RuleGroup::Preview, rules::refurb::rules::TypeNoneComparison),
(Refurb, "171") => (RuleGroup::Preview, rules::refurb::rules::SingleItemMembershipTest),
(Refurb, "177") => (RuleGroup::Preview, rules::refurb::rules::ImplicitCwd),

View File

@@ -163,11 +163,15 @@ mod tests {
"# ( user_content_type , _ )= TimelineEvent.objects.using(db_alias).get_or_create(",
&[]
));
assert!(comment_contains_code(
assert!(comment_contains_code("# )", &[]));
// This used to return true, but our parser has gotten a bit better
// at rejecting invalid Python syntax. And indeed, this is not valid
// Python code.
assert!(!comment_contains_code(
"# app_label=\"core\", model=\"user\"",
&[]
));
assert!(comment_contains_code("# )", &[]));
// TODO(charlie): This should be `true` under aggressive mode.
assert!(!comment_contains_code("#def foo():", &[]));

View File

@@ -27,6 +27,11 @@ use crate::checkers::ast::Checker;
/// raise AssertionError
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as changing an `assert` to a
/// `raise` will change the behavior of your program when running in
/// optimized mode (`python -O`).
///
/// ## References
/// - [Python documentation: `assert`](https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement)
#[violation]

View File

@@ -50,6 +50,16 @@ use crate::checkers::ast::Checker;
/// return arg
/// ```
///
/// If the use of a singleton is intentional, assign the result call to a
/// module-level variable, and use that variable in the default argument:
/// ```python
/// ERROR = ValueError("Hosts weren't successfully added")
///
///
/// def add_host(error: Exception = ERROR) -> None:
/// ...
/// ```
///
/// ## Options
/// - `flake8-bugbear.extend-immutable-calls`
#[violation]
@@ -62,9 +72,9 @@ impl Violation for FunctionCallInDefaultArgument {
fn message(&self) -> String {
let FunctionCallInDefaultArgument { name } = self;
if let Some(name) = name {
format!("Do not perform function call `{name}` in argument defaults")
format!("Do not perform function call `{name}` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable")
} else {
format!("Do not perform function call in argument defaults")
format!("Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable")
}
}
}

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
---
B006_B008.py:102:61: B008 Do not perform function call `range` in argument defaults
B006_B008.py:102:61: B008 Do not perform function call `range` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
101 | # N.B. we're also flagging the function call in the comprehension
102 | def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
@@ -9,21 +9,21 @@ B006_B008.py:102:61: B008 Do not perform function call `range` in argument defau
103 | pass
|
B006_B008.py:106:64: B008 Do not perform function call `range` in argument defaults
B006_B008.py:106:64: B008 Do not perform function call `range` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
106 | def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
| ^^^^^^^^ B008
107 | pass
|
B006_B008.py:110:60: B008 Do not perform function call `range` in argument defaults
B006_B008.py:110:60: B008 Do not perform function call `range` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
110 | def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
| ^^^^^^^^ B008
111 | pass
|
B006_B008.py:126:39: B008 Do not perform function call `time.time` in argument defaults
B006_B008.py:126:39: B008 Do not perform function call `time.time` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
124 | # B008
125 | # Flag function calls as default args (including if they are part of a sub-expression)
@@ -32,21 +32,21 @@ B006_B008.py:126:39: B008 Do not perform function call `time.time` in argument d
127 | ...
|
B006_B008.py:130:12: B008 Do not perform function call `dt.datetime.now` in argument defaults
B006_B008.py:130:12: B008 Do not perform function call `dt.datetime.now` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
130 | def f(when=dt.datetime.now() + dt.timedelta(days=7)):
| ^^^^^^^^^^^^^^^^^ B008
131 | pass
|
B006_B008.py:134:30: B008 Do not perform function call in argument defaults
B006_B008.py:134:30: B008 Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
134 | def can_even_catch_lambdas(a=(lambda x: x)()):
| ^^^^^^^^^^^^^^^ B008
135 | ...
|
B006_B008.py:239:31: B008 Do not perform function call `dt.datetime.now` in argument defaults
B006_B008.py:239:31: B008 Do not perform function call `dt.datetime.now` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
237 | # B006 and B008
238 | # We should handle arbitrary nesting of these B008.
@@ -55,7 +55,7 @@ B006_B008.py:239:31: B008 Do not perform function call `dt.datetime.now` in argu
240 | pass
|
B006_B008.py:245:22: B008 Do not perform function call `map` in argument defaults
B006_B008.py:245:22: B008 Do not perform function call `map` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
243 | # Don't flag nested B006 since we can't guarantee that
244 | # it isn't made mutable by the outer operation.
@@ -64,7 +64,7 @@ B006_B008.py:245:22: B008 Do not perform function call `map` in argument default
246 | pass
|
B006_B008.py:250:19: B008 Do not perform function call `random.randint` in argument defaults
B006_B008.py:250:19: B008 Do not perform function call `random.randint` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
249 | # B008-ception.
250 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
@@ -72,7 +72,7 @@ B006_B008.py:250:19: B008 Do not perform function call `random.randint` in argum
251 | pass
|
B006_B008.py:250:37: B008 Do not perform function call `dt.datetime.now` in argument defaults
B006_B008.py:250:37: B008 Do not perform function call `dt.datetime.now` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
249 | # B008-ception.
250 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
---
B008_extended.py:24:51: B008 Do not perform function call `Depends` in argument defaults
B008_extended.py:24:51: B008 Do not perform function call `Depends` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
24 | def error_due_to_missing_import(data: List[str] = Depends(None)):
| ^^^^^^^^^^^^^ B008

View File

@@ -1,4 +1,4 @@
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::Ranged;
@@ -83,13 +83,14 @@ pub(crate) fn unnecessary_call_around_sorted(
expr.range(),
);
diagnostic.try_set_fix(|| {
let edit =
fixes::fix_unnecessary_call_around_sorted(expr, checker.locator(), checker.stylist())?;
if outer.id == "reversed" {
Ok(Fix::unsafe_edit(edit))
} else {
Ok(Fix::safe_edit(edit))
}
Ok(Fix::applicable_edit(
fixes::fix_unnecessary_call_around_sorted(expr, checker.locator(), checker.stylist())?,
if outer.id == "reversed" {
Applicability::Unsafe
} else {
Applicability::Safe
},
))
});
checker.diagnostics.push(diagnostic);
}

View File

@@ -1,4 +1,4 @@
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::BindingKind;
@@ -103,17 +103,23 @@ pub(crate) fn unnecessary_paren_on_raise_exception(checker: &mut Checker, expr:
.next()
.is_some_and(char::is_alphanumeric)
{
diagnostic.set_fix(if exception_type.is_some() {
Fix::safe_edit(Edit::range_replacement(" ".to_string(), arguments.range()))
} else {
Fix::unsafe_edit(Edit::range_replacement(" ".to_string(), arguments.range()))
});
diagnostic.set_fix(Fix::applicable_edit(
Edit::range_replacement(" ".to_string(), arguments.range()),
if exception_type.is_some() {
Applicability::Safe
} else {
Applicability::Unsafe
},
));
} else {
diagnostic.set_fix(if exception_type.is_some() {
Fix::safe_edit(Edit::range_deletion(arguments.range()))
} else {
Fix::unsafe_edit(Edit::range_deletion(arguments.range()))
});
diagnostic.set_fix(Fix::applicable_edit(
Edit::range_deletion(arguments.range()),
if exception_type.is_some() {
Applicability::Safe
} else {
Applicability::Unsafe
},
));
}
checker.diagnostics.push(diagnostic);

View File

@@ -55,6 +55,7 @@ mod tests {
Ok(())
}
#[test_case(Rule::InDictKeys, Path::new("SIM118.py"))]
#[test_case(Rule::IfElseBlockInsteadOfDictGet, Path::new("SIM401.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(

View File

@@ -1,9 +1,10 @@
use ruff_diagnostics::Edit;
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
use ruff_diagnostics::{Applicability, Edit};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::AnyNodeRef;
use ruff_python_ast::{self as ast, Arguments, CmpOp, Comprehension, Expr};
use ruff_python_semantic::analyze::typing;
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
use ruff_text_size::{Ranged, TextRange};
@@ -27,8 +28,19 @@ use crate::checkers::ast::Checker;
/// key in foo
/// ```
///
/// ## Fix safety
/// Given `key in obj.keys()`, `obj` _could_ be a dictionary, or it could be
/// another type that defines a `.keys()` method. In the latter case, removing
/// the `.keys()` attribute could lead to a runtime error.
///
/// As such, this rule's fixes are marked as unsafe. In [preview], though,
/// fixes are marked as safe when Ruff can determine that `obj` is a
/// dictionary.
///
/// ## References
/// - [Python documentation: Mapping Types](https://docs.python.org/3/library/stdtypes.html#mapping-types-dict)
///
/// [preview]: https://docs.astral.sh/ruff/preview/
#[violation]
pub struct InDictKeys {
operator: String,
@@ -113,6 +125,28 @@ fn key_in_dict(
.skip_trivia()
.find(|token| token.kind == SimpleTokenKind::Dot)
{
// The fix is only safe if we know the expression is a dictionary, since other types
// can define a `.keys()` method.
let applicability = if checker.settings.preview.is_enabled() {
let is_dict = value.as_name_expr().is_some_and(|name| {
let Some(binding) = checker
.semantic()
.only_binding(name)
.map(|id| checker.semantic().binding(id))
else {
return false;
};
typing::is_dict(binding, checker.semantic())
});
if is_dict {
Applicability::Safe
} else {
Applicability::Unsafe
}
} else {
Applicability::Unsafe
};
// If the `.keys()` is followed by (e.g.) a keyword, we need to insert a space,
// since we're removing parentheses, which could lead to invalid syntax, as in:
// ```python
@@ -126,12 +160,15 @@ fn key_in_dict(
.next()
.is_some_and(|char| char.is_ascii_alphabetic())
{
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
" ".to_string(),
range,
)));
diagnostic.set_fix(Fix::applicable_edit(
Edit::range_replacement(" ".to_string(), range),
applicability,
));
} else {
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_deletion(range)));
diagnostic.set_fix(Fix::applicable_edit(
Edit::range_deletion(range),
applicability,
));
}
}
checker.diagnostics.push(diagnostic);

View File

@@ -1,396 +1,401 @@
---
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
---
SIM118.py:1:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:3:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
1 | key in obj.keys() # SIM118
1 | obj = {}
2 |
3 | key in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^ SIM118
2 |
3 | key not in obj.keys() # SIM118
4 |
5 | key not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
1 |-key in obj.keys() # SIM118
1 |+key in obj # SIM118
1 1 | obj = {}
2 2 |
3 3 | key not in obj.keys() # SIM118
3 |-key in obj.keys() # SIM118
3 |+key in obj # SIM118
4 4 |
5 5 | key not in obj.keys() # SIM118
6 6 |
SIM118.py:3:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
SIM118.py:5:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
1 | key in obj.keys() # SIM118
2 |
3 | key not in obj.keys() # SIM118
3 | key in obj.keys() # SIM118
4 |
5 | key not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^ SIM118
4 |
5 | foo["bar"] in obj.keys() # SIM118
6 |
7 | foo["bar"] in obj.keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
1 1 | key in obj.keys() # SIM118
2 2 |
3 |-key not in obj.keys() # SIM118
3 |+key not in obj # SIM118
3 3 | key in obj.keys() # SIM118
4 4 |
5 5 | foo["bar"] in obj.keys() # SIM118
5 |-key not in obj.keys() # SIM118
5 |+key not in obj # SIM118
6 6 |
7 7 | foo["bar"] in obj.keys() # SIM118
8 8 |
SIM118.py:5:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:7:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
3 | key not in obj.keys() # SIM118
4 |
5 | foo["bar"] in obj.keys() # SIM118
5 | key not in obj.keys() # SIM118
6 |
7 | foo["bar"] in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
6 |
7 | foo["bar"] not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
2 2 |
3 3 | key not in obj.keys() # SIM118
4 4 |
5 |-foo["bar"] in obj.keys() # SIM118
5 |+foo["bar"] in obj # SIM118
6 6 |
7 7 | foo["bar"] not in obj.keys() # SIM118
8 8 |
SIM118.py:7:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
5 | foo["bar"] in obj.keys() # SIM118
6 |
7 | foo["bar"] not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
8 |
9 | foo['bar'] in obj.keys() # SIM118
9 | foo["bar"] not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
4 4 |
5 5 | foo["bar"] in obj.keys() # SIM118
5 5 | key not in obj.keys() # SIM118
6 6 |
7 |-foo["bar"] not in obj.keys() # SIM118
7 |+foo["bar"] not in obj # SIM118
7 |-foo["bar"] in obj.keys() # SIM118
7 |+foo["bar"] in obj # SIM118
8 8 |
9 9 | foo['bar'] in obj.keys() # SIM118
9 9 | foo["bar"] not in obj.keys() # SIM118
10 10 |
SIM118.py:9:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:9:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
7 | foo["bar"] not in obj.keys() # SIM118
7 | foo["bar"] in obj.keys() # SIM118
8 |
9 | foo['bar'] in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
9 | foo["bar"] not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
10 |
11 | foo['bar'] not in obj.keys() # SIM118
11 | foo['bar'] in obj.keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
6 6 |
7 7 | foo["bar"] not in obj.keys() # SIM118
7 7 | foo["bar"] in obj.keys() # SIM118
8 8 |
9 |-foo['bar'] in obj.keys() # SIM118
9 |+foo['bar'] in obj # SIM118
9 |-foo["bar"] not in obj.keys() # SIM118
9 |+foo["bar"] not in obj # SIM118
10 10 |
11 11 | foo['bar'] not in obj.keys() # SIM118
11 11 | foo['bar'] in obj.keys() # SIM118
12 12 |
SIM118.py:11:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
SIM118.py:11:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
9 | foo['bar'] in obj.keys() # SIM118
9 | foo["bar"] not in obj.keys() # SIM118
10 |
11 | foo['bar'] not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
11 | foo['bar'] in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
12 |
13 | foo() in obj.keys() # SIM118
13 | foo['bar'] not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
8 8 |
9 9 | foo['bar'] in obj.keys() # SIM118
9 9 | foo["bar"] not in obj.keys() # SIM118
10 10 |
11 |-foo['bar'] not in obj.keys() # SIM118
11 |+foo['bar'] not in obj # SIM118
11 |-foo['bar'] in obj.keys() # SIM118
11 |+foo['bar'] in obj # SIM118
12 12 |
13 13 | foo() in obj.keys() # SIM118
13 13 | foo['bar'] not in obj.keys() # SIM118
14 14 |
SIM118.py:13:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:13:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
11 | foo['bar'] not in obj.keys() # SIM118
11 | foo['bar'] in obj.keys() # SIM118
12 |
13 | foo() in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^ SIM118
13 | foo['bar'] not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
14 |
15 | foo() not in obj.keys() # SIM118
15 | foo() in obj.keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
10 10 |
11 11 | foo['bar'] not in obj.keys() # SIM118
11 11 | foo['bar'] in obj.keys() # SIM118
12 12 |
13 |-foo() in obj.keys() # SIM118
13 |+foo() in obj # SIM118
13 |-foo['bar'] not in obj.keys() # SIM118
13 |+foo['bar'] not in obj # SIM118
14 14 |
15 15 | foo() not in obj.keys() # SIM118
15 15 | foo() in obj.keys() # SIM118
16 16 |
SIM118.py:15:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
SIM118.py:15:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
13 | foo() in obj.keys() # SIM118
13 | foo['bar'] not in obj.keys() # SIM118
14 |
15 | foo() not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM118
15 | foo() in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^ SIM118
16 |
17 | for key in obj.keys(): # SIM118
17 | foo() not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
12 12 |
13 13 | foo() in obj.keys() # SIM118
13 13 | foo['bar'] not in obj.keys() # SIM118
14 14 |
15 |-foo() not in obj.keys() # SIM118
15 |+foo() not in obj # SIM118
15 |-foo() in obj.keys() # SIM118
15 |+foo() in obj # SIM118
16 16 |
17 17 | for key in obj.keys(): # SIM118
18 18 | pass
17 17 | foo() not in obj.keys() # SIM118
18 18 |
SIM118.py:17:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:17:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
15 | foo() not in obj.keys() # SIM118
15 | foo() in obj.keys() # SIM118
16 |
17 | for key in obj.keys(): # SIM118
| ^^^^^^^^^^^^^^^^^ SIM118
18 | pass
17 | foo() not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM118
18 |
19 | for key in obj.keys(): # SIM118
|
= help: Remove `.keys()`
Suggested fix
14 14 |
15 15 | foo() not in obj.keys() # SIM118
15 15 | foo() in obj.keys() # SIM118
16 16 |
17 |-for key in obj.keys(): # SIM118
17 |+for key in obj: # SIM118
18 18 | pass
19 19 |
20 20 | for key in list(obj.keys()):
17 |-foo() not in obj.keys() # SIM118
17 |+foo() not in obj # SIM118
18 18 |
19 19 | for key in obj.keys(): # SIM118
20 20 | pass
SIM118.py:24:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:19:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
22 | del obj[key]
23 |
24 | [k for k in obj.keys()] # SIM118
| ^^^^^^^^^^^^^^^ SIM118
25 |
26 | {k for k in obj.keys()} # SIM118
17 | foo() not in obj.keys() # SIM118
18 |
19 | for key in obj.keys(): # SIM118
| ^^^^^^^^^^^^^^^^^ SIM118
20 | pass
|
= help: Remove `.keys()`
Suggested fix
21 21 | if some_property(key):
22 22 | del obj[key]
23 23 |
24 |-[k for k in obj.keys()] # SIM118
24 |+[k for k in obj] # SIM118
25 25 |
26 26 | {k for k in obj.keys()} # SIM118
27 27 |
16 16 |
17 17 | foo() not in obj.keys() # SIM118
18 18 |
19 |-for key in obj.keys(): # SIM118
19 |+for key in obj: # SIM118
20 20 | pass
21 21 |
22 22 | for key in list(obj.keys()):
SIM118.py:26:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
24 | [k for k in obj.keys()] # SIM118
24 | del obj[key]
25 |
26 | {k for k in obj.keys()} # SIM118
26 | [k for k in obj.keys()] # SIM118
| ^^^^^^^^^^^^^^^ SIM118
27 |
28 | {k: k for k in obj.keys()} # SIM118
28 | {k for k in obj.keys()} # SIM118
|
= help: Remove `.keys()`
Suggested fix
23 23 |
24 24 | [k for k in obj.keys()] # SIM118
23 23 | if some_property(key):
24 24 | del obj[key]
25 25 |
26 |-{k for k in obj.keys()} # SIM118
26 |+{k for k in obj} # SIM118
26 |-[k for k in obj.keys()] # SIM118
26 |+[k for k in obj] # SIM118
27 27 |
28 28 | {k: k for k in obj.keys()} # SIM118
28 28 | {k for k in obj.keys()} # SIM118
29 29 |
SIM118.py:28:11: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:28:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
26 | {k for k in obj.keys()} # SIM118
26 | [k for k in obj.keys()] # SIM118
27 |
28 | {k: k for k in obj.keys()} # SIM118
28 | {k for k in obj.keys()} # SIM118
| ^^^^^^^^^^^^^^^ SIM118
29 |
30 | {k: k for k in obj.keys()} # SIM118
|
= help: Remove `.keys()`
Suggested fix
25 25 |
26 26 | [k for k in obj.keys()] # SIM118
27 27 |
28 |-{k for k in obj.keys()} # SIM118
28 |+{k for k in obj} # SIM118
29 29 |
30 30 | {k: k for k in obj.keys()} # SIM118
31 31 |
SIM118.py:30:11: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
28 | {k for k in obj.keys()} # SIM118
29 |
30 | {k: k for k in obj.keys()} # SIM118
| ^^^^^^^^^^^^^^^ SIM118
29 |
30 | (k for k in obj.keys()) # SIM118
31 |
32 | (k for k in obj.keys()) # SIM118
|
= help: Remove `.keys()`
Suggested fix
25 25 |
26 26 | {k for k in obj.keys()} # SIM118
27 27 |
28 |-{k: k for k in obj.keys()} # SIM118
28 |+{k: k for k in obj} # SIM118
28 28 | {k for k in obj.keys()} # SIM118
29 29 |
30 30 | (k for k in obj.keys()) # SIM118
30 |-{k: k for k in obj.keys()} # SIM118
30 |+{k: k for k in obj} # SIM118
31 31 |
32 32 | (k for k in obj.keys()) # SIM118
33 33 |
SIM118.py:30:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:32:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
28 | {k: k for k in obj.keys()} # SIM118
29 |
30 | (k for k in obj.keys()) # SIM118
30 | {k: k for k in obj.keys()} # SIM118
31 |
32 | (k for k in obj.keys()) # SIM118
| ^^^^^^^^^^^^^^^ SIM118
31 |
32 | key in (obj or {}).keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
27 27 |
28 28 | {k: k for k in obj.keys()} # SIM118
29 29 |
30 |-(k for k in obj.keys()) # SIM118
30 |+(k for k in obj) # SIM118
31 31 |
32 32 | key in (obj or {}).keys() # SIM118
33 33 |
SIM118.py:32:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
30 | (k for k in obj.keys()) # SIM118
31 |
32 | key in (obj or {}).keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
33 |
34 | (key) in (obj or {}).keys() # SIM118
34 | key in (obj or {}).keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
29 29 |
30 30 | (k for k in obj.keys()) # SIM118
30 30 | {k: k for k in obj.keys()} # SIM118
31 31 |
32 |-key in (obj or {}).keys() # SIM118
32 |+key in (obj or {}) # SIM118
32 |-(k for k in obj.keys()) # SIM118
32 |+(k for k in obj) # SIM118
33 33 |
34 34 | (key) in (obj or {}).keys() # SIM118
34 34 | key in (obj or {}).keys() # SIM118
35 35 |
SIM118.py:34:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
32 | key in (obj or {}).keys() # SIM118
32 | (k for k in obj.keys()) # SIM118
33 |
34 | (key) in (obj or {}).keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
34 | key in (obj or {}).keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
35 |
36 | from typing import KeysView
36 | (key) in (obj or {}).keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
31 31 |
32 32 | key in (obj or {}).keys() # SIM118
32 32 | (k for k in obj.keys()) # SIM118
33 33 |
34 |-(key) in (obj or {}).keys() # SIM118
34 |+(key) in (obj or {}) # SIM118
34 |-key in (obj or {}).keys() # SIM118
34 |+key in (obj or {}) # SIM118
35 35 |
36 36 | from typing import KeysView
36 36 | (key) in (obj or {}).keys() # SIM118
37 37 |
SIM118.py:48:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:36:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
47 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
48 | key in obj.keys()and foo
| ^^^^^^^^^^^^^^^^^ SIM118
49 | (key in obj.keys())and foo
50 | key in (obj.keys())and foo
34 | key in (obj or {}).keys() # SIM118
35 |
36 | (key) in (obj or {}).keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
37 |
38 | from typing import KeysView
|
= help: Remove `.keys()`
Suggested fix
45 45 |
46 46 |
47 47 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
48 |-key in obj.keys()and foo
48 |+key in obj and foo
49 49 | (key in obj.keys())and foo
50 50 | key in (obj.keys())and foo
51 51 |
SIM118.py:49:2: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
47 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
48 | key in obj.keys()and foo
49 | (key in obj.keys())and foo
| ^^^^^^^^^^^^^^^^^ SIM118
50 | key in (obj.keys())and foo
|
= help: Remove `.keys()`
Suggested fix
46 46 |
47 47 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
48 48 | key in obj.keys()and foo
49 |-(key in obj.keys())and foo
49 |+(key in obj)and foo
50 50 | key in (obj.keys())and foo
51 51 |
52 52 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
33 33 |
34 34 | key in (obj or {}).keys() # SIM118
35 35 |
36 |-(key) in (obj or {}).keys() # SIM118
36 |+(key) in (obj or {}) # SIM118
37 37 |
38 38 | from typing import KeysView
39 39 |
SIM118.py:50:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
48 | key in obj.keys()and foo
49 | (key in obj.keys())and foo
50 | key in (obj.keys())and foo
49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 | key in obj.keys()and foo
| ^^^^^^^^^^^^^^^^^ SIM118
51 | (key in obj.keys())and foo
52 | key in (obj.keys())and foo
|
= help: Remove `.keys()`
Suggested fix
47 47 |
48 48 |
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 |-key in obj.keys()and foo
50 |+key in obj and foo
51 51 | (key in obj.keys())and foo
52 52 | key in (obj.keys())and foo
53 53 |
SIM118.py:51:2: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 | key in obj.keys()and foo
51 | (key in obj.keys())and foo
| ^^^^^^^^^^^^^^^^^ SIM118
52 | key in (obj.keys())and foo
|
= help: Remove `.keys()`
Suggested fix
48 48 |
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 50 | key in obj.keys()and foo
51 |-(key in obj.keys())and foo
51 |+(key in obj)and foo
52 52 | key in (obj.keys())and foo
53 53 |
54 54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
SIM118.py:52:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
50 | key in obj.keys()and foo
51 | (key in obj.keys())and foo
52 | key in (obj.keys())and foo
| ^^^^^^^^^^^^^^^^^^^ SIM118
51 |
52 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
53 |
54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
= help: Remove `.keys()`
Suggested fix
47 47 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
48 48 | key in obj.keys()and foo
49 49 | (key in obj.keys())and foo
50 |-key in (obj.keys())and foo
50 |+key in (obj)and foo
51 51 |
52 52 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
53 53 | for key in (
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 50 | key in obj.keys()and foo
51 51 | (key in obj.keys())and foo
52 |-key in (obj.keys())and foo
52 |+key in (obj)and foo
53 53 |
54 54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
55 55 | for key in (
SIM118.py:53:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
SIM118.py:55:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
52 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
53 | for key in (
54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
55 | for key in (
| _____^
54 | | self.experiment.surveys[0]
55 | | .stations[0]
56 | | .keys()
57 | | ):
56 | | self.experiment.surveys[0]
57 | | .stations[0]
58 | | .keys()
59 | | ):
| |_^ SIM118
58 | continue
60 | continue
|
= help: Remove `.keys()`
Suggested fix
53 53 | for key in (
54 54 | self.experiment.surveys[0]
55 55 | .stations[0]
56 |- .keys()
56 |+
57 57 | ):
58 58 | continue
55 55 | for key in (
56 56 | self.experiment.surveys[0]
57 57 | .stations[0]
58 |- .keys()
58 |+
59 59 | ):
60 60 | continue

View File

@@ -0,0 +1,401 @@
---
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
---
SIM118.py:3:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
1 | obj = {}
2 |
3 | key in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^ SIM118
4 |
5 | key not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Fix
1 1 | obj = {}
2 2 |
3 |-key in obj.keys() # SIM118
3 |+key in obj # SIM118
4 4 |
5 5 | key not in obj.keys() # SIM118
6 6 |
SIM118.py:5:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
3 | key in obj.keys() # SIM118
4 |
5 | key not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^ SIM118
6 |
7 | foo["bar"] in obj.keys() # SIM118
|
= help: Remove `.keys()`
Fix
2 2 |
3 3 | key in obj.keys() # SIM118
4 4 |
5 |-key not in obj.keys() # SIM118
5 |+key not in obj # SIM118
6 6 |
7 7 | foo["bar"] in obj.keys() # SIM118
8 8 |
SIM118.py:7:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
5 | key not in obj.keys() # SIM118
6 |
7 | foo["bar"] in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
8 |
9 | foo["bar"] not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Fix
4 4 |
5 5 | key not in obj.keys() # SIM118
6 6 |
7 |-foo["bar"] in obj.keys() # SIM118
7 |+foo["bar"] in obj # SIM118
8 8 |
9 9 | foo["bar"] not in obj.keys() # SIM118
10 10 |
SIM118.py:9:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
7 | foo["bar"] in obj.keys() # SIM118
8 |
9 | foo["bar"] not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
10 |
11 | foo['bar'] in obj.keys() # SIM118
|
= help: Remove `.keys()`
Fix
6 6 |
7 7 | foo["bar"] in obj.keys() # SIM118
8 8 |
9 |-foo["bar"] not in obj.keys() # SIM118
9 |+foo["bar"] not in obj # SIM118
10 10 |
11 11 | foo['bar'] in obj.keys() # SIM118
12 12 |
SIM118.py:11:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
9 | foo["bar"] not in obj.keys() # SIM118
10 |
11 | foo['bar'] in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
12 |
13 | foo['bar'] not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Fix
8 8 |
9 9 | foo["bar"] not in obj.keys() # SIM118
10 10 |
11 |-foo['bar'] in obj.keys() # SIM118
11 |+foo['bar'] in obj # SIM118
12 12 |
13 13 | foo['bar'] not in obj.keys() # SIM118
14 14 |
SIM118.py:13:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
11 | foo['bar'] in obj.keys() # SIM118
12 |
13 | foo['bar'] not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
14 |
15 | foo() in obj.keys() # SIM118
|
= help: Remove `.keys()`
Fix
10 10 |
11 11 | foo['bar'] in obj.keys() # SIM118
12 12 |
13 |-foo['bar'] not in obj.keys() # SIM118
13 |+foo['bar'] not in obj # SIM118
14 14 |
15 15 | foo() in obj.keys() # SIM118
16 16 |
SIM118.py:15:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
13 | foo['bar'] not in obj.keys() # SIM118
14 |
15 | foo() in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^ SIM118
16 |
17 | foo() not in obj.keys() # SIM118
|
= help: Remove `.keys()`
Fix
12 12 |
13 13 | foo['bar'] not in obj.keys() # SIM118
14 14 |
15 |-foo() in obj.keys() # SIM118
15 |+foo() in obj # SIM118
16 16 |
17 17 | foo() not in obj.keys() # SIM118
18 18 |
SIM118.py:17:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
15 | foo() in obj.keys() # SIM118
16 |
17 | foo() not in obj.keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM118
18 |
19 | for key in obj.keys(): # SIM118
|
= help: Remove `.keys()`
Fix
14 14 |
15 15 | foo() in obj.keys() # SIM118
16 16 |
17 |-foo() not in obj.keys() # SIM118
17 |+foo() not in obj # SIM118
18 18 |
19 19 | for key in obj.keys(): # SIM118
20 20 | pass
SIM118.py:19:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
17 | foo() not in obj.keys() # SIM118
18 |
19 | for key in obj.keys(): # SIM118
| ^^^^^^^^^^^^^^^^^ SIM118
20 | pass
|
= help: Remove `.keys()`
Fix
16 16 |
17 17 | foo() not in obj.keys() # SIM118
18 18 |
19 |-for key in obj.keys(): # SIM118
19 |+for key in obj: # SIM118
20 20 | pass
21 21 |
22 22 | for key in list(obj.keys()):
SIM118.py:26:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
24 | del obj[key]
25 |
26 | [k for k in obj.keys()] # SIM118
| ^^^^^^^^^^^^^^^ SIM118
27 |
28 | {k for k in obj.keys()} # SIM118
|
= help: Remove `.keys()`
Fix
23 23 | if some_property(key):
24 24 | del obj[key]
25 25 |
26 |-[k for k in obj.keys()] # SIM118
26 |+[k for k in obj] # SIM118
27 27 |
28 28 | {k for k in obj.keys()} # SIM118
29 29 |
SIM118.py:28:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
26 | [k for k in obj.keys()] # SIM118
27 |
28 | {k for k in obj.keys()} # SIM118
| ^^^^^^^^^^^^^^^ SIM118
29 |
30 | {k: k for k in obj.keys()} # SIM118
|
= help: Remove `.keys()`
Fix
25 25 |
26 26 | [k for k in obj.keys()] # SIM118
27 27 |
28 |-{k for k in obj.keys()} # SIM118
28 |+{k for k in obj} # SIM118
29 29 |
30 30 | {k: k for k in obj.keys()} # SIM118
31 31 |
SIM118.py:30:11: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
28 | {k for k in obj.keys()} # SIM118
29 |
30 | {k: k for k in obj.keys()} # SIM118
| ^^^^^^^^^^^^^^^ SIM118
31 |
32 | (k for k in obj.keys()) # SIM118
|
= help: Remove `.keys()`
Fix
27 27 |
28 28 | {k for k in obj.keys()} # SIM118
29 29 |
30 |-{k: k for k in obj.keys()} # SIM118
30 |+{k: k for k in obj} # SIM118
31 31 |
32 32 | (k for k in obj.keys()) # SIM118
33 33 |
SIM118.py:32:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
30 | {k: k for k in obj.keys()} # SIM118
31 |
32 | (k for k in obj.keys()) # SIM118
| ^^^^^^^^^^^^^^^ SIM118
33 |
34 | key in (obj or {}).keys() # SIM118
|
= help: Remove `.keys()`
Fix
29 29 |
30 30 | {k: k for k in obj.keys()} # SIM118
31 31 |
32 |-(k for k in obj.keys()) # SIM118
32 |+(k for k in obj) # SIM118
33 33 |
34 34 | key in (obj or {}).keys() # SIM118
35 35 |
SIM118.py:34:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
32 | (k for k in obj.keys()) # SIM118
33 |
34 | key in (obj or {}).keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
35 |
36 | (key) in (obj or {}).keys() # SIM118
|
= help: Remove `.keys()`
Suggested fix
31 31 |
32 32 | (k for k in obj.keys()) # SIM118
33 33 |
34 |-key in (obj or {}).keys() # SIM118
34 |+key in (obj or {}) # SIM118
35 35 |
36 36 | (key) in (obj or {}).keys() # SIM118
37 37 |
SIM118.py:36:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
34 | key in (obj or {}).keys() # SIM118
35 |
36 | (key) in (obj or {}).keys() # SIM118
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
37 |
38 | from typing import KeysView
|
= help: Remove `.keys()`
Suggested fix
33 33 |
34 34 | key in (obj or {}).keys() # SIM118
35 35 |
36 |-(key) in (obj or {}).keys() # SIM118
36 |+(key) in (obj or {}) # SIM118
37 37 |
38 38 | from typing import KeysView
39 39 |
SIM118.py:50:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 | key in obj.keys()and foo
| ^^^^^^^^^^^^^^^^^ SIM118
51 | (key in obj.keys())and foo
52 | key in (obj.keys())and foo
|
= help: Remove `.keys()`
Fix
47 47 |
48 48 |
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 |-key in obj.keys()and foo
50 |+key in obj and foo
51 51 | (key in obj.keys())and foo
52 52 | key in (obj.keys())and foo
53 53 |
SIM118.py:51:2: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 | key in obj.keys()and foo
51 | (key in obj.keys())and foo
| ^^^^^^^^^^^^^^^^^ SIM118
52 | key in (obj.keys())and foo
|
= help: Remove `.keys()`
Fix
48 48 |
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 50 | key in obj.keys()and foo
51 |-(key in obj.keys())and foo
51 |+(key in obj)and foo
52 52 | key in (obj.keys())and foo
53 53 |
54 54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
SIM118.py:52:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
50 | key in obj.keys()and foo
51 | (key in obj.keys())and foo
52 | key in (obj.keys())and foo
| ^^^^^^^^^^^^^^^^^^^ SIM118
53 |
54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
= help: Remove `.keys()`
Fix
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
50 50 | key in obj.keys()and foo
51 51 | (key in obj.keys())and foo
52 |-key in (obj.keys())and foo
52 |+key in (obj)and foo
53 53 |
54 54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
55 55 | for key in (
SIM118.py:55:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
55 | for key in (
| _____^
56 | | self.experiment.surveys[0]
57 | | .stations[0]
58 | | .keys()
59 | | ):
| |_^ SIM118
60 | continue
|
= help: Remove `.keys()`
Suggested fix
55 55 | for key in (
56 56 | self.experiment.surveys[0]
57 57 | .stations[0]
58 |- .keys()
58 |+
59 59 | ):
60 60 | continue

View File

@@ -0,0 +1,157 @@
use ruff_python_ast::call_path::CallPath;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(super) enum MethodName {
AcloseForcefully,
CancelScope,
CancelShieldedCheckpoint,
Checkpoint,
CheckpointIfCancelled,
FailAfter,
FailAt,
MoveOnAfter,
MoveOnAt,
OpenFile,
OpenProcess,
OpenSslOverTcpListeners,
OpenSslOverTcpStream,
OpenTcpListeners,
OpenTcpStream,
OpenUnixSocket,
PermanentlyDetachCoroutineObject,
ReattachDetachedCoroutineObject,
RunProcess,
ServeListeners,
ServeSslOverTcp,
ServeTcp,
Sleep,
SleepForever,
TemporarilyDetachCoroutineObject,
WaitReadable,
WaitTaskRescheduled,
WaitWritable,
}
impl MethodName {
/// Returns `true` if the method is async, `false` if it is sync.
pub(super) fn is_async(self) -> bool {
match self {
MethodName::AcloseForcefully
| MethodName::CancelShieldedCheckpoint
| MethodName::Checkpoint
| MethodName::CheckpointIfCancelled
| MethodName::OpenFile
| MethodName::OpenProcess
| MethodName::OpenSslOverTcpListeners
| MethodName::OpenSslOverTcpStream
| MethodName::OpenTcpListeners
| MethodName::OpenTcpStream
| MethodName::OpenUnixSocket
| MethodName::PermanentlyDetachCoroutineObject
| MethodName::ReattachDetachedCoroutineObject
| MethodName::RunProcess
| MethodName::ServeListeners
| MethodName::ServeSslOverTcp
| MethodName::ServeTcp
| MethodName::Sleep
| MethodName::SleepForever
| MethodName::TemporarilyDetachCoroutineObject
| MethodName::WaitReadable
| MethodName::WaitTaskRescheduled
| MethodName::WaitWritable => true,
MethodName::MoveOnAfter
| MethodName::MoveOnAt
| MethodName::FailAfter
| MethodName::FailAt
| MethodName::CancelScope => false,
}
}
}
impl MethodName {
pub(super) fn try_from(call_path: &CallPath<'_>) -> Option<Self> {
match call_path.as_slice() {
["trio", "CancelScope"] => Some(Self::CancelScope),
["trio", "aclose_forcefully"] => Some(Self::AcloseForcefully),
["trio", "fail_after"] => Some(Self::FailAfter),
["trio", "fail_at"] => Some(Self::FailAt),
["trio", "lowlevel", "cancel_shielded_checkpoint"] => {
Some(Self::CancelShieldedCheckpoint)
}
["trio", "lowlevel", "checkpoint"] => Some(Self::Checkpoint),
["trio", "lowlevel", "checkpoint_if_cancelled"] => Some(Self::CheckpointIfCancelled),
["trio", "lowlevel", "open_process"] => Some(Self::OpenProcess),
["trio", "lowlevel", "permanently_detach_coroutine_object"] => {
Some(Self::PermanentlyDetachCoroutineObject)
}
["trio", "lowlevel", "reattach_detached_coroutine_object"] => {
Some(Self::ReattachDetachedCoroutineObject)
}
["trio", "lowlevel", "temporarily_detach_coroutine_object"] => {
Some(Self::TemporarilyDetachCoroutineObject)
}
["trio", "lowlevel", "wait_readable"] => Some(Self::WaitReadable),
["trio", "lowlevel", "wait_task_rescheduled"] => Some(Self::WaitTaskRescheduled),
["trio", "lowlevel", "wait_writable"] => Some(Self::WaitWritable),
["trio", "move_on_after"] => Some(Self::MoveOnAfter),
["trio", "move_on_at"] => Some(Self::MoveOnAt),
["trio", "open_file"] => Some(Self::OpenFile),
["trio", "open_ssl_over_tcp_listeners"] => Some(Self::OpenSslOverTcpListeners),
["trio", "open_ssl_over_tcp_stream"] => Some(Self::OpenSslOverTcpStream),
["trio", "open_tcp_listeners"] => Some(Self::OpenTcpListeners),
["trio", "open_tcp_stream"] => Some(Self::OpenTcpStream),
["trio", "open_unix_socket"] => Some(Self::OpenUnixSocket),
["trio", "run_process"] => Some(Self::RunProcess),
["trio", "serve_listeners"] => Some(Self::ServeListeners),
["trio", "serve_ssl_over_tcp"] => Some(Self::ServeSslOverTcp),
["trio", "serve_tcp"] => Some(Self::ServeTcp),
["trio", "sleep"] => Some(Self::Sleep),
["trio", "sleep_forever"] => Some(Self::SleepForever),
_ => None,
}
}
}
impl std::fmt::Display for MethodName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MethodName::AcloseForcefully => write!(f, "trio.aclose_forcefully"),
MethodName::CancelScope => write!(f, "trio.CancelScope"),
MethodName::CancelShieldedCheckpoint => {
write!(f, "trio.lowlevel.cancel_shielded_checkpoint")
}
MethodName::Checkpoint => write!(f, "trio.lowlevel.checkpoint"),
MethodName::CheckpointIfCancelled => write!(f, "trio.lowlevel.checkpoint_if_cancelled"),
MethodName::FailAfter => write!(f, "trio.fail_after"),
MethodName::FailAt => write!(f, "trio.fail_at"),
MethodName::MoveOnAfter => write!(f, "trio.move_on_after"),
MethodName::MoveOnAt => write!(f, "trio.move_on_at"),
MethodName::OpenFile => write!(f, "trio.open_file"),
MethodName::OpenProcess => write!(f, "trio.lowlevel.open_process"),
MethodName::OpenSslOverTcpListeners => write!(f, "trio.open_ssl_over_tcp_listeners"),
MethodName::OpenSslOverTcpStream => write!(f, "trio.open_ssl_over_tcp_stream"),
MethodName::OpenTcpListeners => write!(f, "trio.open_tcp_listeners"),
MethodName::OpenTcpStream => write!(f, "trio.open_tcp_stream"),
MethodName::OpenUnixSocket => write!(f, "trio.open_unix_socket"),
MethodName::PermanentlyDetachCoroutineObject => {
write!(f, "trio.lowlevel.permanently_detach_coroutine_object")
}
MethodName::ReattachDetachedCoroutineObject => {
write!(f, "trio.lowlevel.reattach_detached_coroutine_object")
}
MethodName::RunProcess => write!(f, "trio.run_process"),
MethodName::ServeListeners => write!(f, "trio.serve_listeners"),
MethodName::ServeSslOverTcp => write!(f, "trio.serve_ssl_over_tcp"),
MethodName::ServeTcp => write!(f, "trio.serve_tcp"),
MethodName::Sleep => write!(f, "trio.sleep"),
MethodName::SleepForever => write!(f, "trio.sleep_forever"),
MethodName::TemporarilyDetachCoroutineObject => {
write!(f, "trio.lowlevel.temporarily_detach_coroutine_object")
}
MethodName::WaitReadable => write!(f, "trio.lowlevel.wait_readable"),
MethodName::WaitTaskRescheduled => write!(f, "trio.lowlevel.wait_task_rescheduled"),
MethodName::WaitWritable => write!(f, "trio.lowlevel.wait_writable"),
}
}
}

View File

@@ -1,4 +1,5 @@
//! Rules from [flake8-trio](https://pypi.org/project/flake8-trio/).
pub(super) mod method_name;
pub(crate) mod rules;
#[cfg(test)]
@@ -14,6 +15,8 @@ mod tests {
use crate::test::test_path;
#[test_case(Rule::TrioTimeoutWithoutAwait, Path::new("TRIO100.py"))]
#[test_case(Rule::TrioSyncCall, Path::new("TRIO105.py"))]
#[test_case(Rule::TrioZeroSleepCall, Path::new("TRIO115.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -1,3 +1,7 @@
pub(crate) use sync_call::*;
pub(crate) use timeout_without_await::*;
pub(crate) use zero_sleep_call::*;
mod sync_call;
mod timeout_without_await;
mod zero_sleep_call;

View File

@@ -0,0 +1,87 @@
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{Expr, ExprCall};
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
use crate::fix::edits::pad;
use crate::rules::flake8_trio::method_name::MethodName;
/// ## What it does
/// Checks for calls to trio functions that are not immediately awaited.
///
/// ## Why is this bad?
/// Many of the functions exposed by trio are asynchronous, and must be awaited
/// to take effect. Calling a trio function without an `await` can lead to
/// `RuntimeWarning` diagnostics and unexpected behaviour.
///
/// ## Example
/// ```python
/// async def double_sleep(x):
/// trio.sleep(2 * x)
/// ```
///
/// Use instead:
/// ```python
/// async def double_sleep(x):
/// await trio.sleep(2 * x)
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as adding an `await` to a function
/// call changes its semantics and runtime behavior.
#[violation]
pub struct TrioSyncCall {
method_name: MethodName,
}
impl Violation for TrioSyncCall {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let Self { method_name } = self;
format!("Call to `{method_name}` is not immediately awaited")
}
fn fix_title(&self) -> Option<String> {
Some(format!("Add `await`"))
}
}
/// TRIO105
pub(crate) fn sync_call(checker: &mut Checker, call: &ExprCall) {
let Some(method_name) = ({
let Some(call_path) = checker.semantic().resolve_call_path(call.func.as_ref()) else {
return;
};
MethodName::try_from(&call_path)
}) else {
return;
};
if !method_name.is_async() {
return;
}
if checker
.semantic()
.current_expression_parent()
.is_some_and(Expr::is_await_expr)
{
return;
};
let mut diagnostic = Diagnostic::new(TrioSyncCall { method_name }, call.range);
if checker.semantic().in_async_context() {
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
pad(
"await".to_string(),
TextRange::new(call.func.start(), call.func.start()),
checker.locator(),
),
call.func.start(),
)));
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -1,10 +1,11 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::CallPath;
use ruff_python_ast::visitor::{walk_expr, walk_stmt, Visitor};
use ruff_python_ast::{Expr, ExprAwait, Stmt, StmtWith, WithItem};
use ruff_python_ast::helpers::AwaitVisitor;
use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{StmtWith, WithItem};
use crate::checkers::ast::Checker;
use crate::rules::flake8_trio::method_name::MethodName;
/// ## What it does
/// Checks for trio functions that should contain await but don't.
@@ -56,6 +57,17 @@ pub(crate) fn timeout_without_await(
return;
};
if !matches!(
method_name,
MethodName::MoveOnAfter
| MethodName::MoveOnAt
| MethodName::FailAfter
| MethodName::FailAt
| MethodName::CancelScope
) {
return;
}
let mut visitor = AwaitVisitor::default();
visitor.visit_body(&with_stmt.body);
if visitor.seen_await {
@@ -67,59 +79,3 @@ pub(crate) fn timeout_without_await(
with_stmt.range,
));
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum MethodName {
MoveOnAfter,
MoveOnAt,
FailAfter,
FailAt,
CancelScope,
}
impl MethodName {
fn try_from(call_path: &CallPath<'_>) -> Option<Self> {
match call_path.as_slice() {
["trio", "move_on_after"] => Some(Self::MoveOnAfter),
["trio", "move_on_at"] => Some(Self::MoveOnAt),
["trio", "fail_after"] => Some(Self::FailAfter),
["trio", "fail_at"] => Some(Self::FailAt),
["trio", "CancelScope"] => Some(Self::CancelScope),
_ => None,
}
}
}
impl std::fmt::Display for MethodName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MethodName::MoveOnAfter => write!(f, "trio.move_on_after"),
MethodName::MoveOnAt => write!(f, "trio.move_on_at"),
MethodName::FailAfter => write!(f, "trio.fail_after"),
MethodName::FailAt => write!(f, "trio.fail_at"),
MethodName::CancelScope => write!(f, "trio.CancelScope"),
}
}
}
#[derive(Debug, Default)]
struct AwaitVisitor {
seen_await: bool,
}
impl Visitor<'_> for AwaitVisitor {
fn visit_stmt(&mut self, stmt: &Stmt) {
match stmt {
Stmt::FunctionDef(_) | Stmt::ClassDef(_) => (),
_ => walk_stmt(self, stmt),
}
}
fn visit_expr(&mut self, expr: &Expr) {
if let Expr::Await(ExprAwait { .. }) = expr {
self.seen_await = true;
} else {
walk_expr(self, expr);
}
}
}

View File

@@ -0,0 +1,109 @@
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::Stmt;
use ruff_python_ast::{self as ast, Expr, ExprCall, Int};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
/// ## What it does
/// Checks for uses of `trio.sleep(0)`.
///
/// ## Why is this bad?
/// `trio.sleep(0)` is equivalent to calling `trio.lowlevel.checkpoint()`.
/// However, the latter better conveys the intent of the code.
///
/// ## Example
/// ```python
/// async def func():
/// await trio.sleep(0)
/// ```
///
/// Use instead:
/// ```python
/// async def func():
/// await trio.lowlevel.checkpoint()
/// ```
#[violation]
pub struct TrioZeroSleepCall;
impl AlwaysFixableViolation for TrioZeroSleepCall {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`")
}
fn fix_title(&self) -> String {
format!("Replace with `trio.lowlevel.checkpoint()`")
}
}
/// TRIO115
pub(crate) fn zero_sleep_call(checker: &mut Checker, call: &ExprCall) {
if !checker
.semantic()
.resolve_call_path(call.func.as_ref())
.is_some_and(|call_path| matches!(call_path.as_slice(), ["trio", "sleep"]))
{
return;
}
if call.arguments.len() != 1 {
return;
}
let Some(arg) = call.arguments.find_argument("seconds", 0) else {
return;
};
match arg {
Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => {
let Some(int) = value.as_int() else { return };
if *int != Int::ZERO {
return;
}
}
Expr::Name(ast::ExprName { id, .. }) => {
let scope = checker.semantic().current_scope();
if let Some(binding_id) = scope.get(id) {
let binding = checker.semantic().binding(binding_id);
if binding.kind.is_assignment() || binding.kind.is_named_expr_assignment() {
if let Some(parent_id) = binding.source {
let parent = checker.semantic().statement(parent_id);
if let Stmt::Assign(ast::StmtAssign { value, .. })
| Stmt::AnnAssign(ast::StmtAnnAssign {
value: Some(value), ..
})
| Stmt::AugAssign(ast::StmtAugAssign { value, .. }) = parent
{
let Expr::NumberLiteral(ast::ExprNumberLiteral { value: num, .. }) =
value.as_ref()
else {
return;
};
let Some(int) = num.as_int() else { return };
if *int != Int::ZERO {
return;
}
}
}
}
}
}
_ => return,
}
let mut diagnostic = Diagnostic::new(TrioZeroSleepCall, call.range());
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("trio", "lowlevel.checkpoint"),
call.func.start(),
checker.semantic(),
)?;
let reference_edit = Edit::range_replacement(binding, call.func.range());
let arg_edit = Edit::range_deletion(call.arguments.range);
Ok(Fix::safe_edits(import_edit, [reference_edit, arg_edit]))
});
checker.diagnostics.push(diagnostic);
}

View File

@@ -0,0 +1,514 @@
---
source: crates/ruff_linter/src/rules/flake8_trio/mod.rs
---
TRIO105.py:30:5: TRIO105 [*] Call to `trio.aclose_forcefully` is not immediately awaited
|
29 | # TRIO105
30 | trio.aclose_forcefully(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
31 | trio.open_file(foo)
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
= help: Add `await`
Suggested fix
27 27 | await trio.lowlevel.wait_writable(foo)
28 28 |
29 29 | # TRIO105
30 |- trio.aclose_forcefully(foo)
30 |+ await trio.aclose_forcefully(foo)
31 31 | trio.open_file(foo)
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
TRIO105.py:31:5: TRIO105 [*] Call to `trio.open_file` is not immediately awaited
|
29 | # TRIO105
30 | trio.aclose_forcefully(foo)
31 | trio.open_file(foo)
| ^^^^^^^^^^^^^^^^^^^ TRIO105
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
= help: Add `await`
Suggested fix
28 28 |
29 29 | # TRIO105
30 30 | trio.aclose_forcefully(foo)
31 |- trio.open_file(foo)
31 |+ await trio.open_file(foo)
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 34 | trio.open_tcp_listeners(foo)
TRIO105.py:32:5: TRIO105 [*] Call to `trio.open_ssl_over_tcp_listeners` is not immediately awaited
|
30 | trio.aclose_forcefully(foo)
31 | trio.open_file(foo)
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 | trio.open_tcp_listeners(foo)
|
= help: Add `await`
Suggested fix
29 29 | # TRIO105
30 30 | trio.aclose_forcefully(foo)
31 31 | trio.open_file(foo)
32 |- trio.open_ssl_over_tcp_listeners(foo, foo)
32 |+ await trio.open_ssl_over_tcp_listeners(foo, foo)
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 34 | trio.open_tcp_listeners(foo)
35 35 | trio.open_tcp_stream(foo, foo)
TRIO105.py:33:5: TRIO105 [*] Call to `trio.open_ssl_over_tcp_stream` is not immediately awaited
|
31 | trio.open_file(foo)
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 | trio.open_ssl_over_tcp_stream(foo, foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
34 | trio.open_tcp_listeners(foo)
35 | trio.open_tcp_stream(foo, foo)
|
= help: Add `await`
Suggested fix
30 30 | trio.aclose_forcefully(foo)
31 31 | trio.open_file(foo)
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 |- trio.open_ssl_over_tcp_stream(foo, foo)
33 |+ await trio.open_ssl_over_tcp_stream(foo, foo)
34 34 | trio.open_tcp_listeners(foo)
35 35 | trio.open_tcp_stream(foo, foo)
36 36 | trio.open_unix_socket(foo)
TRIO105.py:34:5: TRIO105 [*] Call to `trio.open_tcp_listeners` is not immediately awaited
|
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 | trio.open_tcp_listeners(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
35 | trio.open_tcp_stream(foo, foo)
36 | trio.open_unix_socket(foo)
|
= help: Add `await`
Suggested fix
31 31 | trio.open_file(foo)
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 |- trio.open_tcp_listeners(foo)
34 |+ await trio.open_tcp_listeners(foo)
35 35 | trio.open_tcp_stream(foo, foo)
36 36 | trio.open_unix_socket(foo)
37 37 | trio.run_process(foo)
TRIO105.py:35:5: TRIO105 [*] Call to `trio.open_tcp_stream` is not immediately awaited
|
33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 | trio.open_tcp_listeners(foo)
35 | trio.open_tcp_stream(foo, foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
36 | trio.open_unix_socket(foo)
37 | trio.run_process(foo)
|
= help: Add `await`
Suggested fix
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 34 | trio.open_tcp_listeners(foo)
35 |- trio.open_tcp_stream(foo, foo)
35 |+ await trio.open_tcp_stream(foo, foo)
36 36 | trio.open_unix_socket(foo)
37 37 | trio.run_process(foo)
38 38 | trio.serve_listeners(foo, foo)
TRIO105.py:36:5: TRIO105 [*] Call to `trio.open_unix_socket` is not immediately awaited
|
34 | trio.open_tcp_listeners(foo)
35 | trio.open_tcp_stream(foo, foo)
36 | trio.open_unix_socket(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
37 | trio.run_process(foo)
38 | trio.serve_listeners(foo, foo)
|
= help: Add `await`
Suggested fix
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
34 34 | trio.open_tcp_listeners(foo)
35 35 | trio.open_tcp_stream(foo, foo)
36 |- trio.open_unix_socket(foo)
36 |+ await trio.open_unix_socket(foo)
37 37 | trio.run_process(foo)
38 38 | trio.serve_listeners(foo, foo)
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
TRIO105.py:37:5: TRIO105 [*] Call to `trio.run_process` is not immediately awaited
|
35 | trio.open_tcp_stream(foo, foo)
36 | trio.open_unix_socket(foo)
37 | trio.run_process(foo)
| ^^^^^^^^^^^^^^^^^^^^^ TRIO105
38 | trio.serve_listeners(foo, foo)
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
= help: Add `await`
Suggested fix
34 34 | trio.open_tcp_listeners(foo)
35 35 | trio.open_tcp_stream(foo, foo)
36 36 | trio.open_unix_socket(foo)
37 |- trio.run_process(foo)
37 |+ await trio.run_process(foo)
38 38 | trio.serve_listeners(foo, foo)
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 40 | trio.serve_tcp(foo, foo)
TRIO105.py:38:5: TRIO105 [*] Call to `trio.serve_listeners` is not immediately awaited
|
36 | trio.open_unix_socket(foo)
37 | trio.run_process(foo)
38 | trio.serve_listeners(foo, foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 | trio.serve_tcp(foo, foo)
|
= help: Add `await`
Suggested fix
35 35 | trio.open_tcp_stream(foo, foo)
36 36 | trio.open_unix_socket(foo)
37 37 | trio.run_process(foo)
38 |- trio.serve_listeners(foo, foo)
38 |+ await trio.serve_listeners(foo, foo)
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 40 | trio.serve_tcp(foo, foo)
41 41 | trio.sleep(foo)
TRIO105.py:39:5: TRIO105 [*] Call to `trio.serve_ssl_over_tcp` is not immediately awaited
|
37 | trio.run_process(foo)
38 | trio.serve_listeners(foo, foo)
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
40 | trio.serve_tcp(foo, foo)
41 | trio.sleep(foo)
|
= help: Add `await`
Suggested fix
36 36 | trio.open_unix_socket(foo)
37 37 | trio.run_process(foo)
38 38 | trio.serve_listeners(foo, foo)
39 |- trio.serve_ssl_over_tcp(foo, foo, foo)
39 |+ await trio.serve_ssl_over_tcp(foo, foo, foo)
40 40 | trio.serve_tcp(foo, foo)
41 41 | trio.sleep(foo)
42 42 | trio.sleep_forever()
TRIO105.py:40:5: TRIO105 [*] Call to `trio.serve_tcp` is not immediately awaited
|
38 | trio.serve_listeners(foo, foo)
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 | trio.serve_tcp(foo, foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
41 | trio.sleep(foo)
42 | trio.sleep_forever()
|
= help: Add `await`
Suggested fix
37 37 | trio.run_process(foo)
38 38 | trio.serve_listeners(foo, foo)
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 |- trio.serve_tcp(foo, foo)
40 |+ await trio.serve_tcp(foo, foo)
41 41 | trio.sleep(foo)
42 42 | trio.sleep_forever()
43 43 | trio.sleep_until(foo)
TRIO105.py:41:5: TRIO105 [*] Call to `trio.sleep` is not immediately awaited
|
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 | trio.serve_tcp(foo, foo)
41 | trio.sleep(foo)
| ^^^^^^^^^^^^^^^ TRIO105
42 | trio.sleep_forever()
43 | trio.sleep_until(foo)
|
= help: Add `await`
Suggested fix
38 38 | trio.serve_listeners(foo, foo)
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 40 | trio.serve_tcp(foo, foo)
41 |- trio.sleep(foo)
41 |+ await trio.sleep(foo)
42 42 | trio.sleep_forever()
43 43 | trio.sleep_until(foo)
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
TRIO105.py:42:5: TRIO105 [*] Call to `trio.sleep_forever` is not immediately awaited
|
40 | trio.serve_tcp(foo, foo)
41 | trio.sleep(foo)
42 | trio.sleep_forever()
| ^^^^^^^^^^^^^^^^^^^^ TRIO105
43 | trio.sleep_until(foo)
44 | trio.lowlevel.cancel_shielded_checkpoint()
|
= help: Add `await`
Suggested fix
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
40 40 | trio.serve_tcp(foo, foo)
41 41 | trio.sleep(foo)
42 |- trio.sleep_forever()
42 |+ await trio.sleep_forever()
43 43 | trio.sleep_until(foo)
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
45 45 | trio.lowlevel.checkpoint()
TRIO105.py:44:5: TRIO105 [*] Call to `trio.lowlevel.cancel_shielded_checkpoint` is not immediately awaited
|
42 | trio.sleep_forever()
43 | trio.sleep_until(foo)
44 | trio.lowlevel.cancel_shielded_checkpoint()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
45 | trio.lowlevel.checkpoint()
46 | trio.lowlevel.checkpoint_if_cancelled()
|
= help: Add `await`
Suggested fix
41 41 | trio.sleep(foo)
42 42 | trio.sleep_forever()
43 43 | trio.sleep_until(foo)
44 |- trio.lowlevel.cancel_shielded_checkpoint()
44 |+ await trio.lowlevel.cancel_shielded_checkpoint()
45 45 | trio.lowlevel.checkpoint()
46 46 | trio.lowlevel.checkpoint_if_cancelled()
47 47 | trio.lowlevel.open_process()
TRIO105.py:45:5: TRIO105 [*] Call to `trio.lowlevel.checkpoint` is not immediately awaited
|
43 | trio.sleep_until(foo)
44 | trio.lowlevel.cancel_shielded_checkpoint()
45 | trio.lowlevel.checkpoint()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
46 | trio.lowlevel.checkpoint_if_cancelled()
47 | trio.lowlevel.open_process()
|
= help: Add `await`
Suggested fix
42 42 | trio.sleep_forever()
43 43 | trio.sleep_until(foo)
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
45 |- trio.lowlevel.checkpoint()
45 |+ await trio.lowlevel.checkpoint()
46 46 | trio.lowlevel.checkpoint_if_cancelled()
47 47 | trio.lowlevel.open_process()
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
TRIO105.py:46:5: TRIO105 [*] Call to `trio.lowlevel.checkpoint_if_cancelled` is not immediately awaited
|
44 | trio.lowlevel.cancel_shielded_checkpoint()
45 | trio.lowlevel.checkpoint()
46 | trio.lowlevel.checkpoint_if_cancelled()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
47 | trio.lowlevel.open_process()
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
= help: Add `await`
Suggested fix
43 43 | trio.sleep_until(foo)
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
45 45 | trio.lowlevel.checkpoint()
46 |- trio.lowlevel.checkpoint_if_cancelled()
46 |+ await trio.lowlevel.checkpoint_if_cancelled()
47 47 | trio.lowlevel.open_process()
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
TRIO105.py:47:5: TRIO105 [*] Call to `trio.lowlevel.open_process` is not immediately awaited
|
45 | trio.lowlevel.checkpoint()
46 | trio.lowlevel.checkpoint_if_cancelled()
47 | trio.lowlevel.open_process()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
= help: Add `await`
Suggested fix
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
45 45 | trio.lowlevel.checkpoint()
46 46 | trio.lowlevel.checkpoint_if_cancelled()
47 |- trio.lowlevel.open_process()
47 |+ await trio.lowlevel.open_process()
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
TRIO105.py:48:5: TRIO105 [*] Call to `trio.lowlevel.permanently_detach_coroutine_object` is not immediately awaited
|
46 | trio.lowlevel.checkpoint_if_cancelled()
47 | trio.lowlevel.open_process()
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
= help: Add `await`
Suggested fix
45 45 | trio.lowlevel.checkpoint()
46 46 | trio.lowlevel.checkpoint_if_cancelled()
47 47 | trio.lowlevel.open_process()
48 |- trio.lowlevel.permanently_detach_coroutine_object(foo)
48 |+ await trio.lowlevel.permanently_detach_coroutine_object(foo)
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 51 | trio.lowlevel.wait_readable(foo)
TRIO105.py:49:5: TRIO105 [*] Call to `trio.lowlevel.reattach_detached_coroutine_object` is not immediately awaited
|
47 | trio.lowlevel.open_process()
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 | trio.lowlevel.wait_readable(foo)
|
= help: Add `await`
Suggested fix
46 46 | trio.lowlevel.checkpoint_if_cancelled()
47 47 | trio.lowlevel.open_process()
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 |- trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
49 |+ await trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 51 | trio.lowlevel.wait_readable(foo)
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
TRIO105.py:50:5: TRIO105 [*] Call to `trio.lowlevel.temporarily_detach_coroutine_object` is not immediately awaited
|
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
51 | trio.lowlevel.wait_readable(foo)
52 | trio.lowlevel.wait_task_rescheduled(foo)
|
= help: Add `await`
Suggested fix
47 47 | trio.lowlevel.open_process()
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 |- trio.lowlevel.temporarily_detach_coroutine_object(foo)
50 |+ await trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 51 | trio.lowlevel.wait_readable(foo)
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
53 53 | trio.lowlevel.wait_writable(foo)
TRIO105.py:51:5: TRIO105 [*] Call to `trio.lowlevel.wait_readable` is not immediately awaited
|
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 | trio.lowlevel.wait_readable(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
52 | trio.lowlevel.wait_task_rescheduled(foo)
53 | trio.lowlevel.wait_writable(foo)
|
= help: Add `await`
Suggested fix
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 |- trio.lowlevel.wait_readable(foo)
51 |+ await trio.lowlevel.wait_readable(foo)
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
53 53 | trio.lowlevel.wait_writable(foo)
54 54 |
TRIO105.py:52:5: TRIO105 [*] Call to `trio.lowlevel.wait_task_rescheduled` is not immediately awaited
|
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 | trio.lowlevel.wait_readable(foo)
52 | trio.lowlevel.wait_task_rescheduled(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
53 | trio.lowlevel.wait_writable(foo)
|
= help: Add `await`
Suggested fix
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 51 | trio.lowlevel.wait_readable(foo)
52 |- trio.lowlevel.wait_task_rescheduled(foo)
52 |+ await trio.lowlevel.wait_task_rescheduled(foo)
53 53 | trio.lowlevel.wait_writable(foo)
54 54 |
55 55 | async with await trio.open_file(foo): # Ok
TRIO105.py:53:5: TRIO105 [*] Call to `trio.lowlevel.wait_writable` is not immediately awaited
|
51 | trio.lowlevel.wait_readable(foo)
52 | trio.lowlevel.wait_task_rescheduled(foo)
53 | trio.lowlevel.wait_writable(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
54 |
55 | async with await trio.open_file(foo): # Ok
|
= help: Add `await`
Suggested fix
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
51 51 | trio.lowlevel.wait_readable(foo)
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
53 |- trio.lowlevel.wait_writable(foo)
53 |+ await trio.lowlevel.wait_writable(foo)
54 54 |
55 55 | async with await trio.open_file(foo): # Ok
56 56 | pass
TRIO105.py:58:16: TRIO105 [*] Call to `trio.open_file` is not immediately awaited
|
56 | pass
57 |
58 | async with trio.open_file(foo): # TRIO105
| ^^^^^^^^^^^^^^^^^^^ TRIO105
59 | pass
|
= help: Add `await`
Suggested fix
55 55 | async with await trio.open_file(foo): # Ok
56 56 | pass
57 57 |
58 |- async with trio.open_file(foo): # TRIO105
58 |+ async with await trio.open_file(foo): # TRIO105
59 59 | pass
60 60 |
61 61 |
TRIO105.py:64:5: TRIO105 Call to `trio.open_file` is not immediately awaited
|
62 | def func() -> None:
63 | # TRIO105 (without fix)
64 | trio.open_file(foo)
| ^^^^^^^^^^^^^^^^^^^ TRIO105
|
= help: Add `await`

View File

@@ -0,0 +1,119 @@
---
source: crates/ruff_linter/src/rules/flake8_trio/mod.rs
---
TRIO115.py:6:11: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
5 | async def func():
6 | await trio.sleep(0) # TRIO115
| ^^^^^^^^^^^^^ TRIO115
7 | await trio.sleep(1) # OK
8 | await trio.sleep(0, 1) # OK
|
= help: Replace with `trio.lowlevel.checkpoint()`
Fix
3 3 |
4 4 |
5 5 | async def func():
6 |- await trio.sleep(0) # TRIO115
6 |+ await trio.lowlevel.checkpoint # TRIO115
7 7 | await trio.sleep(1) # OK
8 8 | await trio.sleep(0, 1) # OK
9 9 | await trio.sleep(...) # OK
TRIO115.py:12:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
10 | await trio.sleep() # OK
11 |
12 | trio.sleep(0) # TRIO115
| ^^^^^^^^^^^^^ TRIO115
13 | foo = 0
14 | trio.sleep(foo) # TRIO115
|
= help: Replace with `trio.lowlevel.checkpoint()`
Fix
9 9 | await trio.sleep(...) # OK
10 10 | await trio.sleep() # OK
11 11 |
12 |- trio.sleep(0) # TRIO115
12 |+ trio.lowlevel.checkpoint # TRIO115
13 13 | foo = 0
14 14 | trio.sleep(foo) # TRIO115
15 15 | trio.sleep(1) # OK
TRIO115.py:14:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
12 | trio.sleep(0) # TRIO115
13 | foo = 0
14 | trio.sleep(foo) # TRIO115
| ^^^^^^^^^^^^^^^ TRIO115
15 | trio.sleep(1) # OK
16 | time.sleep(0) # OK
|
= help: Replace with `trio.lowlevel.checkpoint()`
Fix
11 11 |
12 12 | trio.sleep(0) # TRIO115
13 13 | foo = 0
14 |- trio.sleep(foo) # TRIO115
14 |+ trio.lowlevel.checkpoint # TRIO115
15 15 | trio.sleep(1) # OK
16 16 | time.sleep(0) # OK
17 17 |
TRIO115.py:18:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
16 | time.sleep(0) # OK
17 |
18 | sleep(0) # TRIO115
| ^^^^^^^^ TRIO115
19 |
20 | bar = "bar"
|
= help: Replace with `trio.lowlevel.checkpoint()`
Fix
15 15 | trio.sleep(1) # OK
16 16 | time.sleep(0) # OK
17 17 |
18 |- sleep(0) # TRIO115
18 |+ trio.lowlevel.checkpoint # TRIO115
19 19 |
20 20 | bar = "bar"
21 21 | trio.sleep(bar)
TRIO115.py:24:1: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
24 | trio.sleep(0) # TRIO115
| ^^^^^^^^^^^^^ TRIO115
|
= help: Replace with `trio.lowlevel.checkpoint()`
Fix
21 21 | trio.sleep(bar)
22 22 |
23 23 |
24 |-trio.sleep(0) # TRIO115
24 |+trio.lowlevel.checkpoint # TRIO115
25 25 |
26 26 |
27 27 | def func():
TRIO115.py:28:14: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
27 | def func():
28 | trio.run(trio.sleep(0)) # TRIO115
| ^^^^^^^^^^^^^ TRIO115
|
= help: Replace with `trio.lowlevel.checkpoint()`
Fix
25 25 |
26 26 |
27 27 | def func():
28 |- trio.run(trio.sleep(0)) # TRIO115
28 |+ trio.run(trio.lowlevel.checkpoint) # TRIO115

View File

@@ -19,10 +19,13 @@ use crate::importer::ImportRequest;
/// constants were removed from the main namespace.
///
/// The majority of these functions and constants can be automatically replaced
/// by other members of the NumPy API, even prior to NumPy 2.0, or by
/// equivalents from the Python standard library. This rule flags all uses of
/// removed members, along with automatic fixes for any backwards-compatible
/// replacements.
/// by other members of the NumPy API or by equivalents from the Python
/// standard library. With the exception of renaming `numpy.byte_bounds` to
/// `numpy.lib.array_utils.byte_bounds`, all such replacements are backwards
/// compatible with earlier versions of NumPy.
///
/// This rule flags all uses of removed members, along with automatic fixes for
/// any backwards-compatible replacements.
///
/// ## Examples
/// ```python
@@ -45,6 +48,7 @@ use crate::importer::ImportRequest;
pub struct Numpy2Deprecation {
existing: String,
migration_guide: Option<String>,
code_action: Option<String>,
}
impl Violation for Numpy2Deprecation {
@@ -55,21 +59,23 @@ impl Violation for Numpy2Deprecation {
let Numpy2Deprecation {
existing,
migration_guide,
code_action: _,
} = self;
match migration_guide {
Some(migration_guide) => {
format!("`np.{existing}` will be removed in NumPy 2.0. {migration_guide}",)
}
None => format!("`np.{existing}` will be removed without replacement in NumPy 2.0."),
None => format!("`np.{existing}` will be removed without replacement in NumPy 2.0"),
}
}
fn fix_title(&self) -> Option<String> {
let Numpy2Deprecation {
existing: _,
migration_guide,
migration_guide: _,
code_action,
} = self;
migration_guide.clone()
code_action.clone()
}
}
@@ -82,7 +88,11 @@ struct Replacement<'a> {
#[derive(Debug)]
enum Details<'a> {
/// The deprecated member can be replaced by another member in the NumPy API.
AutoImport { path: &'a str, name: &'a str },
AutoImport {
path: &'a str,
name: &'a str,
compatibility: Compatibility,
},
/// The deprecated member can be replaced by a member of the Python standard library.
AutoPurePython { python_expr: &'a str },
/// The deprecated member can be replaced by a manual migration.
@@ -92,15 +102,54 @@ enum Details<'a> {
impl Details<'_> {
fn guideline(&self) -> Option<String> {
match self {
Details::AutoImport { path, name } => Some(format!("Use `{path}.{name}` instead.")),
Details::AutoImport {
path,
name,
compatibility: Compatibility::BackwardsCompatible,
} => Some(format!("Use `{path}.{name}` instead.")),
Details::AutoImport {
path,
name,
compatibility: Compatibility::Breaking,
} => Some(format!(
"Use `{path}.{name}` on NumPy 2.0, or ignore this warning on earlier versions."
)),
Details::AutoPurePython { python_expr } => {
Some(format!("Use `{python_expr}` instead."))
}
Details::Manual { guideline } => guideline.map(ToString::to_string),
}
}
fn code_action(&self) -> Option<String> {
match self {
Details::AutoImport {
path,
name,
compatibility: Compatibility::BackwardsCompatible,
} => Some(format!("Replace with `{path}.{name}`")),
Details::AutoImport {
path,
name,
compatibility: Compatibility::Breaking,
} => Some(format!(
"Replace with `{path}.{name}` (requires NumPy 2.0 or greater)"
)),
Details::AutoPurePython { python_expr } => {
Some(format!("Replace with `{python_expr}`"))
}
Details::Manual { guideline: _ } => None,
}
}
}
#[derive(Debug)]
enum Compatibility {
/// The changes is backwards compatible with earlier versions of NumPy.
BackwardsCompatible,
/// The change is breaking in NumPy 2.0.
Breaking,
}
/// NPY201
pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
let maybe_replacement = checker
@@ -113,6 +162,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy.lib",
name: "add_docstring",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "add_newdoc"] => Some(Replacement {
@@ -120,6 +170,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy.lib",
name: "add_newdoc",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "add_newdoc_ufunc"] => Some(Replacement {
@@ -139,6 +190,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy.lib.array_utils",
name: "byte_bounds",
compatibility: Compatibility::Breaking,
},
}),
["numpy", "cast"] => Some(Replacement {
@@ -152,6 +204,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "complex128",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "clongfloat"] => Some(Replacement {
@@ -159,6 +212,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "clongdouble",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "compat"] => Some(Replacement {
@@ -172,6 +226,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "complex128",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "DataSource"] => Some(Replacement {
@@ -179,6 +234,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy.lib.npyio",
name: "DataSource",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "deprecate"] => Some(Replacement {
@@ -222,6 +278,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "float64",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "geterrobj"] => Some(Replacement {
@@ -235,6 +292,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "inf",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "Inf"] => Some(Replacement {
@@ -242,6 +300,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "inf",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "Infinity"] => Some(Replacement {
@@ -249,6 +308,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "inf",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "infty"] => Some(Replacement {
@@ -256,6 +316,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "inf",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "issctype"] => Some(Replacement {
@@ -275,6 +336,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "issubdtype",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "mat"] => Some(Replacement {
@@ -282,6 +344,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "asmatrix",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "maximum_sctype"] => Some(Replacement {
@@ -295,6 +358,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "nan",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "nbytes"] => Some(Replacement {
@@ -320,6 +384,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "clongdouble",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "longfloat"] => Some(Replacement {
@@ -327,6 +392,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "longdouble",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "lookfor"] => Some(Replacement {
@@ -346,6 +412,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "inf",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "PZERO"] => Some(Replacement {
@@ -369,6 +436,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "round",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "safe_eval"] => Some(Replacement {
@@ -376,6 +444,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "ast",
name: "literal_eval",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "sctype2char"] => Some(Replacement {
@@ -407,6 +476,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "complex64",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "string_"] => Some(Replacement {
@@ -414,6 +484,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "bytes_",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "source"] => Some(Replacement {
@@ -421,6 +492,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "inspect",
name: "getsource",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "tracemalloc_domain"] => Some(Replacement {
@@ -428,6 +500,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy.lib",
name: "tracemalloc_domain",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "unicode_"] => Some(Replacement {
@@ -435,6 +508,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
details: Details::AutoImport {
path: "numpy",
name: "str_",
compatibility: Compatibility::BackwardsCompatible,
},
}),
["numpy", "who"] => Some(Replacement {
@@ -451,11 +525,16 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
Numpy2Deprecation {
existing: replacement.existing.to_string(),
migration_guide: replacement.details.guideline(),
code_action: replacement.details.code_action(),
},
expr.range(),
);
match replacement.details {
Details::AutoImport { path, name } => {
Details::AutoImport {
path,
name,
compatibility,
} => {
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import_from(path, name),
@@ -463,7 +542,14 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) {
checker.semantic(),
)?;
let replacement_edit = Edit::range_replacement(binding, expr.range());
Ok(Fix::safe_edits(import_edit, [replacement_edit]))
Ok(match compatibility {
Compatibility::BackwardsCompatible => {
Fix::safe_edits(import_edit, [replacement_edit])
}
Compatibility::Breaking => {
Fix::unsafe_edits(import_edit, [replacement_edit])
}
})
});
}
Details::AutoPurePython { python_expr } => diagnostic.set_fix(Fix::safe_edit(

View File

@@ -10,7 +10,7 @@ NPY201.py:4:5: NPY201 [*] `np.add_docstring` will be removed in NumPy 2.0. Use `
5 |
6 | np.add_newdoc
|
= help: Use `numpy.lib.add_docstring` instead.
= help: Replace with `numpy.lib.add_docstring`
Fix
1 |+from numpy.lib import add_docstring
@@ -32,7 +32,7 @@ NPY201.py:6:5: NPY201 [*] `np.add_newdoc` will be removed in NumPy 2.0. Use `num
7 |
8 | np.add_newdoc_ufunc
|
= help: Use `numpy.lib.add_newdoc` instead.
= help: Replace with `numpy.lib.add_newdoc`
Fix
1 |+from numpy.lib import add_newdoc
@@ -56,7 +56,6 @@ NPY201.py:8:5: NPY201 `np.add_newdoc_ufunc` will be removed in NumPy 2.0. `add_n
9 |
10 | np.asfarray([1,2,3])
|
= help: `add_newdoc_ufunc` is an internal function.
NPY201.py:10:5: NPY201 `np.asfarray` will be removed in NumPy 2.0. Use `np.asarray` with a `float` dtype instead.
|
@@ -67,9 +66,8 @@ NPY201.py:10:5: NPY201 `np.asfarray` will be removed in NumPy 2.0. Use `np.asarr
11 |
12 | np.byte_bounds(np.array([1,2,3]))
|
= help: Use `np.asarray` with a `float` dtype instead.
NPY201.py:12:5: NPY201 [*] `np.byte_bounds` will be removed in NumPy 2.0. Use `numpy.lib.array_utils.byte_bounds` instead.
NPY201.py:12:5: NPY201 [*] `np.byte_bounds` will be removed in NumPy 2.0. Use `numpy.lib.array_utils.byte_bounds` on NumPy 2.0, or ignore this warning on earlier versions.
|
10 | np.asfarray([1,2,3])
11 |
@@ -78,9 +76,9 @@ NPY201.py:12:5: NPY201 [*] `np.byte_bounds` will be removed in NumPy 2.0. Use `n
13 |
14 | np.cast
|
= help: Use `numpy.lib.array_utils.byte_bounds` instead.
= help: Replace with `numpy.lib.array_utils.byte_bounds` (requires NumPy 2.0 or greater)
Fix
Suggested fix
1 |+from numpy.lib.array_utils import byte_bounds
1 2 | def func():
2 3 | import numpy as np
@@ -104,7 +102,6 @@ NPY201.py:14:5: NPY201 `np.cast` will be removed in NumPy 2.0. Use `np.asarray(a
15 |
16 | np.cfloat(12+34j)
|
= help: Use `np.asarray(arr, dtype=dtype)` instead.
NPY201.py:16:5: NPY201 [*] `np.cfloat` will be removed in NumPy 2.0. Use `numpy.complex128` instead.
|
@@ -115,7 +112,7 @@ NPY201.py:16:5: NPY201 [*] `np.cfloat` will be removed in NumPy 2.0. Use `numpy.
17 |
18 | np.clongfloat(12+34j)
|
= help: Use `numpy.complex128` instead.
= help: Replace with `numpy.complex128`
Fix
13 13 |
@@ -136,7 +133,7 @@ NPY201.py:18:5: NPY201 [*] `np.clongfloat` will be removed in NumPy 2.0. Use `nu
19 |
20 | np.compat
|
= help: Use `numpy.clongdouble` instead.
= help: Replace with `numpy.clongdouble`
Fix
15 15 |
@@ -157,7 +154,6 @@ NPY201.py:20:5: NPY201 `np.compat` will be removed in NumPy 2.0. Python 2 is no
21 |
22 | np.complex_(12+34j)
|
= help: Python 2 is no longer supported.
NPY201.py:22:5: NPY201 [*] `np.complex_` will be removed in NumPy 2.0. Use `numpy.complex128` instead.
|
@@ -168,7 +164,7 @@ NPY201.py:22:5: NPY201 [*] `np.complex_` will be removed in NumPy 2.0. Use `nump
23 |
24 | np.DataSource
|
= help: Use `numpy.complex128` instead.
= help: Replace with `numpy.complex128`
Fix
19 19 |
@@ -189,7 +185,7 @@ NPY201.py:24:5: NPY201 [*] `np.DataSource` will be removed in NumPy 2.0. Use `nu
25 |
26 | np.deprecate
|
= help: Use `numpy.lib.npyio.DataSource` instead.
= help: Replace with `numpy.lib.npyio.DataSource`
Fix
1 |+from numpy.lib.npyio import DataSource
@@ -215,7 +211,6 @@ NPY201.py:26:5: NPY201 `np.deprecate` will be removed in NumPy 2.0. Emit `Deprec
27 |
28 | np.deprecate_with_doc
|
= help: Emit `DeprecationWarning` with `warnings.warn` directly, or use `typing.deprecated`.
NPY201.py:28:5: NPY201 `np.deprecate_with_doc` will be removed in NumPy 2.0. Emit `DeprecationWarning` with `warnings.warn` directly, or use `typing.deprecated`.
|
@@ -226,7 +221,6 @@ NPY201.py:28:5: NPY201 `np.deprecate_with_doc` will be removed in NumPy 2.0. Emi
29 |
30 | np.disp(10)
|
= help: Emit `DeprecationWarning` with `warnings.warn` directly, or use `typing.deprecated`.
NPY201.py:30:5: NPY201 `np.disp` will be removed in NumPy 2.0. Use a dedicated print function instead.
|
@@ -237,7 +231,6 @@ NPY201.py:30:5: NPY201 `np.disp` will be removed in NumPy 2.0. Use a dedicated p
31 |
32 | np.fastCopyAndTranspose
|
= help: Use a dedicated print function instead.
NPY201.py:32:5: NPY201 `np.fastCopyAndTranspose` will be removed in NumPy 2.0. Use `arr.T.copy()` instead.
|
@@ -248,7 +241,6 @@ NPY201.py:32:5: NPY201 `np.fastCopyAndTranspose` will be removed in NumPy 2.0. U
33 |
34 | np.find_common_type
|
= help: Use `arr.T.copy()` instead.
NPY201.py:34:5: NPY201 `np.find_common_type` will be removed in NumPy 2.0. Use `numpy.promote_types` or `numpy.result_type` instead. To achieve semantics for the `scalar_types` argument, use `numpy.result_type` and pass the Python values `0`, `0.0`, or `0j`.
|
@@ -259,9 +251,8 @@ NPY201.py:34:5: NPY201 `np.find_common_type` will be removed in NumPy 2.0. Use `
35 |
36 | np.get_array_wrap
|
= help: Use `numpy.promote_types` or `numpy.result_type` instead. To achieve semantics for the `scalar_types` argument, use `numpy.result_type` and pass the Python values `0`, `0.0`, or `0j`.
NPY201.py:36:5: NPY201 `np.get_array_wrap` will be removed without replacement in NumPy 2.0.
NPY201.py:36:5: NPY201 `np.get_array_wrap` will be removed without replacement in NumPy 2.0
|
34 | np.find_common_type
35 |
@@ -280,7 +271,7 @@ NPY201.py:38:5: NPY201 [*] `np.float_` will be removed in NumPy 2.0. Use `numpy.
39 |
40 | np.geterrobj
|
= help: Use `numpy.float64` instead.
= help: Replace with `numpy.float64`
Fix
35 35 |
@@ -301,7 +292,6 @@ NPY201.py:40:5: NPY201 `np.geterrobj` will be removed in NumPy 2.0. Use the `np.
41 |
42 | np.Inf
|
= help: Use the `np.errstate` context manager instead.
NPY201.py:42:5: NPY201 [*] `np.Inf` will be removed in NumPy 2.0. Use `numpy.inf` instead.
|
@@ -312,7 +302,7 @@ NPY201.py:42:5: NPY201 [*] `np.Inf` will be removed in NumPy 2.0. Use `numpy.inf
43 |
44 | np.Infinity
|
= help: Use `numpy.inf` instead.
= help: Replace with `numpy.inf`
Fix
39 39 |
@@ -333,7 +323,7 @@ NPY201.py:44:5: NPY201 [*] `np.Infinity` will be removed in NumPy 2.0. Use `nump
45 |
46 | np.infty
|
= help: Use `numpy.inf` instead.
= help: Replace with `numpy.inf`
Fix
41 41 |
@@ -354,7 +344,7 @@ NPY201.py:46:5: NPY201 [*] `np.infty` will be removed in NumPy 2.0. Use `numpy.i
47 |
48 | np.issctype
|
= help: Use `numpy.inf` instead.
= help: Replace with `numpy.inf`
Fix
43 43 |
@@ -366,7 +356,7 @@ NPY201.py:46:5: NPY201 [*] `np.infty` will be removed in NumPy 2.0. Use `numpy.i
48 48 | np.issctype
49 49 |
NPY201.py:48:5: NPY201 `np.issctype` will be removed without replacement in NumPy 2.0.
NPY201.py:48:5: NPY201 `np.issctype` will be removed without replacement in NumPy 2.0
|
46 | np.infty
47 |
@@ -385,7 +375,7 @@ NPY201.py:50:5: NPY201 [*] `np.issubclass_` will be removed in NumPy 2.0. Use `i
51 |
52 | np.issubsctype
|
= help: Use `issubclass` instead.
= help: Replace with `issubclass`
Fix
47 47 |
@@ -406,7 +396,7 @@ NPY201.py:52:5: NPY201 [*] `np.issubsctype` will be removed in NumPy 2.0. Use `n
53 |
54 | np.mat
|
= help: Use `numpy.issubdtype` instead.
= help: Replace with `numpy.issubdtype`
Fix
49 49 |
@@ -427,7 +417,7 @@ NPY201.py:54:5: NPY201 [*] `np.mat` will be removed in NumPy 2.0. Use `numpy.asm
55 |
56 | np.maximum_sctype
|
= help: Use `numpy.asmatrix` instead.
= help: Replace with `numpy.asmatrix`
Fix
51 51 |
@@ -439,7 +429,7 @@ NPY201.py:54:5: NPY201 [*] `np.mat` will be removed in NumPy 2.0. Use `numpy.asm
56 56 | np.maximum_sctype
57 57 |
NPY201.py:56:5: NPY201 `np.maximum_sctype` will be removed without replacement in NumPy 2.0.
NPY201.py:56:5: NPY201 `np.maximum_sctype` will be removed without replacement in NumPy 2.0
|
54 | np.mat
55 |
@@ -458,7 +448,7 @@ NPY201.py:58:5: NPY201 [*] `np.NaN` will be removed in NumPy 2.0. Use `numpy.nan
59 |
60 | np.nbytes[np.int64]
|
= help: Use `numpy.nan` instead.
= help: Replace with `numpy.nan`
Fix
55 55 |
@@ -479,7 +469,6 @@ NPY201.py:60:5: NPY201 `np.nbytes` will be removed in NumPy 2.0. Use `np.dtype(<
61 |
62 | np.NINF
|
= help: Use `np.dtype(<dtype>).itemsize` instead.
NPY201.py:62:5: NPY201 [*] `np.NINF` will be removed in NumPy 2.0. Use `-np.inf` instead.
|
@@ -490,7 +479,7 @@ NPY201.py:62:5: NPY201 [*] `np.NINF` will be removed in NumPy 2.0. Use `-np.inf`
63 |
64 | np.NZERO
|
= help: Use `-np.inf` instead.
= help: Replace with `-np.inf`
Fix
59 59 |
@@ -511,7 +500,7 @@ NPY201.py:64:5: NPY201 [*] `np.NZERO` will be removed in NumPy 2.0. Use `-0.0` i
65 |
66 | np.longcomplex(12+34j)
|
= help: Use `-0.0` instead.
= help: Replace with `-0.0`
Fix
61 61 |
@@ -532,7 +521,7 @@ NPY201.py:66:5: NPY201 [*] `np.longcomplex` will be removed in NumPy 2.0. Use `n
67 |
68 | np.longfloat(12+34j)
|
= help: Use `numpy.clongdouble` instead.
= help: Replace with `numpy.clongdouble`
Fix
63 63 |
@@ -553,7 +542,7 @@ NPY201.py:68:5: NPY201 [*] `np.longfloat` will be removed in NumPy 2.0. Use `num
69 |
70 | np.lookfor
|
= help: Use `numpy.longdouble` instead.
= help: Replace with `numpy.longdouble`
Fix
65 65 |
@@ -574,9 +563,8 @@ NPY201.py:70:5: NPY201 `np.lookfor` will be removed in NumPy 2.0. Search NumPy
71 |
72 | np.obj2sctype(int)
|
= help: Search NumPys documentation directly.
NPY201.py:72:5: NPY201 `np.obj2sctype` will be removed without replacement in NumPy 2.0.
NPY201.py:72:5: NPY201 `np.obj2sctype` will be removed without replacement in NumPy 2.0
|
70 | np.lookfor
71 |
@@ -595,7 +583,7 @@ NPY201.py:74:5: NPY201 [*] `np.PINF` will be removed in NumPy 2.0. Use `numpy.in
75 |
76 | np.PZERO
|
= help: Use `numpy.inf` instead.
= help: Replace with `numpy.inf`
Fix
71 71 |
@@ -616,7 +604,7 @@ NPY201.py:76:5: NPY201 [*] `np.PZERO` will be removed in NumPy 2.0. Use `0.0` in
77 |
78 | np.recfromcsv
|
= help: Use `0.0` instead.
= help: Replace with `0.0`
Fix
73 73 |
@@ -637,7 +625,6 @@ NPY201.py:78:5: NPY201 `np.recfromcsv` will be removed in NumPy 2.0. Use `np.gen
79 |
80 | np.recfromtxt
|
= help: Use `np.genfromtxt` with comma delimiter instead.
NPY201.py:80:5: NPY201 `np.recfromtxt` will be removed in NumPy 2.0. Use `np.genfromtxt` instead.
|
@@ -648,7 +635,6 @@ NPY201.py:80:5: NPY201 `np.recfromtxt` will be removed in NumPy 2.0. Use `np.gen
81 |
82 | np.round_(12.34)
|
= help: Use `np.genfromtxt` instead.
NPY201.py:82:5: NPY201 [*] `np.round_` will be removed in NumPy 2.0. Use `numpy.round` instead.
|
@@ -659,7 +645,7 @@ NPY201.py:82:5: NPY201 [*] `np.round_` will be removed in NumPy 2.0. Use `numpy.
83 |
84 | np.safe_eval
|
= help: Use `numpy.round` instead.
= help: Replace with `numpy.round`
Fix
79 79 |
@@ -680,7 +666,7 @@ NPY201.py:84:5: NPY201 [*] `np.safe_eval` will be removed in NumPy 2.0. Use `ast
85 |
86 | np.sctype2char
|
= help: Use `ast.literal_eval` instead.
= help: Replace with `ast.literal_eval`
Fix
1 |+from ast import literal_eval
@@ -697,7 +683,7 @@ NPY201.py:84:5: NPY201 [*] `np.safe_eval` will be removed in NumPy 2.0. Use `ast
86 87 | np.sctype2char
87 88 |
NPY201.py:86:5: NPY201 `np.sctype2char` will be removed without replacement in NumPy 2.0.
NPY201.py:86:5: NPY201 `np.sctype2char` will be removed without replacement in NumPy 2.0
|
84 | np.safe_eval
85 |
@@ -707,7 +693,7 @@ NPY201.py:86:5: NPY201 `np.sctype2char` will be removed without replacement in N
88 | np.sctypes
|
NPY201.py:88:5: NPY201 `np.sctypes` will be removed without replacement in NumPy 2.0.
NPY201.py:88:5: NPY201 `np.sctypes` will be removed without replacement in NumPy 2.0
|
86 | np.sctype2char
87 |
@@ -726,7 +712,6 @@ NPY201.py:90:5: NPY201 `np.seterrobj` will be removed in NumPy 2.0. Use the `np.
91 |
92 | np.set_numeric_ops
|
= help: Use the `np.errstate` context manager instead.
NPY201.py:94:5: NPY201 `np.set_string_function` will be removed in NumPy 2.0. Use `np.set_printoptions` for custom printing of NumPy objects.
|
@@ -737,7 +722,6 @@ NPY201.py:94:5: NPY201 `np.set_string_function` will be removed in NumPy 2.0. Us
95 |
96 | np.singlecomplex(12+1j)
|
= help: Use `np.set_printoptions` for custom printing of NumPy objects.
NPY201.py:96:5: NPY201 [*] `np.singlecomplex` will be removed in NumPy 2.0. Use `numpy.complex64` instead.
|
@@ -748,7 +732,7 @@ NPY201.py:96:5: NPY201 [*] `np.singlecomplex` will be removed in NumPy 2.0. Use
97 |
98 | np.string_("asdf")
|
= help: Use `numpy.complex64` instead.
= help: Replace with `numpy.complex64`
Fix
93 93 |
@@ -769,7 +753,7 @@ NPY201.py:98:5: NPY201 [*] `np.string_` will be removed in NumPy 2.0. Use `numpy
99 |
100 | np.source
|
= help: Use `numpy.bytes_` instead.
= help: Replace with `numpy.bytes_`
Fix
95 95 |
@@ -790,7 +774,7 @@ NPY201.py:100:5: NPY201 [*] `np.source` will be removed in NumPy 2.0. Use `inspe
101 |
102 | np.tracemalloc_domain
|
= help: Use `inspect.getsource` instead.
= help: Replace with `inspect.getsource`
Fix
1 |+from inspect import getsource
@@ -816,7 +800,7 @@ NPY201.py:102:5: NPY201 [*] `np.tracemalloc_domain` will be removed in NumPy 2.0
103 |
104 | np.unicode_("asf")
|
= help: Use `numpy.lib.tracemalloc_domain` instead.
= help: Replace with `numpy.lib.tracemalloc_domain`
Fix
1 |+from numpy.lib import tracemalloc_domain
@@ -842,7 +826,7 @@ NPY201.py:104:5: NPY201 [*] `np.unicode_` will be removed in NumPy 2.0. Use `num
105 |
106 | np.who()
|
= help: Use `numpy.str_` instead.
= help: Replace with `numpy.str_`
Fix
101 101 |
@@ -860,6 +844,5 @@ NPY201.py:106:5: NPY201 `np.who` will be removed in NumPy 2.0. Use an IDE variab
106 | np.who()
| ^^^^^^ NPY201
|
= help: Use an IDE variable explorer or `locals()` instead.

View File

@@ -4,7 +4,6 @@ use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::helpers::any_over_expr;
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_semantic::analyze::typing::is_dict;
use ruff_python_semantic::Binding;
use crate::checkers::ast::Checker;
@@ -129,22 +128,16 @@ pub(crate) fn manual_dict_comprehension(checker: &mut Checker, target: &Expr, bo
}
// Exclude non-dictionary value.
let Expr::Name(ast::ExprName {
id: subscript_name, ..
}) = subscript_value.as_ref()
let Some(name) = subscript_value.as_name_expr() else {
return;
};
let Some(binding) = checker
.semantic()
.only_binding(name)
.map(|id| checker.semantic().binding(id))
else {
return;
};
let scope = checker.semantic().current_scope();
let bindings: Vec<&Binding> = scope
.get_all(subscript_name)
.map(|binding_id| checker.semantic().binding(binding_id))
.collect();
let [binding] = bindings.as_slice() else {
return;
};
if !is_dict(binding, checker.semantic()) {
return;
}
@@ -165,8 +158,7 @@ pub(crate) fn manual_dict_comprehension(checker: &mut Checker, target: &Expr, bo
// ```
if if_test.is_some_and(|test| {
any_over_expr(test, &|expr| {
expr.as_name_expr()
.is_some_and(|expr| expr.id == *subscript_name)
ComparableExpr::from(expr) == ComparableExpr::from(name)
})
}) {
return;

View File

@@ -5,7 +5,6 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::helpers::any_over_expr;
use ruff_python_semantic::analyze::typing::is_list;
use ruff_python_semantic::Binding;
use crate::checkers::ast::Checker;
@@ -144,20 +143,16 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, bo
}
// Avoid non-list values.
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
let Some(name) = value.as_name_expr() else {
return;
};
let bindings: Vec<&Binding> = checker
let Some(binding) = checker
.semantic()
.current_scope()
.get_all(id)
.map(|binding_id| checker.semantic().binding(binding_id))
.collect();
let [binding] = bindings.as_slice() else {
.only_binding(name)
.map(|id| checker.semantic().binding(id))
else {
return;
};
if !is_list(binding, checker.semantic()) {
return;
}
@@ -176,15 +171,12 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, bo
// ```python
// filtered = [x for x in y if x in filtered]
// ```
if let Some(value_name) = value.as_name_expr() {
if if_test.is_some_and(|test| {
any_over_expr(test, &|expr| {
expr.as_name_expr()
.is_some_and(|expr| expr.id == value_name.id)
})
}) {
return;
}
if if_test.is_some_and(|test| {
any_over_expr(test, &|expr| {
expr.as_name_expr().is_some_and(|expr| expr.id == name.id)
})
}) {
return;
}
checker

View File

@@ -3,7 +3,6 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::any_over_expr;
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
use ruff_python_semantic::analyze::typing::is_list;
use ruff_python_semantic::Binding;
use crate::checkers::ast::Checker;
@@ -102,20 +101,16 @@ pub(crate) fn manual_list_copy(checker: &mut Checker, target: &Expr, body: &[Stm
}
// Avoid non-list values.
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
let Some(name) = value.as_name_expr() else {
return;
};
let bindings: Vec<&Binding> = checker
let Some(binding) = checker
.semantic()
.current_scope()
.get_all(id)
.map(|binding_id| checker.semantic().binding(binding_id))
.collect();
let [binding] = bindings.as_slice() else {
.only_binding(name)
.map(|id| checker.semantic().binding(id))
else {
return;
};
if !is_list(binding, checker.semantic()) {
return;
}

View File

@@ -24,7 +24,7 @@ use ruff_source_file::Locator;
/// if foo == "blah":
/// do_blah_thing()
/// ```
///
/// [PEP 8]: https://peps.python.org/pep-0008/#other-recommendations
#[violation]
pub struct MultipleStatementsOnOneLineColon;
@@ -206,12 +206,13 @@ pub(crate) fn compound_statements(
{
colon = Some((range.start(), range.end()));
// Allow `class C: ...`-style definitions in stubs.
allow_ellipsis = class.is_some();
// Allow `class C: ...`-style definitions.
allow_ellipsis = true;
}
}
Tok::Semi => {
semi = Some((range.start(), range.end()));
allow_ellipsis = false;
}
Tok::Comment(..) | Tok::Indent | Tok::Dedent | Tok::NonLogicalNewline => {}
_ => {
@@ -223,6 +224,7 @@ pub(crate) fn compound_statements(
// Reset.
semi = None;
allow_ellipsis = false;
}
if let Some((start, end)) = colon {
@@ -245,6 +247,7 @@ pub(crate) fn compound_statements(
try_ = None;
while_ = None;
with = None;
allow_ellipsis = false;
}
}
}

View File

@@ -138,6 +138,7 @@ fn deprecated_type_comparison(checker: &mut Checker, compare: &ast::ExprCompare)
| "list"
| "dict"
| "set"
| "memoryview"
) && checker.semantic().is_builtin(id)
{
checker.diagnostics.push(Diagnostic::new(
@@ -197,7 +198,98 @@ fn is_type(expr: &Expr, semantic: &SemanticModel) -> bool {
// Ex) `type(obj) == int`
matches!(
id.as_str(),
"int" | "str" | "float" | "bool" | "complex" | "bytes" | "list" | "dict" | "set"
"bool"
| "bytearray"
| "bytes"
| "classmethod"
| "complex"
| "dict"
| "enumerate"
| "filter"
| "float"
| "frozenset"
| "int"
| "list"
| "map"
| "memoryview"
| "object"
| "property"
| "range"
| "reversed"
| "set"
| "slice"
| "staticmethod"
| "str"
| "super"
| "tuple"
| "type"
| "zip"
| "ArithmeticError"
| "AssertionError"
| "AttributeError"
| "BaseException"
| "BlockingIOError"
| "BrokenPipeError"
| "BufferError"
| "BytesWarning"
| "ChildProcessError"
| "ConnectionAbortedError"
| "ConnectionError"
| "ConnectionRefusedError"
| "ConnectionResetError"
| "DeprecationWarning"
| "EnvironmentError"
| "EOFError"
| "Exception"
| "FileExistsError"
| "FileNotFoundError"
| "FloatingPointError"
| "FutureWarning"
| "GeneratorExit"
| "ImportError"
| "ImportWarning"
| "IndentationError"
| "IndexError"
| "InterruptedError"
| "IOError"
| "IsADirectoryError"
| "KeyboardInterrupt"
| "KeyError"
| "LookupError"
| "MemoryError"
| "ModuleNotFoundError"
| "NameError"
| "NotADirectoryError"
| "NotImplementedError"
| "OSError"
| "OverflowError"
| "PendingDeprecationWarning"
| "PermissionError"
| "ProcessLookupError"
| "RecursionError"
| "ReferenceError"
| "ResourceWarning"
| "RuntimeError"
| "RuntimeWarning"
| "StopAsyncIteration"
| "StopIteration"
| "SyntaxError"
| "SyntaxWarning"
| "SystemError"
| "SystemExit"
| "TabError"
| "TimeoutError"
| "TypeError"
| "UnboundLocalError"
| "UnicodeDecodeError"
| "UnicodeEncodeError"
| "UnicodeError"
| "UnicodeTranslateError"
| "UnicodeWarning"
| "UserWarning"
| "ValueError"
| "Warning"
| "ZeroDivisionError"
) && semantic.is_builtin(id)
}
_ => false,

View File

@@ -92,6 +92,8 @@ E70.py:71:4: E703 [*] Statement ends with an unnecessary semicolon
70 | a = \
71 | 5;
| ^ E703
72 | #:
73 | with x(y) as z: ...
|
= help: Remove unnecessary semicolon
@@ -101,5 +103,7 @@ E70.py:71:4: E703 [*] Statement ends with an unnecessary semicolon
70 70 | a = \
71 |- 5;
71 |+ 5
72 72 | #:
73 73 | with x(y) as z: ...

View File

@@ -17,144 +17,154 @@ E721.py:5:4: E721 Do not compare types, use `isinstance()`
5 | if type(res) != type(""):
| ^^^^^^^^^^^^^^^^^^^^^ E721
6 | pass
7 | #: Okay
7 | #: E721
|
E721.py:15:4: E721 Do not compare types, use `isinstance()`
E721.py:8:4: E721 Do not compare types, use `isinstance()`
|
13 | import types
14 |
15 | if type(res) is not types.ListType:
6 | pass
7 | #: E721
8 | if type(res) == memoryview:
| ^^^^^^^^^^^^^^^^^^^^^^^ E721
9 | pass
10 | #: Okay
|
E721.py:18:4: E721 Do not compare types, use `isinstance()`
|
16 | import types
17 |
18 | if type(res) is not types.ListType:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E721
16 | pass
17 | #: E721
19 | pass
20 | #: E721
|
E721.py:18:8: E721 Do not compare types, use `isinstance()`
E721.py:21:8: E721 Do not compare types, use `isinstance()`
|
16 | pass
17 | #: E721
18 | assert type(res) == type(False) or type(res) == type(None)
19 | pass
20 | #: E721
21 | assert type(res) == type(False) or type(res) == type(None)
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
19 | #: E721
20 | assert type(res) == type([])
22 | #: E721
23 | assert type(res) == type([])
|
E721.py:20:8: E721 Do not compare types, use `isinstance()`
E721.py:23:8: E721 Do not compare types, use `isinstance()`
|
18 | assert type(res) == type(False) or type(res) == type(None)
19 | #: E721
20 | assert type(res) == type([])
21 | assert type(res) == type(False) or type(res) == type(None)
22 | #: E721
23 | assert type(res) == type([])
| ^^^^^^^^^^^^^^^^^^^^^ E721
21 | #: E721
22 | assert type(res) == type(())
24 | #: E721
25 | assert type(res) == type(())
|
E721.py:22:8: E721 Do not compare types, use `isinstance()`
E721.py:25:8: E721 Do not compare types, use `isinstance()`
|
20 | assert type(res) == type([])
21 | #: E721
22 | assert type(res) == type(())
23 | assert type(res) == type([])
24 | #: E721
25 | assert type(res) == type(())
| ^^^^^^^^^^^^^^^^^^^^^ E721
23 | #: E721
24 | assert type(res) == type((0,))
26 | #: E721
27 | assert type(res) == type((0,))
|
E721.py:24:8: E721 Do not compare types, use `isinstance()`
E721.py:27:8: E721 Do not compare types, use `isinstance()`
|
22 | assert type(res) == type(())
23 | #: E721
24 | assert type(res) == type((0,))
25 | assert type(res) == type(())
26 | #: E721
27 | assert type(res) == type((0,))
| ^^^^^^^^^^^^^^^^^^^^^^^ E721
25 | #: E721
26 | assert type(res) == type((0))
28 | #: E721
29 | assert type(res) == type((0))
|
E721.py:26:8: E721 Do not compare types, use `isinstance()`
E721.py:29:8: E721 Do not compare types, use `isinstance()`
|
24 | assert type(res) == type((0,))
25 | #: E721
26 | assert type(res) == type((0))
27 | assert type(res) == type((0,))
28 | #: E721
29 | assert type(res) == type((0))
| ^^^^^^^^^^^^^^^^^^^^^^ E721
27 | #: E721
28 | assert type(res) != type((1, ))
30 | #: E721
31 | assert type(res) != type((1, ))
|
E721.py:28:8: E721 Do not compare types, use `isinstance()`
E721.py:31:8: E721 Do not compare types, use `isinstance()`
|
26 | assert type(res) == type((0))
27 | #: E721
28 | assert type(res) != type((1, ))
29 | assert type(res) == type((0))
30 | #: E721
31 | assert type(res) != type((1, ))
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
29 | #: Okay
30 | assert type(res) is type((1, ))
32 | #: Okay
33 | assert type(res) is type((1, ))
|
E721.py:30:8: E721 Do not compare types, use `isinstance()`
E721.py:33:8: E721 Do not compare types, use `isinstance()`
|
28 | assert type(res) != type((1, ))
29 | #: Okay
30 | assert type(res) is type((1, ))
31 | assert type(res) != type((1, ))
32 | #: Okay
33 | assert type(res) is type((1, ))
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
31 | #: Okay
32 | assert type(res) is not type((1, ))
34 | #: Okay
35 | assert type(res) is not type((1, ))
|
E721.py:32:8: E721 Do not compare types, use `isinstance()`
E721.py:35:8: E721 Do not compare types, use `isinstance()`
|
30 | assert type(res) is type((1, ))
31 | #: Okay
32 | assert type(res) is not type((1, ))
33 | assert type(res) is type((1, ))
34 | #: Okay
35 | assert type(res) is not type((1, ))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E721
33 | #: E211 E721
34 | assert type(res) == type ([2, ])
36 | #: E211 E721
37 | assert type(res) == type ([2, ])
|
E721.py:34:8: E721 Do not compare types, use `isinstance()`
E721.py:37:8: E721 Do not compare types, use `isinstance()`
|
32 | assert type(res) is not type((1, ))
33 | #: E211 E721
34 | assert type(res) == type ([2, ])
35 | assert type(res) is not type((1, ))
36 | #: E211 E721
37 | assert type(res) == type ([2, ])
| ^^^^^^^^^^^^^^^^^^^^^^^^^ E721
35 | #: E201 E201 E202 E721
36 | assert type(res) == type( ( ) )
38 | #: E201 E201 E202 E721
39 | assert type(res) == type( ( ) )
|
E721.py:36:8: E721 Do not compare types, use `isinstance()`
E721.py:39:8: E721 Do not compare types, use `isinstance()`
|
34 | assert type(res) == type ([2, ])
35 | #: E201 E201 E202 E721
36 | assert type(res) == type( ( ) )
37 | assert type(res) == type ([2, ])
38 | #: E201 E201 E202 E721
39 | assert type(res) == type( ( ) )
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
37 | #: E201 E202 E721
38 | assert type(res) == type( (0, ) )
40 | #: E201 E202 E721
41 | assert type(res) == type( (0, ) )
|
E721.py:38:8: E721 Do not compare types, use `isinstance()`
E721.py:41:8: E721 Do not compare types, use `isinstance()`
|
36 | assert type(res) == type( ( ) )
37 | #: E201 E202 E721
38 | assert type(res) == type( (0, ) )
39 | assert type(res) == type( ( ) )
40 | #: E201 E202 E721
41 | assert type(res) == type( (0, ) )
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ E721
39 | #:
42 | #:
|
E721.py:96:12: E721 Do not compare types, use `isinstance()`
|
94 | def asdf(self, value: str | None):
95 | #: E721
96 | if type(value) is str:
| ^^^^^^^^^^^^^^^^^^ E721
97 | ...
|
E721.py:106:12: E721 Do not compare types, use `isinstance()`
E721.py:107:12: E721 Do not compare types, use `isinstance()`
|
104 | def asdf(self, value: str | None):
105 | #: E721
106 | if type(value) is str:
105 | def asdf(self, value: str | None):
106 | #: E721
107 | if type(value) is str:
| ^^^^^^^^^^^^^^^^^^ E721
107 | ...
108 | ...
|
E721.py:117:12: E721 Do not compare types, use `isinstance()`
|
115 | def asdf(self, value: str | None):
116 | #: E721
117 | if type(value) is str:
| ^^^^^^^^^^^^^^^^^^ E721
118 | ...
|

View File

@@ -17,96 +17,116 @@ E721.py:5:4: E721 Use `is` and `is not` for type comparisons, or `isinstance()`
5 | if type(res) != type(""):
| ^^^^^^^^^^^^^^^^^^^^^ E721
6 | pass
7 | #: Okay
7 | #: E721
|
E721.py:18:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:8:4: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
16 | pass
17 | #: E721
18 | assert type(res) == type(False) or type(res) == type(None)
6 | pass
7 | #: E721
8 | if type(res) == memoryview:
| ^^^^^^^^^^^^^^^^^^^^^^^ E721
9 | pass
10 | #: Okay
|
E721.py:21:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
19 | pass
20 | #: E721
21 | assert type(res) == type(False) or type(res) == type(None)
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
19 | #: E721
20 | assert type(res) == type([])
22 | #: E721
23 | assert type(res) == type([])
|
E721.py:20:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:23:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
18 | assert type(res) == type(False) or type(res) == type(None)
19 | #: E721
20 | assert type(res) == type([])
21 | assert type(res) == type(False) or type(res) == type(None)
22 | #: E721
23 | assert type(res) == type([])
| ^^^^^^^^^^^^^^^^^^^^^ E721
21 | #: E721
22 | assert type(res) == type(())
24 | #: E721
25 | assert type(res) == type(())
|
E721.py:22:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:25:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
20 | assert type(res) == type([])
21 | #: E721
22 | assert type(res) == type(())
23 | assert type(res) == type([])
24 | #: E721
25 | assert type(res) == type(())
| ^^^^^^^^^^^^^^^^^^^^^ E721
23 | #: E721
24 | assert type(res) == type((0,))
26 | #: E721
27 | assert type(res) == type((0,))
|
E721.py:24:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:27:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
22 | assert type(res) == type(())
23 | #: E721
24 | assert type(res) == type((0,))
25 | assert type(res) == type(())
26 | #: E721
27 | assert type(res) == type((0,))
| ^^^^^^^^^^^^^^^^^^^^^^^ E721
25 | #: E721
26 | assert type(res) == type((0))
28 | #: E721
29 | assert type(res) == type((0))
|
E721.py:26:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:29:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
24 | assert type(res) == type((0,))
25 | #: E721
26 | assert type(res) == type((0))
27 | assert type(res) == type((0,))
28 | #: E721
29 | assert type(res) == type((0))
| ^^^^^^^^^^^^^^^^^^^^^^ E721
27 | #: E721
28 | assert type(res) != type((1, ))
30 | #: E721
31 | assert type(res) != type((1, ))
|
E721.py:28:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:31:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
26 | assert type(res) == type((0))
27 | #: E721
28 | assert type(res) != type((1, ))
29 | assert type(res) == type((0))
30 | #: E721
31 | assert type(res) != type((1, ))
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
29 | #: Okay
30 | assert type(res) is type((1, ))
32 | #: Okay
33 | assert type(res) is type((1, ))
|
E721.py:34:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:37:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
32 | assert type(res) is not type((1, ))
33 | #: E211 E721
34 | assert type(res) == type ([2, ])
35 | assert type(res) is not type((1, ))
36 | #: E211 E721
37 | assert type(res) == type ([2, ])
| ^^^^^^^^^^^^^^^^^^^^^^^^^ E721
35 | #: E201 E201 E202 E721
36 | assert type(res) == type( ( ) )
38 | #: E201 E201 E202 E721
39 | assert type(res) == type( ( ) )
|
E721.py:36:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:39:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
34 | assert type(res) == type ([2, ])
35 | #: E201 E201 E202 E721
36 | assert type(res) == type( ( ) )
37 | assert type(res) == type ([2, ])
38 | #: E201 E201 E202 E721
39 | assert type(res) == type( ( ) )
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
37 | #: E201 E202 E721
38 | assert type(res) == type( (0, ) )
40 | #: E201 E202 E721
41 | assert type(res) == type( (0, ) )
|
E721.py:38:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
E721.py:41:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
36 | assert type(res) == type( ( ) )
37 | #: E201 E202 E721
38 | assert type(res) == type( (0, ) )
39 | assert type(res) == type( ( ) )
40 | #: E201 E202 E721
41 | assert type(res) == type( (0, ) )
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ E721
39 | #:
42 | #:
|
E721.py:59:4: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
57 | pass
58 | #: E721
59 | if type(res) == type:
| ^^^^^^^^^^^^^^^^^ E721
60 | pass
61 | #: Okay
|

View File

@@ -1,6 +1,6 @@
use memchr::memchr_iter;
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
@@ -46,14 +46,16 @@ use crate::docstrings::Docstring;
#[violation]
pub struct EscapeSequenceInDocstring;
impl AlwaysFixableViolation for EscapeSequenceInDocstring {
impl Violation for EscapeSequenceInDocstring {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
format!(r#"Use `r"""` if any backslashes in a docstring"#)
}
fn fix_title(&self) -> String {
format!(r#"Add `r` prefix"#)
fn fix_title(&self) -> Option<String> {
Some(format!(r#"Add `r` prefix"#))
}
}
@@ -74,10 +76,12 @@ pub(crate) fn backslashes(checker: &mut Checker, docstring: &Docstring) {
}) {
let mut diagnostic = Diagnostic::new(EscapeSequenceInDocstring, docstring.range());
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
"r".to_owned() + docstring.contents,
docstring.range(),
)));
if !docstring.leading_quote().contains(['u', 'U']) {
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
"r".to_owned() + docstring.contents,
docstring.range(),
)));
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -61,12 +61,14 @@ impl Violation for TripleSingleQuotes {
pub(crate) fn triple_quotes(checker: &mut Checker, docstring: &Docstring) {
let leading_quote = docstring.leading_quote();
let prefixes = docstring
.leading_quote()
let prefixes = leading_quote
.trim_end_matches(|c| c == '\'' || c == '"')
.to_owned();
let expected_quote = if docstring.body().contains("\"\"\"") {
if docstring.body().contains("\'\'\'") {
return;
}
Quote::Single
} else {
Quote::Double

View File

@@ -17,4 +17,12 @@ D301.py:2:5: D301 [*] Use `r"""` if any backslashes in a docstring
4 4 |
5 5 | def double_quotes_backslash_raw():
D301.py:37:5: D301 Use `r"""` if any backslashes in a docstring
|
36 | def shouldnt_add_raw_here2():
37 | u"Sum\\mary."
| ^^^^^^^^^^^^^ D301
|
= help: Add `r` prefix

View File

@@ -1,5 +1,6 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
assertion_line: 134
---
D300.py:6:5: D300 Use triple double quotes `"""`
|
@@ -23,5 +24,8 @@ D300.py:10:5: D300 [*] Use triple double quotes `"""`
9 9 | def contains_quote():
10 |- 'Sum"\\mary.'
10 |+ """Sum"\\mary."""
11 11 |
12 12 |
13 13 | # OK

View File

@@ -26,6 +26,7 @@ mod tests {
use crate::linter::{check_path, LinterResult};
use crate::registry::{AsRule, Linter, Rule};
use crate::rules::pyflakes;
use crate::settings::types::PreviewMode;
use crate::settings::{flags, LinterSettings};
use crate::source_kind::SourceKind;
use crate::test::{test_path, test_snippet};
@@ -145,6 +146,7 @@ mod tests {
#[test_case(Rule::UnusedVariable, Path::new("F841_1.py"))]
#[test_case(Rule::UnusedVariable, Path::new("F841_2.py"))]
#[test_case(Rule::UnusedVariable, Path::new("F841_3.py"))]
#[test_case(Rule::UnusedVariable, Path::new("F841_4.py"))]
#[test_case(Rule::UnusedAnnotation, Path::new("F842.py"))]
#[test_case(Rule::RaiseNotImplemented, Path::new("F901.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
@@ -157,6 +159,24 @@ mod tests {
Ok(())
}
#[test_case(Rule::UnusedVariable, Path::new("F841_4.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("pyflakes").join(path).as_path(),
&LinterSettings {
preview: PreviewMode::Enabled,
..LinterSettings::for_rule(rule_code)
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
#[test]
fn f841_dummy_variable_rgx() -> Result<()> {
let diagnostics = test_path(
@@ -1126,7 +1146,8 @@ mod tests {
#[test]
fn used_as_star_unpack() {
// Star names in unpack are used if RHS is not a tuple/list literal.
// In stable, starred names in unpack are used if RHS is not a tuple/list literal.
// In preview, these should be marked as unused.
flakes(
r#"
def f():

View File

@@ -12,6 +12,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::checkers::ast::Checker;
use crate::fix::edits::delete_stmt;
use crate::settings::types::PreviewMode;
/// ## What it does
/// Checks for the presence of unused variables in function scopes.
@@ -24,6 +25,9 @@ use crate::fix::edits::delete_stmt;
/// prefixed with an underscore, or some other value that adheres to the
/// [`dummy-variable-rgx`] pattern.
///
/// Under [preview mode](https://docs.astral.sh/ruff/preview), this rule also
/// triggers on unused unpacked assignments (for example, `x, y = foo()`).
///
/// ## Example
/// ```python
/// def foo():
@@ -318,7 +322,10 @@ pub(crate) fn unused_variable(checker: &Checker, scope: &Scope, diagnostics: &mu
.bindings()
.map(|(name, binding_id)| (name, checker.semantic().binding(binding_id)))
.filter_map(|(name, binding)| {
if (binding.kind.is_assignment() || binding.kind.is_named_expr_assignment())
if (binding.kind.is_assignment()
|| binding.kind.is_named_expr_assignment()
|| (matches!(checker.settings.preview, PreviewMode::Enabled)
&& binding.kind.is_unpacked_assignment()))
&& !binding.is_nonlocal()
&& !binding.is_global()
&& !binding.is_used()

View File

@@ -20,7 +20,7 @@ F841_1.py:6:8: F841 Local variable `y` is assigned to but never used
F841_1.py:16:14: F841 [*] Local variable `coords` is assigned to but never used
|
15 | def f():
16 | (x, y) = coords = 1, 2 # this triggers F841 on coords
16 | (x, y) = coords = 1, 2
| ^^^^^^ F841
|
= help: Remove assignment to unused variable `coords`
@@ -29,8 +29,8 @@ F841_1.py:16:14: F841 [*] Local variable `coords` is assigned to but never used
13 13 |
14 14 |
15 15 | def f():
16 |- (x, y) = coords = 1, 2 # this triggers F841 on coords
16 |+ (x, y) = 1, 2 # this triggers F841 on coords
16 |- (x, y) = coords = 1, 2
16 |+ (x, y) = 1, 2
17 17 |
18 18 |
19 19 | def f():
@@ -38,7 +38,7 @@ F841_1.py:16:14: F841 [*] Local variable `coords` is assigned to but never used
F841_1.py:20:5: F841 [*] Local variable `coords` is assigned to but never used
|
19 | def f():
20 | coords = (x, y) = 1, 2 # this triggers F841 on coords
20 | coords = (x, y) = 1, 2
| ^^^^^^ F841
|
= help: Remove assignment to unused variable `coords`
@@ -47,8 +47,8 @@ F841_1.py:20:5: F841 [*] Local variable `coords` is assigned to but never used
17 17 |
18 18 |
19 19 | def f():
20 |- coords = (x, y) = 1, 2 # this triggers F841 on coords
20 |+ (x, y) = 1, 2 # this triggers F841 on coords
20 |- coords = (x, y) = 1, 2
20 |+ (x, y) = 1, 2
21 21 |
22 22 |
23 23 | def f():

View File

@@ -0,0 +1,23 @@
---
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
---
F841_4.py:12:5: F841 [*] Local variable `a` is assigned to but never used
|
11 | def bar():
12 | a = foo()
| ^ F841
13 | b, c = foo()
|
= help: Remove assignment to unused variable `a`
Suggested fix
9 9 |
10 10 |
11 11 | def bar():
12 |- a = foo()
12 |+ foo()
13 13 | b, c = foo()
14 14 |
15 15 |

View File

@@ -0,0 +1,41 @@
---
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
---
F841_4.py:12:5: F841 [*] Local variable `a` is assigned to but never used
|
11 | def bar():
12 | a = foo()
| ^ F841
13 | b, c = foo()
|
= help: Remove assignment to unused variable `a`
Suggested fix
9 9 |
10 10 |
11 11 | def bar():
12 |- a = foo()
12 |+ foo()
13 13 | b, c = foo()
14 14 |
15 15 |
F841_4.py:13:5: F841 Local variable `b` is assigned to but never used
|
11 | def bar():
12 | a = foo()
13 | b, c = foo()
| ^ F841
|
= help: Remove assignment to unused variable `b`
F841_4.py:13:8: F841 Local variable `c` is assigned to but never used
|
11 | def bar():
12 | a = foo()
13 | b, c = foo()
| ^ F841
|
= help: Remove assignment to unused variable `c`

View File

@@ -60,6 +60,7 @@ mod tests {
#[test_case(Rule::ReplaceStdoutStderr, Path::new("UP022.py"))]
#[test_case(Rule::ReplaceUniversalNewlines, Path::new("UP021.py"))]
#[test_case(Rule::SuperCallWithParameters, Path::new("UP008.py"))]
#[test_case(Rule::TimeoutErrorAlias, Path::new("UP041.py"))]
#[test_case(Rule::TypeOfPrimitive, Path::new("UP003.py"))]
#[test_case(Rule::TypingTextStrAlias, Path::new("UP019.py"))]
#[test_case(Rule::UTF8EncodingDeclaration, Path::new("UP009_0.py"))]

View File

@@ -191,15 +191,21 @@ fn try_convert_to_f_string(
summary: &mut FormatSummaryValues,
locator: &Locator,
) -> Result<Option<String>> {
let contents = locator.slice(range);
// Strip the unicode prefix. It's redundant in Python 3, and invalid when used
// with f-strings.
let contents = locator.slice(range);
let contents = if contents.starts_with('U') || contents.starts_with('u') {
&contents[1..]
} else {
contents
};
// Temporarily strip the raw prefix, if present. It will be prepended to the result, before the
// 'f', to match the prefix order both the Ruff formatter (and Black) use when formatting code.
let raw = contents.starts_with('R') || contents.starts_with('r');
let contents = if raw { &contents[1..] } else { contents };
// Remove the leading and trailing quotes.
let leading_quote = leading_quote(contents).context("Unable to identify leading quote")?;
let trailing_quote = trailing_quote(contents).context("Unable to identify trailing quote")?;
@@ -291,7 +297,10 @@ fn try_convert_to_f_string(
}
// Construct the format string.
let mut contents = String::with_capacity(1 + converted.len());
let mut contents = String::with_capacity(usize::from(raw) + 1 + converted.len());
if raw {
contents.push('r');
}
contents.push('f');
contents.push_str(leading_quote);
contents.push_str(&converted);

View File

@@ -20,6 +20,7 @@ pub(crate) use redundant_open_modes::*;
pub(crate) use replace_stdout_stderr::*;
pub(crate) use replace_universal_newlines::*;
pub(crate) use super_call_with_parameters::*;
pub(crate) use timeout_error_alias::*;
pub(crate) use type_of_primitive::*;
pub(crate) use typing_text_str_alias::*;
pub(crate) use unicode_kind_prefix::*;
@@ -59,6 +60,7 @@ mod redundant_open_modes;
mod replace_stdout_stderr;
mod replace_universal_newlines;
mod super_call_with_parameters;
mod timeout_error_alias;
mod type_of_primitive;
mod typing_text_str_alias;
mod unicode_kind_prefix;

View File

@@ -0,0 +1,192 @@
use ruff_python_ast::{self as ast, ExceptHandler, Expr, ExprContext};
use ruff_text_size::{Ranged, TextRange};
use crate::fix::edits::pad;
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::compose_call_path;
use ruff_python_semantic::SemanticModel;
use crate::checkers::ast::Checker;
use crate::settings::types::PythonVersion;
/// ## What it does
/// Checks for uses of exceptions that alias `TimeoutError`.
///
/// ## Why is this bad?
/// `TimeoutError` is the builtin error type used for exceptions when a system
/// function timed out at the system level.
///
/// In Python 3.10, `socket.timeout` was aliased to `TimeoutError`. In Python
/// 3.11, `asyncio.TimeoutError` was aliased to `TimeoutError`.
///
/// These aliases remain in place for compatibility with older versions of
/// Python, but may be removed in future versions.
///
/// Prefer using `TimeoutError` directly, as it is more idiomatic and future-proof.
///
/// ## Example
/// ```python
/// raise asyncio.TimeoutError
/// ```
///
/// Use instead:
/// ```python
/// raise TimeoutError
/// ```
///
/// ## References
/// - [Python documentation: `TimeoutError`](https://docs.python.org/3/library/exceptions.html#TimeoutError)
#[violation]
pub struct TimeoutErrorAlias {
name: Option<String>,
}
impl AlwaysFixableViolation for TimeoutErrorAlias {
#[derive_message_formats]
fn message(&self) -> String {
format!("Replace aliased errors with `TimeoutError`")
}
fn fix_title(&self) -> String {
let TimeoutErrorAlias { name } = self;
match name {
None => "Replace with builtin `TimeoutError`".to_string(),
Some(name) => format!("Replace `{name}` with builtin `TimeoutError`"),
}
}
}
/// Return `true` if an [`Expr`] is an alias of `TimeoutError`.
fn is_alias(expr: &Expr, semantic: &SemanticModel, target_version: PythonVersion) -> bool {
semantic.resolve_call_path(expr).is_some_and(|call_path| {
if target_version >= PythonVersion::Py311 {
matches!(call_path.as_slice(), [""] | ["asyncio", "TimeoutError"])
} else {
matches!(
call_path.as_slice(),
[""] | ["asyncio", "TimeoutError"] | ["socket", "timeout"]
)
}
})
}
/// Return `true` if an [`Expr`] is `TimeoutError`.
fn is_timeout_error(expr: &Expr, semantic: &SemanticModel) -> bool {
semantic
.resolve_call_path(expr)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["", "TimeoutError"]))
}
/// Create a [`Diagnostic`] for a single target, like an [`Expr::Name`].
fn atom_diagnostic(checker: &mut Checker, target: &Expr) {
let mut diagnostic = Diagnostic::new(
TimeoutErrorAlias {
name: compose_call_path(target),
},
target.range(),
);
if checker.semantic().is_builtin("TimeoutError") {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
"TimeoutError".to_string(),
target.range(),
)));
}
checker.diagnostics.push(diagnostic);
}
/// Create a [`Diagnostic`] for a tuple of expressions.
fn tuple_diagnostic(checker: &mut Checker, tuple: &ast::ExprTuple, aliases: &[&Expr]) {
let mut diagnostic = Diagnostic::new(TimeoutErrorAlias { name: None }, tuple.range());
if checker.semantic().is_builtin("TimeoutError") {
// Filter out any `TimeoutErrors` aliases.
let mut remaining: Vec<Expr> = tuple
.elts
.iter()
.filter_map(|elt| {
if aliases.contains(&elt) {
None
} else {
Some(elt.clone())
}
})
.collect();
// If `TimeoutError` itself isn't already in the tuple, add it.
if tuple
.elts
.iter()
.all(|elt| !is_timeout_error(elt, checker.semantic()))
{
let node = ast::ExprName {
id: "TimeoutError".into(),
ctx: ExprContext::Load,
range: TextRange::default(),
};
remaining.insert(0, node.into());
}
let content = if remaining.len() == 1 {
"TimeoutError".to_string()
} else {
let node = ast::ExprTuple {
elts: remaining,
ctx: ExprContext::Load,
range: TextRange::default(),
};
format!("({})", checker.generator().expr(&node.into()))
};
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
pad(content, tuple.range(), checker.locator()),
tuple.range(),
)));
}
checker.diagnostics.push(diagnostic);
}
/// UP041
pub(crate) fn timeout_error_alias_handlers(checker: &mut Checker, handlers: &[ExceptHandler]) {
for handler in handlers {
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { type_, .. }) = handler;
let Some(expr) = type_.as_ref() else {
continue;
};
match expr.as_ref() {
Expr::Name(_) | Expr::Attribute(_) => {
if is_alias(expr, checker.semantic(), checker.settings.target_version) {
atom_diagnostic(checker, expr);
}
}
Expr::Tuple(tuple) => {
// List of aliases to replace with `TimeoutError`.
let mut aliases: Vec<&Expr> = vec![];
for elt in &tuple.elts {
if is_alias(elt, checker.semantic(), checker.settings.target_version) {
aliases.push(elt);
}
}
if !aliases.is_empty() {
tuple_diagnostic(checker, tuple, &aliases);
}
}
_ => {}
}
}
}
/// UP041
pub(crate) fn timeout_error_alias_call(checker: &mut Checker, func: &Expr) {
if is_alias(func, checker.semantic(), checker.settings.target_version) {
atom_diagnostic(checker, func);
}
}
/// UP041
pub(crate) fn timeout_error_alias_raise(checker: &mut Checker, expr: &Expr) {
if matches!(expr, Expr::Name(_) | Expr::Attribute(_)) {
if is_alias(expr, checker.semantic(), checker.settings.target_version) {
atom_diagnostic(checker, expr);
}
}
}

View File

@@ -138,7 +138,7 @@ pub(crate) fn non_pep695_type_alias(checker: &mut Checker, stmt: &StmtAnnAssign)
stmt.range(),
);
// The fix is only safe in a type stub because new-style aliases have different runtime behavior
// The fix is only safe in a type stub because new-style aliases have different runtime behavior
// See https://github.com/astral-sh/ruff/issues/6434
let fix = if checker.source_type.is_stub() {
Fix::safe_edit(edit)

View File

@@ -353,7 +353,7 @@ UP032_0.py:37:1: UP032 [*] Use f-string instead of `format` call
35 35 | "foo{}".format(1)
36 36 |
37 |-r"foo{}".format(1)
37 |+fr"foo{1}"
37 |+rf"foo{1}"
38 38 |
39 39 | x = "{a}".format(a=1)
40 40 |

View File

@@ -0,0 +1,104 @@
---
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
---
UP041.py:5:8: UP041 [*] Replace aliased errors with `TimeoutError`
|
3 | try:
4 | pass
5 | except asyncio.TimeoutError:
| ^^^^^^^^^^^^^^^^^^^^ UP041
6 | pass
|
= help: Replace `asyncio.TimeoutError` with builtin `TimeoutError`
Fix
2 2 | # These should be fixed
3 3 | try:
4 4 | pass
5 |-except asyncio.TimeoutError:
5 |+except TimeoutError:
6 6 | pass
7 7 |
8 8 | try:
UP041.py:17:8: UP041 [*] Replace aliased errors with `TimeoutError`
|
15 | try:
16 | pass
17 | except (asyncio.TimeoutError,):
| ^^^^^^^^^^^^^^^^^^^^^^^ UP041
18 | pass
|
= help: Replace with builtin `TimeoutError`
Fix
14 14 |
15 15 | try:
16 16 | pass
17 |-except (asyncio.TimeoutError,):
17 |+except TimeoutError:
18 18 | pass
19 19 |
20 20 | try:
UP041.py:27:8: UP041 [*] Replace aliased errors with `TimeoutError`
|
25 | try:
26 | pass
27 | except (asyncio.TimeoutError, socket.timeout,):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP041
28 | pass
|
= help: Replace with builtin `TimeoutError`
Fix
24 24 |
25 25 | try:
26 26 | pass
27 |-except (asyncio.TimeoutError, socket.timeout,):
27 |+except (TimeoutError, socket.timeout):
28 28 | pass
29 29 |
30 30 | # Should be kept in parentheses (because multiple)
UP041.py:34:8: UP041 [*] Replace aliased errors with `TimeoutError`
|
32 | try:
33 | pass
34 | except (asyncio.TimeoutError, socket.timeout, KeyError, TimeoutError):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP041
35 | pass
|
= help: Replace with builtin `TimeoutError`
Fix
31 31 |
32 32 | try:
33 33 | pass
34 |-except (asyncio.TimeoutError, socket.timeout, KeyError, TimeoutError):
34 |+except (socket.timeout, KeyError, TimeoutError):
35 35 | pass
36 36 |
37 37 | # First should change, second should not
UP041.py:42:8: UP041 [*] Replace aliased errors with `TimeoutError`
|
40 | try:
41 | pass
42 | except (asyncio.TimeoutError, error):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP041
43 | pass
|
= help: Replace with builtin `TimeoutError`
Fix
39 39 | from .mmap import error
40 40 | try:
41 41 | pass
42 |-except (asyncio.TimeoutError, error):
42 |+except (TimeoutError, error):
43 43 | pass
44 44 |
45 45 | # These should not change

View File

@@ -34,3 +34,30 @@ pub(super) fn generate_method_call(name: &str, method: &str, generator: Generato
};
generator.stmt(&stmt.into())
}
/// Format a code snippet comparing `name` to `None` (e.g., `name is None`).
pub(super) fn generate_none_identity_comparison(
name: &str,
negate: bool,
generator: Generator,
) -> String {
// Construct `name`.
let var = ast::ExprName {
id: name.to_string(),
ctx: ast::ExprContext::Load,
range: TextRange::default(),
};
// Construct `name is None` or `name is not None`.
let op = if negate {
ast::CmpOp::IsNot
} else {
ast::CmpOp::Is
};
let compare = ast::ExprCompare {
left: Box::new(var.into()),
ops: vec![op],
comparators: vec![ast::Expr::NoneLiteral(ast::ExprNoneLiteral::default())],
range: TextRange::default(),
};
generator.expr(&compare.into())
}

View File

@@ -25,6 +25,7 @@ mod tests {
#[test_case(Rule::ImplicitCwd, Path::new("FURB177.py"))]
#[test_case(Rule::SingleItemMembershipTest, Path::new("FURB171.py"))]
#[test_case(Rule::IsinstanceTypeNone, Path::new("FURB168.py"))]
#[test_case(Rule::TypeNoneComparison, Path::new("FURB169.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -97,9 +97,9 @@ pub(crate) fn check_and_remove_from_set(checker: &mut Checker, if_stmt: &ast::St
// Check if what we assume is set is indeed a set.
if !checker
.semantic()
.resolve_name(check_set)
.map(|binding_id| checker.semantic().binding(binding_id))
.map_or(false, |binding| is_set(binding, checker.semantic()))
.only_binding(check_set)
.map(|id| checker.semantic().binding(id))
.is_some_and(|binding| is_set(binding, checker.semantic()))
{
return;
};

View File

@@ -2,7 +2,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::analyze::typing::{is_dict, is_list};
use ruff_python_semantic::{Binding, SemanticModel};
use ruff_python_semantic::SemanticModel;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@@ -70,7 +70,7 @@ pub(crate) fn delete_full_slice(checker: &mut Checker, delete: &ast::StmtDelete)
// Fix is only supported for single-target deletions.
if delete.targets.len() == 1 {
let replacement = generate_method_call(name, "clear", checker.generator());
let replacement = generate_method_call(&name.id, "clear", checker.generator());
diagnostic.set_fix(Fix::unsafe_edit(Edit::replacement(
replacement,
delete.start(),
@@ -83,7 +83,7 @@ pub(crate) fn delete_full_slice(checker: &mut Checker, delete: &ast::StmtDelete)
}
/// Match `del expr[:]` where `expr` is a list or a dict.
fn match_full_slice<'a>(expr: &'a Expr, semantic: &SemanticModel) -> Option<&'a str> {
fn match_full_slice<'a>(expr: &'a Expr, semantic: &SemanticModel) -> Option<&'a ast::ExprName> {
// Check that it is `del expr[...]`.
let subscript = expr.as_subscript_expr()?;
@@ -100,22 +100,9 @@ fn match_full_slice<'a>(expr: &'a Expr, semantic: &SemanticModel) -> Option<&'a
return None;
}
// Check that it is del var[:]
let ast::ExprName { id: name, .. } = subscript.value.as_name_expr()?;
// Let's find definition for var
let scope = semantic.current_scope();
let bindings: Vec<&Binding> = scope
.get_all(name)
.map(|binding_id| semantic.binding(binding_id))
.collect();
// NOTE: Maybe it is too strict of a limitation, but it seems reasonable.
let [binding] = bindings.as_slice() else {
return None;
};
// It should only apply to variables that are known to be lists or dicts.
let name = subscript.value.as_name_expr()?;
let binding = semantic.binding(semantic.only_binding(name)?);
if !(is_dict(binding, semantic) || is_list(binding, semantic)) {
return None;
}

View File

@@ -2,11 +2,11 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_python_ast::{self as ast, Expr, Operator};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_codegen::Generator;
use ruff_text_size::{Ranged, TextRange};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::fix::edits::pad;
use crate::rules::refurb::helpers::generate_none_identity_comparison;
/// ## What it does
/// Checks for uses of `isinstance` that check if an object is of type `None`.
@@ -69,7 +69,8 @@ pub(crate) fn isinstance_type_none(checker: &mut Checker, call: &ast::ExprCall)
return;
};
let mut diagnostic = Diagnostic::new(IsinstanceTypeNone, call.range());
let replacement = generate_replacement(object_name, checker.generator());
let replacement =
generate_none_identity_comparison(object_name, false, checker.generator());
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
pad(replacement, call.range(), checker.locator()),
call.range(),
@@ -117,21 +118,3 @@ fn is_none(expr: &Expr) -> bool {
}
inner(expr, false)
}
/// Format a code snippet comparing `name` to `None` (e.g., `name is None`).
fn generate_replacement(name: &str, generator: Generator) -> String {
// Construct `name`.
let var = ast::ExprName {
id: name.to_string(),
ctx: ast::ExprContext::Load,
range: TextRange::default(),
};
// Construct `name is None`.
let compare = ast::ExprCompare {
left: Box::new(var.into()),
ops: vec![ast::CmpOp::Is],
comparators: vec![ast::Expr::NoneLiteral(ast::ExprNoneLiteral::default())],
range: TextRange::default(),
};
generator.expr(&compare.into())
}

View File

@@ -8,6 +8,7 @@ pub(crate) use reimplemented_starmap::*;
pub(crate) use repeated_append::*;
pub(crate) use single_item_membership_test::*;
pub(crate) use slice_copy::*;
pub(crate) use type_none_comparison::*;
pub(crate) use unnecessary_enumerate::*;
mod check_and_remove_from_set;
@@ -20,4 +21,5 @@ mod reimplemented_starmap;
mod repeated_append;
mod single_item_membership_test;
mod slice_copy;
mod type_none_comparison;
mod unnecessary_enumerate;

View File

@@ -76,7 +76,7 @@ impl Violation for RepeatedAppend {
/// FURB113
pub(crate) fn repeated_append(checker: &mut Checker, stmt: &Stmt) {
let Some(appends) = match_consecutive_appends(checker.semantic(), stmt) else {
let Some(appends) = match_consecutive_appends(stmt, checker.semantic()) else {
return;
};
@@ -163,8 +163,8 @@ impl Ranged for AppendGroup<'_> {
/// Match consecutive calls to `append` on list variables starting from the given statement.
fn match_consecutive_appends<'a>(
semantic: &'a SemanticModel,
stmt: &'a Stmt,
semantic: &'a SemanticModel,
) -> Option<Vec<Append<'a>>> {
// Match the current statement, to see if it's an append.
let append = match_append(semantic, stmt)?;

View File

@@ -0,0 +1,153 @@
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, CmpOp, Expr};
use ruff_python_semantic::SemanticModel;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::fix::edits::pad;
use crate::rules::refurb::helpers::generate_none_identity_comparison;
/// ## What it does
/// Checks for uses of `type` that compare the type of an object to the type of
/// `None`.
///
/// ## Why is this bad?
/// There is only ever one instance of `None`, so it is more efficient and
/// readable to use the `is` operator to check if an object is `None`.
///
/// ## Example
/// ```python
/// type(obj) is type(None)
/// ```
///
/// Use instead:
/// ```python
/// obj is None
/// ```
///
/// ## References
/// - [Python documentation: `isinstance`](https://docs.python.org/3/library/functions.html#isinstance)
/// - [Python documentation: `None`](https://docs.python.org/3/library/constants.html#None)
/// - [Python documentation: `type`](https://docs.python.org/3/library/functions.html#type)
/// - [Python documentation: Identity comparisons](https://docs.python.org/3/reference/expressions.html#is-not)
#[violation]
pub struct TypeNoneComparison {
object: String,
comparison: Comparison,
}
impl Violation for TypeNoneComparison {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let TypeNoneComparison { object, .. } = self;
format!("Compare the identities of `{object}` and `None` instead of their respective types")
}
fn fix_title(&self) -> Option<String> {
let TypeNoneComparison { object, comparison } = self;
match comparison {
Comparison::Is | Comparison::Eq => Some(format!("Replace with `{object} is None`")),
Comparison::IsNot | Comparison::NotEq => {
Some(format!("Replace with `{object} is not None`"))
}
}
}
}
/// FURB169
pub(crate) fn type_none_comparison(checker: &mut Checker, compare: &ast::ExprCompare) {
let ([op], [right]) = (compare.ops.as_slice(), compare.comparators.as_slice()) else {
return;
};
// Ensure that the comparison is an identity or equality test.
let comparison = match op {
CmpOp::Is => Comparison::Is,
CmpOp::IsNot => Comparison::IsNot,
CmpOp::Eq => Comparison::Eq,
CmpOp::NotEq => Comparison::NotEq,
_ => return,
};
// Get the objects whose types are being compared.
let Some(left_arg) = type_call_arg(&compare.left, checker.semantic()) else {
return;
};
let Some(right_arg) = type_call_arg(right, checker.semantic()) else {
return;
};
// If one of the objects is `None`, get the other object; else, return.
let other_arg = match (
left_arg.is_none_literal_expr(),
right_arg.is_none_literal_expr(),
) {
(true, false) => right_arg,
(false, true) => left_arg,
// If both are `None`, just pick one.
(true, true) => left_arg,
_ => return,
};
// Get the name of the other object (or `None` if both were `None`).
let other_arg_name = match other_arg {
Expr::Name(ast::ExprName { id, .. }) => id.as_str(),
Expr::NoneLiteral { .. } => "None",
_ => return,
};
let mut diagnostic = Diagnostic::new(
TypeNoneComparison {
object: other_arg_name.to_string(),
comparison,
},
compare.range(),
);
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
pad(
match comparison {
Comparison::Is | Comparison::Eq => {
generate_none_identity_comparison(other_arg_name, false, checker.generator())
}
Comparison::IsNot | Comparison::NotEq => {
generate_none_identity_comparison(other_arg_name, true, checker.generator())
}
},
compare.range(),
checker.locator(),
),
compare.range(),
)));
checker.diagnostics.push(diagnostic);
}
/// Returns the object passed to the function, if the expression is a call to
/// `type` with a single argument.
fn type_call_arg<'a>(expr: &'a Expr, semantic: &'a SemanticModel) -> Option<&'a Expr> {
// The expression must be a single-argument call to `type`.
let ast::ExprCall {
func, arguments, ..
} = expr.as_call_expr()?;
if arguments.len() != 1 {
return None;
}
// The function itself must be the builtin `type`.
let ast::ExprName { id, .. } = func.as_name_expr()?;
if id.as_str() != "type" || !semantic.is_builtin(id) {
return None;
}
arguments.find_positional(0)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Comparison {
Is,
IsNot,
Eq,
NotEq,
}

View File

@@ -6,7 +6,7 @@ use ruff_python_ast as ast;
use ruff_python_ast::{Arguments, Expr, Int};
use ruff_python_codegen::Generator;
use ruff_python_semantic::analyze::typing::{is_dict, is_list, is_set, is_tuple};
use ruff_python_semantic::Binding;
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
@@ -114,7 +114,7 @@ pub(crate) fn unnecessary_enumerate(checker: &mut Checker, stmt_for: &ast::StmtF
};
// Get the first argument, which is the sequence to iterate over.
let Some(Expr::Name(ast::ExprName { id: sequence, .. })) = arguments.args.first() else {
let Some(Expr::Name(sequence)) = arguments.args.first() else {
return;
};
@@ -138,7 +138,8 @@ pub(crate) fn unnecessary_enumerate(checker: &mut Checker, stmt_for: &ast::StmtF
);
// The index is unused, so replace with `for value in sequence`.
let replace_iter = Edit::range_replacement(sequence.into(), stmt_for.iter.range());
let replace_iter =
Edit::range_replacement(sequence.id.to_string(), stmt_for.iter.range());
let replace_target = Edit::range_replacement(
pad(
checker.locator().slice(value).to_string(),
@@ -154,12 +155,11 @@ pub(crate) fn unnecessary_enumerate(checker: &mut Checker, stmt_for: &ast::StmtF
(false, true) => {
// Ensure the sequence object works with `len`. If it doesn't, the
// fix is unclear.
let scope = checker.semantic().current_scope();
let bindings: Vec<&Binding> = scope
.get_all(sequence)
.map(|binding_id| checker.semantic().binding(binding_id))
.collect();
let [binding] = bindings.as_slice() else {
let Some(binding) = checker
.semantic()
.only_binding(sequence)
.map(|id| checker.semantic().binding(id))
else {
return;
};
// This will lead to a lot of false negatives, but it is the best
@@ -193,7 +193,7 @@ pub(crate) fn unnecessary_enumerate(checker: &mut Checker, stmt_for: &ast::StmtF
)
}) {
let replace_iter = Edit::range_replacement(
generate_range_len_call(sequence, checker.generator()),
generate_range_len_call(&sequence.id, checker.generator()),
stmt_for.iter.range(),
);

View File

@@ -127,6 +127,27 @@ FURB131.py:43:5: FURB131 [*] Prefer `clear` over deleting a full slice
43 |+ x.clear()
44 44 |
45 45 |
46 46 | # these should not
46 46 | def yes_five(x: Dict[int, str]):
FURB131.py:48:5: FURB131 [*] Prefer `clear` over deleting a full slice
|
46 | def yes_five(x: Dict[int, str]):
47 | # FURB131
48 | del x[:]
| ^^^^^^^^ FURB131
49 |
50 | x = 1
|
= help: Replace with `clear()`
Suggested fix
45 45 |
46 46 | def yes_five(x: Dict[int, str]):
47 47 | # FURB131
48 |- del x[:]
48 |+ x.clear()
49 49 |
50 50 | x = 1
51 51 |

View File

@@ -0,0 +1,256 @@
---
source: crates/ruff_linter/src/rules/refurb/mod.rs
---
FURB169.py:5:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
3 | # Error.
4 |
5 | type(foo) is type(None)
| ^^^^^^^^^^^^^^^^^^^^^^^ FURB169
6 |
7 | type(None) is type(foo)
|
= help: Replace with `foo is None`
Fix
2 2 |
3 3 | # Error.
4 4 |
5 |-type(foo) is type(None)
5 |+foo is None
6 6 |
7 7 | type(None) is type(foo)
8 8 |
FURB169.py:7:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
5 | type(foo) is type(None)
6 |
7 | type(None) is type(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^ FURB169
8 |
9 | type(None) is type(None)
|
= help: Replace with `foo is None`
Fix
4 4 |
5 5 | type(foo) is type(None)
6 6 |
7 |-type(None) is type(foo)
7 |+foo is None
8 8 |
9 9 | type(None) is type(None)
10 10 |
FURB169.py:9:1: FURB169 [*] Compare the identities of `None` and `None` instead of their respective types
|
7 | type(None) is type(foo)
8 |
9 | type(None) is type(None)
| ^^^^^^^^^^^^^^^^^^^^^^^^ FURB169
10 |
11 | type(foo) is not type(None)
|
= help: Replace with `None is None`
Fix
6 6 |
7 7 | type(None) is type(foo)
8 8 |
9 |-type(None) is type(None)
9 |+None is None
10 10 |
11 11 | type(foo) is not type(None)
12 12 |
FURB169.py:11:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
9 | type(None) is type(None)
10 |
11 | type(foo) is not type(None)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB169
12 |
13 | type(None) is not type(foo)
|
= help: Replace with `foo is not None`
Fix
8 8 |
9 9 | type(None) is type(None)
10 10 |
11 |-type(foo) is not type(None)
11 |+foo is not None
12 12 |
13 13 | type(None) is not type(foo)
14 14 |
FURB169.py:13:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
11 | type(foo) is not type(None)
12 |
13 | type(None) is not type(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB169
14 |
15 | type(None) is not type(None)
|
= help: Replace with `foo is not None`
Fix
10 10 |
11 11 | type(foo) is not type(None)
12 12 |
13 |-type(None) is not type(foo)
13 |+foo is not None
14 14 |
15 15 | type(None) is not type(None)
16 16 |
FURB169.py:15:1: FURB169 [*] Compare the identities of `None` and `None` instead of their respective types
|
13 | type(None) is not type(foo)
14 |
15 | type(None) is not type(None)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB169
16 |
17 | type(foo) == type(None)
|
= help: Replace with `None is not None`
Fix
12 12 |
13 13 | type(None) is not type(foo)
14 14 |
15 |-type(None) is not type(None)
15 |+None is not None
16 16 |
17 17 | type(foo) == type(None)
18 18 |
FURB169.py:17:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
15 | type(None) is not type(None)
16 |
17 | type(foo) == type(None)
| ^^^^^^^^^^^^^^^^^^^^^^^ FURB169
18 |
19 | type(None) == type(foo)
|
= help: Replace with `foo is None`
Fix
14 14 |
15 15 | type(None) is not type(None)
16 16 |
17 |-type(foo) == type(None)
17 |+foo is None
18 18 |
19 19 | type(None) == type(foo)
20 20 |
FURB169.py:19:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
17 | type(foo) == type(None)
18 |
19 | type(None) == type(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^ FURB169
20 |
21 | type(None) == type(None)
|
= help: Replace with `foo is None`
Fix
16 16 |
17 17 | type(foo) == type(None)
18 18 |
19 |-type(None) == type(foo)
19 |+foo is None
20 20 |
21 21 | type(None) == type(None)
22 22 |
FURB169.py:21:1: FURB169 [*] Compare the identities of `None` and `None` instead of their respective types
|
19 | type(None) == type(foo)
20 |
21 | type(None) == type(None)
| ^^^^^^^^^^^^^^^^^^^^^^^^ FURB169
22 |
23 | type(foo) != type(None)
|
= help: Replace with `None is None`
Fix
18 18 |
19 19 | type(None) == type(foo)
20 20 |
21 |-type(None) == type(None)
21 |+None is None
22 22 |
23 23 | type(foo) != type(None)
24 24 |
FURB169.py:23:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
21 | type(None) == type(None)
22 |
23 | type(foo) != type(None)
| ^^^^^^^^^^^^^^^^^^^^^^^ FURB169
24 |
25 | type(None) != type(foo)
|
= help: Replace with `foo is not None`
Fix
20 20 |
21 21 | type(None) == type(None)
22 22 |
23 |-type(foo) != type(None)
23 |+foo is not None
24 24 |
25 25 | type(None) != type(foo)
26 26 |
FURB169.py:25:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
23 | type(foo) != type(None)
24 |
25 | type(None) != type(foo)
| ^^^^^^^^^^^^^^^^^^^^^^^ FURB169
26 |
27 | type(None) != type(None)
|
= help: Replace with `foo is not None`
Fix
22 22 |
23 23 | type(foo) != type(None)
24 24 |
25 |-type(None) != type(foo)
25 |+foo is not None
26 26 |
27 27 | type(None) != type(None)
28 28 |
FURB169.py:27:1: FURB169 [*] Compare the identities of `None` and `None` instead of their respective types
|
25 | type(None) != type(foo)
26 |
27 | type(None) != type(None)
| ^^^^^^^^^^^^^^^^^^^^^^^^ FURB169
28 |
29 | # Ok.
|
= help: Replace with `None is not None`
Fix
24 24 |
25 25 | type(None) != type(foo)
26 26 |
27 |-type(None) != type(None)
27 |+None is not None
28 28 |
29 29 | # Ok.
30 30 |

View File

@@ -17,7 +17,7 @@ mod tests {
use crate::pyproject_toml::lint_pyproject_toml;
use crate::registry::Rule;
use crate::settings::resolve_per_file_ignores;
use crate::settings::types::{PerFileIgnore, PythonVersion};
use crate::settings::types::{PerFileIgnore, PreviewMode, PythonVersion};
use crate::test::{test_path, test_resource_path};
use crate::{assert_messages, settings};
@@ -88,6 +88,24 @@ mod tests {
Ok(())
}
#[test]
fn preview_confusables() -> Result<()> {
let diagnostics = test_path(
Path::new("ruff/confusables.py"),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
allowed_confusables: FxHashSet::from_iter(['', 'ρ', '']),
..settings::LinterSettings::for_rules(vec![
Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring,
Rule::AmbiguousUnicodeCharacterComment,
])
},
)?;
assert_messages!(diagnostics);
Ok(())
}
#[test]
fn noqa() -> Result<()> {
let diagnostics = test_path(

View File

@@ -13,12 +13,20 @@ use crate::rules::ruff::rules::Context;
use crate::settings::LinterSettings;
/// ## What it does
/// Checks for ambiguous unicode characters in strings.
/// Checks for ambiguous Unicode characters in strings.
///
/// ## Why is this bad?
/// The use of ambiguous unicode characters can confuse readers and cause
/// Some Unicode characters are visually similar to ASCII characters, but have
/// different code points. For example, `LATIN CAPITAL LETTER A` (`U+0041`) is
/// visually similar, but not identical, to the ASCII character `A`.
///
/// The use of ambiguous Unicode characters can confuse readers and cause
/// subtle bugs.
///
/// In [preview], this rule will also flag Unicode characters that are
/// confusable with other, non-preferred Unicode characters. For example, the
/// spec recommends `GREEK CAPITAL LETTER OMEGA` over `OHM SIGN`.
///
/// ## Example
/// ```python
/// print("Ηello, world!") # "Η" is the Greek eta (`U+0397`).
@@ -28,6 +36,8 @@ use crate::settings::LinterSettings;
/// ```python
/// print("Hello, world!") # "H" is the Latin capital H (`U+0048`).
/// ```
///
/// [preview]: https://docs.astral.sh/ruff/preview/
#[violation]
pub struct AmbiguousUnicodeCharacterString {
confusable: char,
@@ -50,12 +60,20 @@ impl Violation for AmbiguousUnicodeCharacterString {
}
/// ## What it does
/// Checks for ambiguous unicode characters in docstrings.
/// Checks for ambiguous Unicode characters in docstrings.
///
/// ## Why is this bad?
/// The use of ambiguous unicode characters can confuse readers and cause
/// Some Unicode characters are visually similar to ASCII characters, but have
/// different code points. For example, `LATIN CAPITAL LETTER A` (`U+0041`) is
/// visually similar, but not identical, to the ASCII character `A`.
///
/// The use of ambiguous Unicode characters can confuse readers and cause
/// subtle bugs.
///
/// In [preview], this rule will also flag Unicode characters that are
/// confusable with other, non-preferred Unicode characters. For example, the
/// spec recommends `GREEK CAPITAL LETTER OMEGA` over `OHM SIGN`.
///
/// ## Example
/// ```python
/// """A lovely docstring (with a `U+FF09` parenthesis."""
@@ -65,6 +83,8 @@ impl Violation for AmbiguousUnicodeCharacterString {
/// ```python
/// """A lovely docstring (with no strange parentheses)."""
/// ```
///
/// [preview]: https://docs.astral.sh/ruff/preview/
#[violation]
pub struct AmbiguousUnicodeCharacterDocstring {
confusable: char,
@@ -87,12 +107,20 @@ impl Violation for AmbiguousUnicodeCharacterDocstring {
}
/// ## What it does
/// Checks for ambiguous unicode characters in comments.
/// Checks for ambiguous Unicode characters in comments.
///
/// ## Why is this bad?
/// The use of ambiguous unicode characters can confuse readers and cause
/// Some Unicode characters are visually similar to ASCII characters, but have
/// different code points. For example, `LATIN CAPITAL LETTER A` (`U+0041`) is
/// visually similar, but not identical, to the ASCII character `A`.
///
/// The use of ambiguous Unicode characters can confuse readers and cause
/// subtle bugs.
///
/// In [preview], this rule will also flag Unicode characters that are
/// confusable with other, non-preferred Unicode characters. For example, the
/// spec recommends `GREEK CAPITAL LETTER OMEGA` over `OHM SIGN`.
///
/// ## Example
/// ```python
/// foo() # nоqa # "о" is Cyrillic (`U+043E`)
@@ -102,6 +130,8 @@ impl Violation for AmbiguousUnicodeCharacterDocstring {
/// ```python
/// foo() # noqa # "o" is Latin (`U+006F`)
/// ```
///
/// [preview]: https://docs.astral.sh/ruff/preview/
#[violation]
pub struct AmbiguousUnicodeCharacterComment {
confusable: char,
@@ -159,11 +189,13 @@ pub(crate) fn ambiguous_unicode_character(
// Check if the boundary character is itself an ambiguous unicode character, in which
// case, it's always included as a diagnostic.
if !current_char.is_ascii() {
if let Some(representant) = confusable(current_char as u32) {
if let Some(representant) = confusable(current_char as u32)
.filter(|representant| settings.preview.is_enabled() || representant.is_ascii())
{
let candidate = Candidate::new(
TextSize::try_from(relative_offset).unwrap() + range.start(),
current_char,
representant as char,
representant,
);
if let Some(diagnostic) = candidate.into_diagnostic(context, settings) {
diagnostics.push(diagnostic);
@@ -173,12 +205,14 @@ pub(crate) fn ambiguous_unicode_character(
} else if current_char.is_ascii() {
// The current word contains at least one ASCII character.
word_flags |= WordFlags::ASCII;
} else if let Some(representant) = confusable(current_char as u32) {
} else if let Some(representant) = confusable(current_char as u32)
.filter(|representant| settings.preview.is_enabled() || representant.is_ascii())
{
// The current word contains an ambiguous unicode character.
word_candidates.push(Candidate::new(
TextSize::try_from(relative_offset).unwrap() + range.start(),
current_char,
representant as char,
representant,
));
} else {
// The current word contains at least one unambiguous unicode character.

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,7 @@ use crate::importer::ImportRequest;
/// lists:
///
/// - `functools.reduce(operator.iconcat, lists, [])`
/// - `list(itertools.chain.from_iterable(lists)`
/// - `list(itertools.chain.from_iterable(lists))`
/// - `[item for sublist in lists for item in sublist]`
///
/// ## Example

View File

@@ -20,15 +20,6 @@ use crate::fix::snippet::SourceCodeSnippet;
/// element of the collection, you can use `next(...)` or `next(iter(...)` to
/// lazily fetch the first element.
///
/// Note that migrating from `list(...)[0]` to `next(iter(...))` can change
/// the behavior of your program in two ways:
///
/// 1. First, `list(...)` will eagerly evaluate the entire collection, while
/// `next(iter(...))` will only evaluate the first element. As such, any
/// side effects that occur during iteration will be delayed.
/// 2. Second, `list(...)[0]` will raise `IndexError` if the collection is
/// empty, while `next(iter(...))` will raise `StopIteration`.
///
/// ## Example
/// ```python
/// head = list(x)[0]
@@ -41,6 +32,16 @@ use crate::fix::snippet::SourceCodeSnippet;
/// head = next(x * x for x in range(10))
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as migrating from `list(...)[0]` to
/// `next(iter(...))` can change the behavior of your program in two ways:
///
/// 1. First, `list(...)` will eagerly evaluate the entire collection, while
/// `next(iter(...))` will only evaluate the first element. As such, any
/// side effects that occur during iteration will be delayed.
/// 2. Second, `list(...)[0]` will raise `IndexError` if the collection is
/// empty, while `next(iter(...))` will raise `StopIteration`.
///
/// ## References
/// - [Iterators and Iterables in Python: Run Efficient Iterations](https://realpython.com/python-iterators-iterables/#when-to-use-an-iterator-in-python)
#[violation]

View File

@@ -0,0 +1,164 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---
confusables.py:1:6: RUF001 String contains ambiguous `𝐁` (MATHEMATICAL BOLD CAPITAL B). Did you mean `B` (LATIN CAPITAL LETTER B)?
|
1 | x = "𝐁ad string"
| ^ RUF001
2 | y = ""
|
confusables.py:6:56: RUF002 Docstring contains ambiguous `` (FULLWIDTH RIGHT PARENTHESIS). Did you mean `)` (RIGHT PARENTHESIS)?
|
5 | def f():
6 | """Here's a docstring with an unusual parenthesis: """
| ^^ RUF002
7 | # And here's a comment with an unusual punctuation mark:
8 | ...
|
confusables.py:7:62: RUF003 Comment contains ambiguous `` (PHILIPPINE SINGLE PUNCTUATION). Did you mean `/` (SOLIDUS)?
|
5 | def f():
6 | """Here's a docstring with an unusual parenthesis: """
7 | # And here's a comment with an unusual punctuation mark:
| ^ RUF003
8 | ...
|
confusables.py:17:6: RUF001 String contains ambiguous `𝐁` (MATHEMATICAL BOLD CAPITAL B). Did you mean `B` (LATIN CAPITAL LETTER B)?
|
17 | x = "𝐁ad string"
| ^ RUF001
18 | x = ""
|
confusables.py:26:10: RUF001 String contains ambiguous `α` (GREEK SMALL LETTER ALPHA). Did you mean `a` (LATIN SMALL LETTER A)?
|
24 | # The first word should be ignored, while the second should be included, since it
25 | # contains ASCII.
26 | x = "βα Bαd"
| ^ RUF001
27 |
28 | # The two characters should be flagged here. The first character is a "word"
|
confusables.py:31:6: RUF001 String contains ambiguous `Р` (CYRILLIC CAPITAL LETTER ER). Did you mean `P` (LATIN CAPITAL LETTER P)?
|
29 | # consisting of a single ambiguous character, while the second character is a "word
30 | # boundary" (whitespace) that it itself ambiguous.
31 | x = "Р усский"
| ^ RUF001
32 |
33 | # Same test cases as above but using f-strings instead:
|
confusables.py:31:7: RUF001 String contains ambiguous ` ` (EN QUAD). Did you mean ` ` (SPACE)?
|
29 | # consisting of a single ambiguous character, while the second character is a "word
30 | # boundary" (whitespace) that it itself ambiguous.
31 | x = "Р усский"
| ^ RUF001
32 |
33 | # Same test cases as above but using f-strings instead:
|
confusables.py:34:7: RUF001 String contains ambiguous `𝐁` (MATHEMATICAL BOLD CAPITAL B). Did you mean `B` (LATIN CAPITAL LETTER B)?
|
33 | # Same test cases as above but using f-strings instead:
34 | x = f"𝐁ad string"
| ^ RUF001
35 | x = f""
36 | x = f"Русский"
|
confusables.py:37:11: RUF001 String contains ambiguous `α` (GREEK SMALL LETTER ALPHA). Did you mean `a` (LATIN SMALL LETTER A)?
|
35 | x = f""
36 | x = f"Русский"
37 | x = f"βα Bαd"
| ^ RUF001
38 | x = f"Р усский"
|
confusables.py:38:7: RUF001 String contains ambiguous `Р` (CYRILLIC CAPITAL LETTER ER). Did you mean `P` (LATIN CAPITAL LETTER P)?
|
36 | x = f"Русский"
37 | x = f"βα Bαd"
38 | x = f"Р усский"
| ^ RUF001
39 |
40 | # Nested f-strings
|
confusables.py:38:8: RUF001 String contains ambiguous ` ` (EN QUAD). Did you mean ` ` (SPACE)?
|
36 | x = f"Русский"
37 | x = f"βα Bαd"
38 | x = f"Р усский"
| ^ RUF001
39 |
40 | # Nested f-strings
|
confusables.py:41:7: RUF001 String contains ambiguous `𝐁` (MATHEMATICAL BOLD CAPITAL B). Did you mean `B` (LATIN CAPITAL LETTER B)?
|
40 | # Nested f-strings
41 | x = f"𝐁ad string {f" {f"Р усский"}"}"
| ^ RUF001
42 |
43 | # Comments inside f-strings
|
confusables.py:41:21: RUF001 String contains ambiguous ` ` (EN QUAD). Did you mean ` ` (SPACE)?
|
40 | # Nested f-strings
41 | x = f"𝐁ad string {f" {f"Р усский"}"}"
| ^ RUF001
42 |
43 | # Comments inside f-strings
|
confusables.py:41:25: RUF001 String contains ambiguous `Р` (CYRILLIC CAPITAL LETTER ER). Did you mean `P` (LATIN CAPITAL LETTER P)?
|
40 | # Nested f-strings
41 | x = f"𝐁ad string {f" {f"Р усский"}"}"
| ^ RUF001
42 |
43 | # Comments inside f-strings
|
confusables.py:41:26: RUF001 String contains ambiguous ` ` (EN QUAD). Did you mean ` ` (SPACE)?
|
40 | # Nested f-strings
41 | x = f"𝐁ad string {f" {f"Р усский"}"}"
| ^ RUF001
42 |
43 | # Comments inside f-strings
|
confusables.py:44:68: RUF003 Comment contains ambiguous `` (FULLWIDTH RIGHT PARENTHESIS). Did you mean `)` (RIGHT PARENTHESIS)?
|
43 | # Comments inside f-strings
44 | x = f"string { # And here's a comment with an unusual parenthesis:
| ^^ RUF003
45 | # And here's a comment with a greek alpha:
46 | foo # And here's a comment with an unusual punctuation mark:
|
confusables.py:46:62: RUF003 Comment contains ambiguous `` (PHILIPPINE SINGLE PUNCTUATION). Did you mean `/` (SOLIDUS)?
|
44 | x = f"string { # And here's a comment with an unusual parenthesis:
45 | # And here's a comment with a greek alpha:
46 | foo # And here's a comment with an unusual punctuation mark:
| ^ RUF003
47 | }"
|
confusables.py:55:28: RUF001 String contains ambiguous `µ` (MICRO SIGN). Did you mean `μ` (GREEK SMALL LETTER MU)?
|
55 | assert getattr(Labware(), "µL") == 1.5
| ^ RUF001
|

View File

@@ -31,6 +31,10 @@ use crate::checkers::ast::Checker;
/// except ValueError:
/// raise
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it doesn't properly handle bound
/// exceptions that are shadowed between the `except` and `raise` statements.
#[violation]
pub struct VerboseRaise;

View File

@@ -10,6 +10,7 @@ use ruff_text_size::{Ranged, TextRange};
use crate::call_path::CallPath;
use crate::parenthesize::parenthesized_range;
use crate::statement_visitor::{walk_body, walk_stmt, StatementVisitor};
use crate::visitor::Visitor;
use crate::AnyNodeRef;
use crate::{
self as ast, Arguments, CmpOp, ExceptHandler, Expr, MatchCase, Pattern, Stmt, TypeParam,
@@ -931,6 +932,29 @@ where
}
}
/// A [`Visitor`] that detects the presence of `await` expressions in the current scope.
#[derive(Debug, Default)]
pub struct AwaitVisitor {
pub seen_await: bool,
}
impl Visitor<'_> for AwaitVisitor {
fn visit_stmt(&mut self, stmt: &Stmt) {
match stmt {
Stmt::FunctionDef(_) | Stmt::ClassDef(_) => (),
_ => crate::visitor::walk_stmt(self, stmt),
}
}
fn visit_expr(&mut self, expr: &Expr) {
if let Expr::Await(ast::ExprAwait { .. }) = expr {
self.seen_await = true;
} else {
crate::visitor::walk_expr(self, expr);
}
}
}
/// Return `true` if a `Stmt` is a docstring.
pub fn is_docstring_stmt(stmt: &Stmt) -> bool {
if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt {

View File

@@ -203,3 +203,28 @@ lambda: ( # comment
y:
z
)
lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), e=1, f=2, g=2: d
# Regression tests for https://github.com/astral-sh/ruff/issues/8179
def a():
return b(
c,
d,
e,
f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
*args, **kwargs
),
)
def a():
return b(
c,
d,
e,
f=lambda self, araa, kkkwargs,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
args,kwargs,
e=1, f=2, g=2: d,
g = 10
)

View File

@@ -0,0 +1,206 @@
comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top.
# 88 characters unparenthesized
____aaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c
# 88 characters
____aaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c
# 89 characters parenthesized (collapse)
____aaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c
## Parenthesized
# 88 characters unparenthesized
____aaa = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c
)
# 88 characters
____aaa = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c
)
# 89 characters parenthesized (collapse)
____aaa = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c
)
## Expression and statement comments
# 88 characters unparenthesized
____aaa = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb # c
) # d
# 88 characters
____aaa = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvv # c
) # d
# 89 characters parenthesized (collapse)
____aaa = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvv # c
) # d
## Strings
# 88 characters unparenthesized
____aaa = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv" # c
# 88 characters
____aaa = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv" # c
# 89 characters parenthesized (collapse)
____aaa = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv" # c
# Always parenthesize if implicit concatenated
____aaa = (
"aaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvv"
) # c
## Numbers
# 88 characters unparenthesized
____aaa = 1111111111111111111111111111111111111111111111111111111111111111111111111 # c
# 88 characters
____aaa = 1111111111111111111111111111111111111111111111111111111111111111111111111111111 # c
# 89 characters parenthesized (collapse)
____aaa = 11111111111111111111111111111111111111111111111111111111111111111111111111111111 # c
## Breaking left
# Should break `[a]` first
____[a] = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c
____[
a
] = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # cc
)
(
# some weird comments
____[aaaaaaaaa]
# some weird comments 2
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c
# Preserve trailing assignment comments when the expression has own line comments
____aaa = (
# leading
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv
# trailing
) # cc
def setUpTestData(cls):
cls.happening = (
Happening.objects.create()
) # make sure the defaults are working (#20158)
def setUpTestData(cls):
cls.happening = (
Happening.objects.create # make sure the defaults are working (#20158)
)
if True:
if True:
if True:
# Black layout
model.config.use_cache = (
False # FSTM still requires this hack -> FSTM should probably be refactored s
)
## Annotated Assign
# 88 characters unparenthesized
____a: a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c
# 88 characters
____a: a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c
# 89 characters parenthesized (collapse)
____a: a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c
# 88 characters unparenthesized
____a : a = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c
)
# 88 characters
____a: a = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c
)
# 89 characters parenthesized (collapse)
____a: a = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c
)
_a: a[b] = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c
)
## Augmented Assign
# 88 characters unparenthesized
____aa += aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c
# 88 characters
____aa += aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c
# 89 characters parenthesized (collapse)
____aa += aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c
# 88 characters unparenthesized
____aa += (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c
)
# 88 characters
____aa += (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c
)
# 89 characters parenthesized (collapse)
____aa += (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c
)
## Return
def test():
# 88 characters unparenthesized
return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c
def test2():
# 88 characters
return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvv # c
def test3():
# 89 characters parenthesized (collapse)
return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvv # c
## Return Parenthesized
def test4():
# 88 characters unparenthesized
return (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c
)
def test5():
# 88 characters
return (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvv # c
)
def test6():
# 89 characters parenthesized (collapse)
return (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvv # c
)

View File

@@ -22,8 +22,6 @@ def foo():
pass
(yield a, b) = (1, 2)
# some comment
for e in l : yield e # some comment

View File

@@ -20,7 +20,7 @@ impl FormatNodeRule<ExprAwait> for FormatExprAwait {
[
token("await"),
space(),
maybe_parenthesize_expression(value, item, Parenthesize::IfBreaks)
maybe_parenthesize_expression(value, item, Parenthesize::IfRequired)
]
)
}
@@ -39,6 +39,7 @@ impl NeedsParentheses for ExprAwait {
context.comments().ranges(),
context.source(),
) {
// Prefer splitting the value if it is parenthesized.
OptionalParentheses::Never
} else {
self.value.needs_parentheses(self.into(), context)

View File

@@ -59,7 +59,10 @@ impl NeedsParentheses for AnyExpressionYield<'_> {
OptionalParentheses::Never
} else {
// Ex) `x = yield f(1, 2, 3)`
value.needs_parentheses(self.into(), context)
match value.needs_parentheses(self.into(), context) {
OptionalParentheses::BestFit => OptionalParentheses::Never,
parentheses => parentheses,
}
}
} else {
// Ex) `x = yield`

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