Compare commits

..

98 Commits

Author SHA1 Message Date
Brent Westbrook
a1fdd66f10 Bump 0.13.0 (#20336) 2025-09-10 12:11:22 -04:00
Shunsuke Shibayama
8770b95509 [ty] introduce DivergentType (#20312)
## Summary

From #17371

In #17371, `DivergentType` was introduced to prevent type inference for
recursive functions from diverging and causing panics. This turned out
to be useful for other divergent type inferences
(https://github.com/astral-sh/ruff/pull/17371#discussion_r2329337965),
so I extracted the introduction part of `DivergentType` into this PR so
that we can use it without waiting for the merge of #17371.

Note that this PR only introduces `DivergentType` and does not actually
address divergent type inference yet. Please refer to
https://github.com/astral-sh/ruff/pull/17371/files#diff-20b910c6e20faa962bb1642e111db1cbad8e66ace089bdd966ac9d7f9fa99ff2R542-R622
etc. when implementing handling of divergence suppression using this
type.

## Test Plan

---------

Co-authored-by: Carl Meyer <carl@oddbird.net>
2025-09-10 08:32:26 -07:00
David Peter
65982a1e14 [ty] Use 'unknown' specialization for upper bound on Self (#20325)
## Summary

closes https://github.com/astral-sh/ty/issues/1156

## Test Plan

Added a regression test
2025-09-10 17:00:28 +02:00
David Peter
57d1f7132d [ty] Simplify unions of enum literals and subtypes thereof (#20324)
## Summary

When adding an enum literal `E = Literal[Color.RED]` to a union which
already contained a subtype of that enum literal(!), we were previously
not simplifying the union correctly. My assumption is that our property
tests didn't catch that earlier, because the only possible non-trivial
subytpe of an enum literal that I can think of is `Any & E`. And in
order for that to be detected by the property tests, it would have to
randomly generate `Any & E | E` and then also compare that with `E` on
the other side (in an equivalence test, or the subtyping-antisymmetry
test).

closes https://github.com/astral-sh/ty/issues/1155

## Test Plan

* Added a regression test.
* I also ran the property tests for a while, but probably not for two
months worth of daily CI runs.
2025-09-10 15:54:06 +02:00
Loïc Riegel
7a75702237 Ignore deprecated rules unless selected by exact code (#20167)
<!--
Thank you for contributing to Ruff/ty! 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? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

Closes #18349

After this change:
- All deprecated rules are deselected by default
- They are only selected if the user specifically selects them by code,
e.g. `--select UP038`
- Thus, `--select ALL --select UP --select UP0` won't select the
deprecated rule UP038
- Documented the change in version policy. From now on, deprecating a
rule should increase the minor version

## Test Plan

Integration tests in "integration_tests.rs"

Also tested with a temporary test package:
```
~> ../../ruff/target/debug/ruff.exe check --select UP038
warning: Rule `UP038` is deprecated and will be removed in a future release.
warning: Detected debug build without --no-cache.
UP038 Use `X | Y` in `isinstance` call instead of `(X, Y)`
 --> main.py:2:11
  |
1 | def main():
2 |     print(isinstance(25, (str, int)))
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
help: Convert to `X | Y`

Found 1 error.
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).

~> ../../ruff/target/debug/ruff.exe check --select UP03
warning: Detected debug build without --no-cache.
All checks passed!

~> ../../ruff/target/debug/ruff.exe check --select UP0
warning: Detected debug build without --no-cache.
All checks passed!

~> ../../ruff/target/debug/ruff.exe check --select UP
warning: Detected debug build without --no-cache.
All checks passed!

~> ../../ruff/target/debug/ruff.exe check --select ALL
# warnings and errors, but because of other errors, UP038 was deselected
```
2025-09-10 09:00:27 -04:00
Dylan
9ca632c84f Stabilize adding future import via config option (#20277)
Introduced in #19100. Removed gating, updated tests, removed warning(s),
and updated documentation.
2025-09-10 09:00:27 -04:00
Dylan
64fe7d30a3 [flake8-errmsg] Stabilize extending raw-string-in-exception (EM101) to support byte strings (#20273)
Introduced in #18867. Removed gating, updated tests, updated docs.
2025-09-10 09:00:27 -04:00
Brent Westbrook
beeeb8d5c5 Stabilize the remaining Airflow rules (#20250)
- **Stabilize `airflow3-suggested-update` (`AIR311`)**
- **Stabilize `airflow3-suggested-to-move-to-provider` (`AIR312`)**
- **Stabilize `airflow3-removal` (`AIR301`)**
- **Stabilize `airflow3-moved-to-provider` (`AIR302`)**
- **Stabilize `airflow-dag-no-schedule-argument` (`AIR002`)**

I put this all in one PR to make it easier to double check with @Lee-W
before we merge this. I also made a few minor documentation changes and
updated one error message that I want to make sure are okay. But for the
most part this just moves the rules from `RuleGroup::Preview` to
`RuleGroup::Stable`!

Fixes #17749
2025-09-10 09:00:27 -04:00
Dylan
b6fca52855 [flake8-bugbear] Stabilize support for non-context-manager calls in assert-raises-exception (B017) (#20274)
Introduced in #19063. Removed gating, updated tests. Not documented so
docs are the same.
2025-09-10 09:00:27 -04:00
Dylan
ac7f882c78 [flake8-commas] Stabilize support for trailing comma checks in type parameter lists (COM812, COM819) (#20275)
Introduced in #19390. Removed gating, updated tests. No documentation to
update.
2025-09-10 09:00:27 -04:00
Dylan
aef0a107a8 [pygrep_hooks] Stabilize usingAsyncMock methods in invalid-mock-access (PGH005) (#20272)
Introduced in #18547. Removed gating, updated tests. Not documented so
documentation is the same.
2025-09-10 09:00:27 -04:00
Dylan
512395f4e6 Stabilize new strategy for classifying imports as first party (#20268)
This stabilizes the behavior introduced in #16565 which (roughly) tries
to match an import like `import a.b.c` to an actual directory path
`a/b/c` in order to label it as first-party, rather than simply looking
for a directory `a`.

Mainly this affects the sorting of imports in the presence of namespace
packages, but a few other rules are affected as well.
2025-09-10 09:00:27 -04:00
Dylan
5dec37fbaf [pylint] Stabilize ignoring __init__.py for useless-import-alias (PLC0414) (#20271)
Stabilizes change from #18400. Removed gating, updated docs, updated
tests.
2025-09-10 09:00:27 -04:00
Dylan
4bda9dad68 [pylint] Stabilize adding U+061C to bidirectional-unicode (PLE2502) (#20276)
Introduced in #20106. Removed gating. Updated tests. No documentation to
update.
2025-09-10 09:00:27 -04:00
Dylan
9d972d0583 [flake8-simplify] Stabilize fix safety of multiple-with-statements (SIM117) (#20270)
Introduced in #18208. Removed gating, updated tests and docs.
2025-09-10 09:00:27 -04:00
Brent Westbrook
1bbb553d6f Stabilize pytest-raises-ambiguous-pattern (RUF043) (#20253)
This one has been a bit contentious in the past. It usually uncovers
~700 ecosystem hits. See:

- https://github.com/astral-sh/ruff/pull/16657
- https://github.com/astral-sh/ruff/issues/16690

But I think there's consensus that it's okay to merge as-is. We'd love
an
autofix since it's so common, but we can't reliably tell what a user
meant. The
pattern is ambiguous after all 😆

This is the first rule that actually needed its test case relocated, but
the
docs looked good.
2025-09-10 09:00:27 -04:00
Brent Westbrook
bb4c51afb2 Stabilize f-string-number-format (FURB116) (#20247)
Tests and docs look good
2025-09-10 09:00:27 -04:00
हिमांशु
3dbdd2b883 [pyupgrade] Remove non-pep604-isinstance (UP038) (#19156)
## Summary
This PR Removes deprecated UP038 as per instructed in #18727 
closes #18727 
## Test Plan
I have run tests non of them failing 

One Question i have is do we have to document that UP038 is removed?

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-09-10 09:00:27 -04:00
हिमांशु
d8e43bf9f7 [pandas-vet] Remove pandas-df-variable-name (PD901) (#19223)
## Summary
closes #7710 

## Test Plan

It is is removal so i don't think we have to add tests otherwise i have
followed test plan mentioned in contributing.md

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-09-10 09:00:27 -04:00
हिमांशु
ee448eab2d Remove deprecated macOS config file discovery (#19210)
## Summary
In #11115 we moved from defaulting to $HOME/Library/Application Support
to $XDG_HOME on macOS and added a deprecation warning if we find files
in the old location. So this PR removes the warning
closes #19145 

## Test Plan
Ran `cargo test`
2025-09-10 09:00:27 -04:00
Brent Westbrook
307b7df027 Stabilize redundant-none-literal (PYI061) (#20236)
Tests and docs look good
2025-09-10 09:00:27 -04:00
Brent Westbrook
e139104aba Stabilize generic-not-last-base-class (PYI059) (#20246)
Tests and docs look good

We nearly stabilized this last time
(https://github.com/astral-sh/ruff/pull/18601), but it needed one more
bug fix and a documentation improvement
(https://github.com/astral-sh/ruff/pull/18611)
2025-09-10 09:00:27 -04:00
Brent Westbrook
9bb9b54168 Stabilize useless-class-metaclass-type (UP050) (#20230)
Tests and docs look good
2025-09-10 09:00:27 -04:00
Brent Westbrook
262f2767ca Stabilize os-symlink (PTH211) (#20229)
Summary
--

Rule and test/snapshot updated, the docs look good

My one hesitation here is that we could hold off stabilizing the rule
until its fix is also ready for stabilization, but this is also the only
preview PTH rule, so I think it's okay to stabilize the rule and later
(probably in the next minor release) stabilize the fixes together.
2025-09-10 09:00:27 -04:00
Brent Westbrook
1de9dac9d5 Stabilize unused-unpacked-variable (RUF059) (#20233)
The tests looked good. For the docs, I added a `## See also` section
pointing to
the closely-related F841 (unused-variable) and the corresponding section
to F841
pointing back to RUF059. It seems like you'd probably want both of these
active
or at least to know about the other when reading the docs.
2025-09-10 09:00:27 -04:00
Brent Westbrook
1cf6c2439f Stabilize long-sleep-not-forever (ASYNC116) (#20244)
Tests and docs look good
2025-09-10 09:00:27 -04:00
David Peter
2b51ec6531 [ty] Improve specialization-error diagnostics (#20326)
## Summary

Add information about the upper bound or the constraints of the type
variable to the `SpecializationError` diagnostics.
2025-09-10 14:01:23 +02:00
Alex Waygood
b85c995927 [ty] "foo".startswith is not an instance of types.MethodWrapperType (#20317) 2025-09-10 11:14:26 +00:00
Alex Waygood
fd7eb1e22f [ty] Allow protocols to participate in nominal subtyping as well as structural subtyping (#20314) 2025-09-10 11:05:50 +00:00
Alex Waygood
4de7d653bd [ty] Treat Hashable, and similar protocols, equivalently to object for subtyping/assignability (#20284) 2025-09-10 11:38:58 +01:00
Brent Westbrook
9cb37db510 Bump LibCST to 1.8.4 (#20321)
This should fix the fuzz build on `main`. They added support for
t-strings, which made one of our matches non-exhaustive.

https://github.com/Instagram/LibCST/releases/tag/v1.8.4
2025-09-09 17:34:33 -04:00
Douglas Creager
ed06fb5ce2 [ty] Use partial-order-friendly representation of typevar constraints (#20306)
The constraint representation that we added in #19997 was subtly wrong,
in that it didn't correctly model that type assignability is a _partial_
order — it's possible for two types to be incomparable, with neither a
subtype of the other. That means the negation of a constraint like `T ≤
t` (typevar `T` must be a subtype of `t`) is **_not_** `t < T`, but
rather `t < T ∨ T ≁ t` (using ≁ to mean "not comparable to").

That means we need to update our constraint representation to be an
enum, so that we can track both _range_ constraints (upper/lower bound
on the typevar), and these new _incomparable_ constraints.

Since we need an enum now, that also lets us simplify how we were
modeling range constraints. Before, we let the lower/upper bounds be
either open (<) or closed (≤). Now, range constraints are always closed,
and we add a third kind of constraint for _not equivalent_ (≠). We can
translate an open upper bound `T < t` into `T ≤ t ∧ T ≠ t`.

We already had the logic for doing adding _clauses_ to a _set_ by doing
a pairwise simplification. We copy that over to where we add
_constraints_ to a _clause_. To calculate the intersection or union of
two constraints, the new enum representation makes it easy to break down
all of the possibilities into a small number of cases: intersect range
with range, intersect range with not-equivalent, etc. I've done the math
[here](https://dcreager.net/theory/constraints/) to show that the
simplifications for each of these cases is correct.
2025-09-09 15:54:47 -04:00
Igor Drokin
54df73c9f7 [pyupgrade] Apply UP008 only when the __class__ cell exists (#19424)
## Summary

Resolves #19357 

Skip UP008 diagnostic for `builtins.super(P, self)` calls when
`__class__` is not referenced locally, preventing incorrect fixes.

**Note:** I haven't found concrete information about which cases
`__class__` will be loaded into the scope. Let me know if anyone has
references, it would be useful to enhance the implementation. I did a
lot of tests to determine when `__class__` is loaded. Considered
sources:
1. [Python doc
super](https://docs.python.org/3/library/functions.html#super)
2. [Python doc classes](https://docs.python.org/3/tutorial/classes.html)
3. [pep-3135](https://peps.python.org/pep-3135/#specification)

As I understand it, Python will inject at runtime into local scope a
`__class__` variable if it detects references to `super` or `__class__`.
This allows calling `super()` and passing appropriate parameters.
However, the compiler doesn't do the same for `builtins.super`, so we
need to somehow introduce `__class__` into the local scope.

I figured out `__class__` will be in scope with valid value when two
conditions are met:
1. `super` or `__class__` names have been loaded within function scope
4. `__class__` is not overridden.

I think my solution isn't elegant, so I would be appreciate a detailed
review.

## Test Plan

Added 19 test cases, updated snapshots.

---------

Co-authored-by: Igor Drokin <drokinii1017@gmail.com>
2025-09-09 14:59:23 -04:00
Amethyst Reese
d7524ea6d4 Refactor diagnostic start|end location helpers (#20309)
- Renames functions to drop `expect_` from names.
- Make functions return `Option<LineColumn>` to appropriately signal
  when range is not available.
- Update existing consumers to use `unwrap_or_default()`. Uncertain if
  there are better fallback behaviors for individual consumers.
2025-09-09 11:39:31 -07:00
Alex Waygood
bf66178959 [ty] Add tests for protocols with generic method members (#20316) 2025-09-09 16:44:00 +00:00
Zanie Blue
9cdac2d6fb Add support for using uv as an alternative formatter backend (#19665)
This adds a new `backend: internal | uv` option to the LSP
`FormatOptions` allowing users to perform document and range formatting
operations though uv. The idea here is to prototype a solution for users
to transition to a `uv format` command without encountering version
mismatches (and consequently, formatting differences) between the LSP's
version of `ruff` and uv's version of `ruff`.

The primarily alternative to this would be to use uv to discover the
`ruff` version used to start the LSP in the first place. However, this
would increase the scope of a minimal `uv format` command beyond "run a
formatter", and raise larger questions about how uv should be used to
coordinate toolchain discovery. I think those are good things to
explore, but I'm hesitant to let them block a `uv format`
implementation. Another downside of using uv to discover `ruff`, is that
it needs to be implemented _outside_ the LSP; e.g., we'd need to change
the instructions on how to run the LSP and implement it in each editor
integration, like the VS Code plugin.

---------

Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2025-09-09 20:39:53 +05:30
Igor Drokin
79706a2e26 [pyupgrade] Enable rule triggering for stub files (UP043) (#20027)
## Summary
Resolves #20011

Implemented alternative triggering condition for rule
[`UP043`](https://docs.astral.sh/ruff/rules/unnecessary-default-type-args/)
based on requirements outlined in [issue
#20011](https://github.com/astral-sh/ruff/issues/20011)
## Test Plan
Created .pyi file to ensure triggering the rule

---------

Co-authored-by: Igor Drokin <drokinii1017@gmail.com>
Co-authored-by: dylwil3 <dylwil3@gmail.com>
2025-09-09 12:57:26 +00:00
Andrew Gallant
25853e2377 Allow the if_not_else Clippy lint
Specifically, the [`if_not_else`] lint will sometimes flag
code to change the order of `if` and `else` bodies if this
would allow a `!` to be removed. While perhaps tasteful in
some cases, there are many cases in my experience where this
bows to other competing concerns that impact readability.
(Such as the relative sizes of the `if` and `else` bodies,
or perhaps an ordering that just makes the code flow in a
more natural way.)

[`if_not_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#/if_not_else
2025-09-09 08:49:25 -04:00
Renkai Ge
61f906d8e7 [ty] equality narrowing on enums that don't override __eq__ or __ne__ (#20285)
Add equality narrowing for enums, if they don't override `__eq__` or `__ne__` in an unsafe way.

Follow-up to PR https://github.com/astral-sh/ruff/pull/20164

Fixes https://github.com/astral-sh/ty/issues/939
2025-09-08 16:56:28 -07:00
Shunsuke Shibayama
08a561fc05 [ty] more precise lazy scope place lookup (#19932)
## Summary

This is a follow-up to https://github.com/astral-sh/ruff/pull/19321.

Now lazy snapshots are updated to take into account new bindings on
every symbol reassignment.

```python
def outer(x: A | None):
    if x is None:
        x = A()

    reveal_type(x)  # revealed: A

    def inner() -> None:
        # lazy snapshot: {x: A}
        reveal_type(x)  # revealed: A
    inner()

def outer() -> None:
    x = None

    x = 1

    def inner() -> None:
        # lazy snapshot: {x: Literal[1]} -> {x: Literal[1, 2]}
        reveal_type(x)  # revealed: Literal[1, 2]
    inner()

    x = 2
```

Closes astral-sh/ty#559.

## Test Plan

Some TODOs in `public_types.md` now work properly.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-09-08 21:08:35 +00:00
Ibraheem Ahmed
aa5d665d52 [ty] Add support for generic PEP695 type aliases (#20219)
## Summary

Adds support for generic PEP695 type aliases, e.g.,
```python
type A[T] = T
reveal_type(A[int]) # A[int]
```

Resolves https://github.com/astral-sh/ty/issues/677.
2025-09-08 13:26:21 -07:00
David Peter
d55edb3d74 [ty] Support "legacy" typing.Self in combination with PEP 695 generic contexts (#20304)
## Summary

Support cases like the following, where we need the generic context to
include both `Self` and `T` (not just `T`):

```py
from typing import Self

class C:
    def method[T](self: Self, arg: T): ...

C().method(1)
```

closes https://github.com/astral-sh/ty/issues/1131

## Test Plan

Added regression test
2025-09-08 16:57:09 +02:00
arielle
ab86ae1760 [pep8-naming] Fix formatting of __all__ (N816) (#20301)
<!--
Thank you for contributing to Ruff/ty! 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? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

Noticed this was not escaped when writing a project that parses the
result of `ruff rule --outputformat json`. This is visible here:
<https://docs.astral.sh/ruff/rules/mixed-case-variable-in-global-scope/#why-is-this-bad>

## Test Plan

documentation only

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-09-08 14:40:38 +00:00
David Peter
916968d0ff [ty] Fix signature of NamedTupleLike._make (#20302) 2025-09-08 14:53:17 +02:00
Alex Waygood
deb3d3d150 [ty] Fall back to object for attribute access on synthesized protocols (#20286) 2025-09-08 13:04:37 +01:00
Frank Dana
982a0a2a7c CI: Eliminate warning in fuzz build workflow (#20290)
## Summary

Pr #11919 changed the fuzz build from `taiki-e/install-action` to
`cargo-bins/cargo-binstall` for necessary reasons of version selection.
But it left the `with:` parameter, which the `binstall` action does not
support. As a result, all workflow runs are showing a warning:
> Unexpected input(s) `'tool'`, valid inputs are `['']`

Eliminate the warning by removing the `with` parameter.

## Test Plan

Run CI, determine that the "cargo fuzz build" step no longer includes an
Annotation showing the warning message (quoted above).
2025-09-08 11:33:38 +05:30
renovate[bot]
f893b19930 Update dependency ruff to v0.12.12 (#20293)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [ruff](https://docs.astral.sh/ruff)
([source](https://redirect.github.com/astral-sh/ruff),
[changelog](https://redirect.github.com/astral-sh/ruff/blob/main/CHANGELOG.md))
| `==0.12.11` -> `==0.12.12` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/ruff/0.12.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/ruff/0.12.11/0.12.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>astral-sh/ruff (ruff)</summary>

###
[`v0.12.12`](https://redirect.github.com/astral-sh/ruff/blob/HEAD/CHANGELOG.md#01212)

[Compare
Source](https://redirect.github.com/astral-sh/ruff/compare/0.12.11...0.12.12)

##### Preview features

- Show fixes by default
([#&#8203;19919](https://redirect.github.com/astral-sh/ruff/pull/19919))
- \[`airflow`] Convert `DatasetOrTimeSchedule(datasets=...)` to
`AssetOrTimeSchedule(assets=...)` (`AIR311`)
([#&#8203;20202](https://redirect.github.com/astral-sh/ruff/pull/20202))
- \[`airflow`] Improve the `AIR002` error message
([#&#8203;20173](https://redirect.github.com/astral-sh/ruff/pull/20173))
- \[`airflow`] Move `airflow.operators.postgres_operator.Mapping` from
`AIR302` to `AIR301`
([#&#8203;20172](https://redirect.github.com/astral-sh/ruff/pull/20172))
- \[`flake8-async`] Implement `blocking-input` rule (`ASYNC250`)
([#&#8203;20122](https://redirect.github.com/astral-sh/ruff/pull/20122))
- \[`flake8-use-pathlib`] Make `PTH119` and `PTH120` fixes unsafe
because they can change behavior
([#&#8203;20118](https://redirect.github.com/astral-sh/ruff/pull/20118))
- \[`pylint`] Add U+061C to `PLE2502`
([#&#8203;20106](https://redirect.github.com/astral-sh/ruff/pull/20106))
- \[`ruff`] Fix false negative for empty f-strings in `deque` calls
(`RUF037`)
([#&#8203;20109](https://redirect.github.com/astral-sh/ruff/pull/20109))

##### Bug fixes

- Less confidently mark f-strings as empty when inferring truthiness
([#&#8203;20152](https://redirect.github.com/astral-sh/ruff/pull/20152))
- \[`fastapi`] Fix false positive for paths with spaces around
parameters (`FAST003`)
([#&#8203;20077](https://redirect.github.com/astral-sh/ruff/pull/20077))
- \[`flake8-comprehensions`] Skip `C417` when lambda contains
`yield`/`yield from`
([#&#8203;20201](https://redirect.github.com/astral-sh/ruff/pull/20201))
- \[`perflint`] Handle tuples in dictionary comprehensions (`PERF403`)
([#&#8203;19934](https://redirect.github.com/astral-sh/ruff/pull/19934))

##### Rule changes

- \[`pycodestyle`] Preserve return type annotation for `ParamSpec`
(`E731`)
([#&#8203;20108](https://redirect.github.com/astral-sh/ruff/pull/20108))

##### Documentation

- Add fix safety sections to docs
([#&#8203;17490](https://redirect.github.com/astral-sh/ruff/pull/17490),[#&#8203;17499](https://redirect.github.com/astral-sh/ruff/pull/17499))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 11:03:19 +05:30
renovate[bot]
c96ebe3936 Update cargo-bins/cargo-binstall action to v1.15.4 (#20292)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[cargo-bins/cargo-binstall](https://redirect.github.com/cargo-bins/cargo-binstall)
| action | patch | `v1.15.3` -> `v1.15.4` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>cargo-bins/cargo-binstall (cargo-bins/cargo-binstall)</summary>

###
[`v1.15.4`](https://redirect.github.com/cargo-bins/cargo-binstall/releases/tag/v1.15.4)

[Compare
Source](https://redirect.github.com/cargo-bins/cargo-binstall/compare/v1.15.3...v1.15.4)

*Binstall is a tool to fetch and install Rust-based executables as
binaries. It aims to be a drop-in replacement for `cargo install` in
most cases. Install it today with `cargo install cargo-binstall`, from
the binaries below, or if you already have it, upgrade with `cargo
binstall cargo-binstall`.*

##### In this release:

- clarify `--install-path` behavior when installing from source
([#&#8203;2259](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2259)
[#&#8203;2294](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2294))

##### Other changes:

- bump dependencies
([#&#8203;2295](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2295)
[#&#8203;2298](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2298)
[#&#8203;2299](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2299))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 11:03:07 +05:30
renovate[bot]
22ca5dd890 Update dependency mdformat-mkdocs to v4.4.1 (#20299)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[mdformat-mkdocs](https://redirect.github.com/kyleking/mdformat-mkdocs)
([changelog](https://redirect.github.com/kyleking/mdformat-mkdocs/releases))
| `==4.3.0` -> `==4.4.1` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/mdformat-mkdocs/4.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/mdformat-mkdocs/4.3.0/4.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>kyleking/mdformat-mkdocs (mdformat-mkdocs)</summary>

###
[`v4.4.1`](https://redirect.github.com/KyleKing/mdformat-mkdocs/releases/tag/v4.4.1)

[Compare
Source](https://redirect.github.com/kyleking/mdformat-mkdocs/compare/v4.4.0...v4.4.1)

##### What's Changed

-
fix([#&#8203;56](https://redirect.github.com/kyleking/mdformat-mkdocs/issues/56)):
narrowly scope escape\_deflist by
[@&#8203;KyleKing](https://redirect.github.com/KyleKing) in
[KyleKing#57](https://redirect.github.com/KyleKing/mdformat-mkdocs/pull/57)

**Full Changelog**:
<https://github.com/KyleKing/mdformat-mkdocs/compare/v4.4.0...v4.4.1>

###
[`v4.4.0`](https://redirect.github.com/KyleKing/mdformat-mkdocs/releases/tag/v4.4.0)

[Compare
Source](https://redirect.github.com/kyleking/mdformat-mkdocs/compare/v4.3.0...v4.4.0)

##### What's Changed

-
fix([#&#8203;54](https://redirect.github.com/kyleking/mdformat-mkdocs/issues/54)):
add 4-space indented deflists by
[@&#8203;KyleKing](https://redirect.github.com/KyleKing) in
[KyleKing#55](https://redirect.github.com/KyleKing/mdformat-mkdocs/pull/55)

**Full Changelog**:
<https://github.com/KyleKing/mdformat-mkdocs/compare/v4.3.0...v4.4.0>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 11:02:55 +05:30
renovate[bot]
f7995f4aef Update astral-sh/setup-uv action to v6.6.1 (#20291)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [astral-sh/setup-uv](https://redirect.github.com/astral-sh/setup-uv) |
action | patch | `v6.6.0` -> `v6.6.1` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>astral-sh/setup-uv (astral-sh/setup-uv)</summary>

###
[`v6.6.1`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v6.6.1):
🌈 Fix exclusions in cache-dependency-glob

[Compare
Source](https://redirect.github.com/astral-sh/setup-uv/compare/v6.6.0...v6.6.1)

##### Changes

Exclusions with a leading `!` in the
[cache-dependency-glob](https://redirect.github.com/astral-sh/setup-uv?tab=readme-ov-file#cache-dependency-glob)
did not work and got fixed with this release. Thank you
[@&#8203;KnisterPeter](https://redirect.github.com/KnisterPeter) for
raising this!

##### 🐛 Bug fixes

- Fix exclusions in cache-dependency-glob
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;546](https://redirect.github.com/astral-sh/setup-uv/issues/546))

##### 🧰 Maintenance

- Bump dependencies
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;547](https://redirect.github.com/astral-sh/setup-uv/issues/547))
- chore: update known versions for 0.8.14
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;543](https://redirect.github.com/astral-sh/setup-uv/issues/543))
- chore: update known versions for 0.8.13
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;536](https://redirect.github.com/astral-sh/setup-uv/issues/536))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 11:00:16 +05:30
renovate[bot]
480fb278d0 Update Rust crate bitflags to v2.9.4 (#20294)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [bitflags](https://redirect.github.com/bitflags/bitflags) |
workspace.dependencies | patch | `2.9.3` -> `2.9.4` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>bitflags/bitflags (bitflags)</summary>

###
[`v2.9.4`](https://redirect.github.com/bitflags/bitflags/blob/HEAD/CHANGELOG.md#294)

[Compare
Source](https://redirect.github.com/bitflags/bitflags/compare/2.9.3...2.9.4)

#### What's Changed

- Add Cargo features to readme by
[@&#8203;KodrAus](https://redirect.github.com/KodrAus) in
[#&#8203;460](https://redirect.github.com/bitflags/bitflags/pull/460)

**Full Changelog**:
<https://github.com/bitflags/bitflags/compare/2.9.3...2.9.4>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 10:59:59 +05:30
renovate[bot]
b2f364d9cb Update Rust crate clap to v4.5.47 (#20295)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [clap](https://redirect.github.com/clap-rs/clap) |
workspace.dependencies | patch | `4.5.46` -> `4.5.47` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>clap-rs/clap (clap)</summary>

###
[`v4.5.47`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#4547---2025-09-02)

[Compare
Source](https://redirect.github.com/clap-rs/clap/compare/v4.5.46...v4.5.47)

##### Features

- Added `impl FromArgMatches for ()`
- Added `impl Args for ()`
- Added `impl Subcommand for ()`
- Added `impl FromArgMatches for Infallible`
- Added `impl Subcommand for Infallible`

##### Fixes

- *(derive)* Update runtime error text to match `clap`

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 10:59:47 +05:30
renovate[bot]
aa82137d9f Update Rust crate wasm-bindgen-test to v0.3.51 (#20298)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[wasm-bindgen-test](https://redirect.github.com/wasm-bindgen/wasm-bindgen)
| workspace.dependencies | patch | `0.3.50` -> `0.3.51` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 10:59:29 +05:30
renovate[bot]
adfe2438e9 Update Rust crate log to v0.4.28 (#20297)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [log](https://redirect.github.com/rust-lang/log) |
workspace.dependencies | patch | `0.4.27` -> `0.4.28` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>rust-lang/log (log)</summary>

###
[`v0.4.28`](https://redirect.github.com/rust-lang/log/blob/HEAD/CHANGELOG.md#0428---2025-09-02)

[Compare
Source](https://redirect.github.com/rust-lang/log/compare/0.4.27...0.4.28)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 10:59:09 +05:30
renovate[bot]
3247991429 Update Rust crate insta to v1.43.2 (#20296)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [insta](https://insta.rs/)
([source](https://redirect.github.com/mitsuhiko/insta)) |
workspace.dependencies | patch | `1.43.1` -> `1.43.2` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>mitsuhiko/insta (insta)</summary>

###
[`v1.43.2`](https://redirect.github.com/mitsuhiko/insta/blob/HEAD/CHANGELOG.md#1432)

[Compare
Source](https://redirect.github.com/mitsuhiko/insta/compare/1.43.1...1.43.2)

- Fix panics when `cargo metadata` fails to execute or parse (e.g., when
cargo is not in PATH or returns invalid output). Now falls back to using
the manifest directory as the workspace root.
[#&#8203;798](https://redirect.github.com/mitsuhiko/insta/issues/798)
([@&#8203;adriangb](https://redirect.github.com/adriangb))
- Fix clippy `uninlined_format_args` lint warnings.
[#&#8203;801](https://redirect.github.com/mitsuhiko/insta/issues/801)
- Changed diff line numbers to 1-based indexing.
[#&#8203;799](https://redirect.github.com/mitsuhiko/insta/issues/799)
- Preserve snapshot names with `INSTA_GLOB_FILTER`.
[#&#8203;786](https://redirect.github.com/mitsuhiko/insta/issues/786)
- Bumped `libc` crate to `0.2.174`, fixing building on musl targets, and
increasing the MSRV of
`insta` to `1.64.0` (released Sept 2022).
[#&#8203;784](https://redirect.github.com/mitsuhiko/insta/issues/784)
- Fix clippy 1.88 errors.
[#&#8203;783](https://redirect.github.com/mitsuhiko/insta/issues/783)
- Fix source path in snapshots for non-child workspaces.
[#&#8203;778](https://redirect.github.com/mitsuhiko/insta/issues/778)
- Add lifetime to Selector in redaction iterator.
[#&#8203;779](https://redirect.github.com/mitsuhiko/insta/issues/779)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 10:58:52 +05:30
justin
08fcf7e106 [ty] initial support for slots=True in dataclasses (#20278) 2025-09-07 18:25:35 +01:00
Carl Meyer
2467c4352e [ty] propagate visitors and constraints through has_relation_in_invariant_position (#20259)
## Summary

The sub-checks for assignability and subtyping of materializations
performed in `has_relation_in_invariant_position` and
`is_subtype_in_invariant_position` need to propagate the
`HasRelationToVisitor`, or we can stack overflow.

A side effect of this change is that we also propagate the
`ConstraintSet` through, rather than using `C::from_bool`, which I think
may also become important for correctness in cases involving type
variables (though it isn't testable yet, since we aren't yet actually
creating constraints other than always-true and always-false.)

## Test Plan

Added mdtest (derived from code found in pydantic) which
stack-overflowed before this PR.

With this change incorporated, pydantic now checks successfully on my
draft PR for PEP 613 TypeAlias support.
2025-09-06 00:17:17 +00:00
Amethyst Reese
a27c64811e Add support for sorting diagnostics without a range (#20257)
- Update ruff ordering compare to work with optional ranges (treating
  them as starting from zero)
2025-09-05 15:23:30 -07:00
Alex Waygood
5d52902e18 [ty] Implement the legacy PEP-484 convention for indicating positional-only parameters (#20248)
Co-authored-by: Carl Meyer <carl@astral.sh>
2025-09-05 17:56:06 +01:00
Eric Mark Martin
eb6154f792 [ty] add doc-comments for some variance stuff (#20189) 2025-09-05 15:03:10 +01:00
David Peter
fdfb51b595 [ty] Update mypy_primer, add egglog-python project (#20078)
Now that https://github.com/astral-sh/ruff/pull/20263 is merged, we can
update mypy_primer and add the new `egglog-python` project to
`good.txt`. The ecosystem-analyzer run shows that we now add 1,356
diagnostics (where we had over 5,000 previously, due to the unsupported
project layout).
2025-09-05 14:17:07 +02:00
David Peter
7ee863b6d7 [ty] Include python folder in environment.root if it exists (#20263)
## Summary

I felt it was safer to add the `python` folder *in addition* to a
possibly-existing `src` folder, even though the `src` folder only
contains Rust code for `maturin`-based projects. There might be
non-maturin projects where a `python` folder exists for other reasons,
next to a normal `src` layout.

closes https://github.com/astral-sh/ty/issues/1120

## Test Plan

Tested locally on the egglog-python project.
2025-09-05 13:53:48 +02:00
David Peter
8ade6c4eaf [ty] Add backreferences to TypedDict items in diagnostics (#20262)
## Summary

Add backreferences to the original item declaration in TypedDict
diagnostics.

Thanks to @AlexWaygood for the suggestion.

## Test Plan

Updated snapshots
2025-09-05 12:38:37 +02:00
David Peter
9e45bfa9fd [ty] Cover full range of annotated assignments (#20261)
## Summary

An annotated assignment `name: annotation` without a right-hand side was
previously not covered by the range returned from
`DefinitionKind::full_range`, because we did expand the range to include
the right-hand side (if there was one), but failed to include the
annotation.

## Test Plan

Updated snapshot tests
2025-09-05 10:12:40 +02:00
David Peter
7509d376eb [ty] Minor: 'can not' => cannot (#20260) 2025-09-05 09:19:14 +02:00
David Peter
a24a4b55ee [ty] TypedDict: Add support for typing.ReadOnly (#20241)
## Summary

Add support for `typing.ReadOnly` as a type qualifier to mark
`TypedDict` fields as being read-only. If you try to mutate them, you
get a new diagnostic:

<img width="787" height="234" alt="image"
src="https://github.com/user-attachments/assets/f62fddf9-4961-4bcd-ad1c-747043ebe5ff"
/>


## Test Plan

* New Markdown tests
* The typing conformance changes are all correct. There are some false
negatives, but those are related to the missing support for the
functional form of `TypedDict`, or to overriding of fields via
inheritance. Both of these topics are tracked in
https://github.com/astral-sh/ty/issues/154
2025-09-04 15:37:42 -07:00
Alex Waygood
888a22e849 [ty] Reduce false positives for ParamSpecs and TypeVarTuples (#20239) 2025-09-04 23:34:37 +01:00
Jelle Zijlstra
08c1d3660c [ty] Narrow specialized generics using isinstance() (#20256)
Closes astral-sh/ty#456. Part of astral-sh/ty#994.

After all the foundational work, this is only a small change, but let's
see if it exposes any unresolved issues.
2025-09-04 15:28:33 -07:00
Takayuki Maeda
670fffef37 [ruff] Use helper function for empty f-string detection in in-empty-collection (RUF060) (#20249)
## Summary

Fixes #20238

Replace inline f-string emptiness check with `is_empty_f_string` helper
function
2025-09-04 20:20:59 +00:00
Jelle Zijlstra
de63f408b9 [ty] Attribute access on top/bottom materializations (#20221)
## Summary

Part of astral-sh/ty#994. The goal of this PR was to add correct
behavior for attribute access on the top and bottom materializations.
This is necessary for the end goal of using the top materialization for
narrowing generics (`isinstance(x, list)`): we want methods like
`x.append` to work correctly in that case.

It turned out to be convenient to represent materialization as a
TypeMapping, so it can be stashed in the `type_mappings` list of a
function object. This also allowed me to remove most concrete
`materialize` methods, since they usually just delegate to the subparts
of the type, the same as other type mappings. That is why the net effect
of this PR is to remove a few hundred lines.

## Test Plan

I added a few more tests. Much of this PR is refactoring and covered by
existing tests.

## Followups

Assigning to attributes of top materializations is not yet covered. This
seems less important so I'd like to defer it.

I noticed that the `materialize` implementation of `Parameters` was
wrong; it did the same for the top and bottom materializations. This PR
makes the bottom materialization slightly more reasonable, but
implementing this correctly will require extending the struct.
2025-09-04 12:01:44 -07:00
Alex Waygood
555b9f78d6 [ty] Minor cleanups (#20240)
## Summary

Two minor cleanups:
- Return `Option<ClassType>` rather than `Option<ClassLiteral>` from
`TypeInferenceBuilder::class_context_of_current_method`. Now that
`ClassType::is_protocol` exists as a method as well as
`ClassLiteral::is_protocol`, this simplifies most of the call-sites of
the `class_context_of_current_method()` method.
- Make more use of the `MethodDecorator::try_from_fn_type` method in
`class.rs`. Under the hood, this method uses the new methods
`FunctionType::is_classmethod()` and `FunctionType::is_staticmethod()`
that @sharkdp recently added, so it gets the semantics more precisely
correct than the code it's replacing in `infer.rs` (by accounting for
implicit staticmethods/classmethods as well as explicit ones). By using
these methods we can delete some code elsewhere (the
`FunctionDecorators::from_decorator_types()` constructor)

## Test Plan

Existing tests
2025-09-04 10:25:49 -07:00
Dylan
c6516e9b60 Bump 0.12.12 (#20242) 2025-09-04 11:35:56 -05:00
David Peter
1aaa0847ab [ty] More tests for TypedDict (#20205)
## Summary

A small set of additional tests for `TypedDict` that I wrote while going
through the spec. Note that this certainly doesn't make the test suite
exhaustive (see remaining open points in the updated list here:
https://github.com/astral-sh/ty/issues/154).
2025-09-04 15:55:42 +00:00
Charlie Marsh
b49aa35074 Split LICENSE addendum by derivation type (#20222)
## Summary

Per
https://github.com/astral-sh/ruff/issues/20191#issuecomment-3251131478,
this PR restructures the license file to draw a distinction between
projects from which we've (e.g.) drawn source code and projects whose
rules we've implemented but have otherwise not reused or adapted source
code from, which are credited in the README. While I was here, I also
sorted the list.
2025-09-03 21:35:33 -04:00
Samuel Rigaud
1e34f3f20a [ty] Fix small test typo (#20220)
Small typo in the comment of a test

Co-authored-by: Samuel Rigaud <rigaud@gmail.com>
2025-09-03 15:24:17 -07:00
Douglas Creager
77b2cee223 [ty] Add functions for revealing assignability/subtyping constraints (#20217)
This PR adds two new `ty_extensions` functions,
`reveal_when_assignable_to` and `reveal_when_subtype_of`. These are
closely related to the existing `is_assignable_to` and `is_subtype_of`,
but instead of returning when the property (always) holds, it produces a
diagnostic that describes _when_ the property holds. (This will let us
construct mdtests that print out constraints that are not always true or
always false — though we don't currently have any instances of those.)

I did not replace _every_ occurrence of the `is_property` variants in
the mdtest suite, instead focusing on the generics-related tests where
it will be important to see the full detail of the constraint sets.

As part of this, I also updated the mdtest harness to accept the shorter
`# revealed:` assertion format for more than just `reveal_type`, and
updated the existing uses of `reveal_protocol_interface` to take
advantage of this.
2025-09-03 16:44:35 -04:00
Dan Parizher
200349c6e8 [flake8-comprehensions] Skip C417 when lambda contains yield/yield from (#20201)
## Summary

Fixes #20198
2025-09-03 16:39:11 -04:00
David Peter
0d4f7dde99 [ty] Treat __new__ as a static method (#20212)
## Summary

Pull this out of https://github.com/astral-sh/ruff/pull/18473 as an
isolated change to make sure it has no adverse effects.

The wrong behavior is observable on `main` for something like
```py
class C:
    def __new__(cls) -> "C":
        cls.x = 1

C.x  # previously: Attribute `x` can only be accessed on instances
     # now:        Type `<class 'C'>` has no attribute `x`
```
where we currently treat `x` as an *instance* attribute (because we
consider `__new__` to be a normal function and `cls` to be the "self"
attribute). With this PR, we do not consider `x` to be an attribute,
neither on the class nor on instances of `C`. If this turns out to be an
important feature, we should add it intentionally, instead of
accidentally.

## Test Plan

Ecosystem checks.
2025-09-03 19:55:20 +02:00
Shahar Naveh
cb1ba0d4c2 Expose Indentation in ruff_python_codegen (#20216)
## Summary

I'm trying to reduce code complexity for
[RustPython](https://github.com/RustPython/RustPython), we have this
file:
056795eed4/compiler/codegen/src/unparse.rs
which can be replaced entirely by `ruff_python_codegen::Generator`.
Unfortunately we can not create an instance of `Generator` easily,
because `Indentation` is not exported at
cda376afe0/crates/ruff_python_codegen/src/lib.rs (L3)

I have managed to bypass this restriction by doing:
```rust
let contents = r"x = 1";
let module = ruff_python_parser::parse_module(contents).unwrap();
let stylist = ruff_python_codegen::Stylist::from_tokens(module.tokens(), contents);
stylist.indentation()
```

But ideally I'd rather use:
```rust
ruff_python_codegen::Indentation::default()
```
2025-09-03 13:32:31 -04:00
Renkai Ge
cda376afe0 [ty]eliminate definitely-impossible types from union in equality narrowing (#20164)
solves https://github.com/astral-sh/ty/issues/939

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-09-03 08:34:22 -07:00
renovate[bot]
b14fc96141 Update Rust crate tracing-subscriber to v0.3.20 (#20162)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [tracing-subscriber](https://tokio.rs)
([source](https://redirect.github.com/tokio-rs/tracing)) |
workspace.dependencies | patch | `0.3.19` -> `0.3.20` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

### GitHub Vulnerability Alerts

####
[CVE-2025-58160](https://redirect.github.com/tokio-rs/tracing/security/advisories/GHSA-xwfj-jgwm-7wp5)

### Impact

Previous versions of tracing-subscriber were vulnerable to ANSI escape
sequence injection attacks. Untrusted user input containing ANSI escape
sequences could be injected into terminal output when logged,
potentially allowing attackers to:

- Manipulate terminal title bars
- Clear screens or modify terminal display
- Potentially mislead users through terminal manipulation

In isolation, impact is minimal, however security issues have been found
in terminal emulators that enabled an attacker to use ANSI escape
sequences via logs to exploit vulnerabilities in the terminal emulator.

### Patches

`tracing-subscriber` version 0.3.20 fixes this vulnerability by escaping
ANSI control characters in when writing events to destinations that may
be printed to the terminal.

### Workarounds

Avoid printing logs to terminal emulators without escaping ANSI control
sequences.

### References

https://www.packetlabs.net/posts/weaponizing-ansi-escape-sequences/

### Acknowledgments

We would like to thank [zefr0x](http://github.com/zefr0x) who
responsibly reported the issue at `security@tokio.rs`.

If you believe you have found a security vulnerability in any tokio-rs
project, please email us at `security@tokio.rs`.

---

### Release Notes

<details>
<summary>tokio-rs/tracing (tracing-subscriber)</summary>

###
[`v0.3.20`](https://redirect.github.com/tokio-rs/tracing/releases/tag/tracing-subscriber-0.3.20):
tracing-subscriber 0.3.20

[Compare
Source](https://redirect.github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20)

**Security Fix**: ANSI Escape Sequence Injection (CVE-TBD)

#### Impact

Previous versions of tracing-subscriber were vulnerable to ANSI escape
sequence injection attacks. Untrusted user input containing ANSI escape
sequences could be injected into terminal output when logged,
potentially allowing attackers to:

- Manipulate terminal title bars
- Clear screens or modify terminal display
- Potentially mislead users through terminal manipulation

In isolation, impact is minimal, however security issues have been found
in terminal emulators that enabled an attacker to use ANSI escape
sequences via logs to exploit vulnerabilities in the terminal emulator.

#### Solution

Version 0.3.20 fixes this vulnerability by escaping ANSI control
characters in when writing events to destinations that may be printed to
the terminal.

#### Affected Versions

All versions of tracing-subscriber prior to 0.3.20 are affected by this
vulnerability.

#### Recommendations

Immediate Action Required: We recommend upgrading to tracing-subscriber
0.3.20 immediately, especially if your application:

- Logs user-provided input (form data, HTTP headers, query parameters,
etc.)
- Runs in environments where terminal output is displayed to users

#### Migration

This is a patch release with no breaking API changes. Simply update your
Cargo.toml:

```toml
[dependencies]
tracing-subscriber = "0.3.20"
```

#### Acknowledgments

We would like to thank [zefr0x](http://github.com/zefr0x) who
responsibly reported the issue at `security@tokio.rs`.

If you believe you have found a security vulnerability in any tokio-rs
project, please email us at `security@tokio.rs`.

</details>

---

### Configuration

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

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-03 10:48:38 -04:00
Wei Lee
c452a2cb79 [airflow] Move airflow.operators.postgres_operator.Mapping from AIR302 to AIR301 (#20172)
<!--
Thank you for contributing to Ruff/ty! 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? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

### Why
Removal should be grouped into the same category. It doesn't matter
whether it's from a provider or not (and the only case we used to have
was not anyway).
`ProviderReplacement` is used to indicate that we have a replacement and
we might need to install an extra Python package to cater to it.

### What
Move `airflow.operators.postgres_operator.Mapping` from AIR302 to AIR301
and get rid of `ProviderReplace::None`

## Test Plan

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

Update the test fixtures accordingly in the first commit and reorganize
them in the second commit
2025-09-03 10:18:17 -04:00
Bhuminjay Soni
4c3e1930f6 [syntax-errors] Detect yield from inside async function (#20051)
<!--
Thank you for contributing to Ruff/ty! 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? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR implements
https://docs.astral.sh/ruff/rules/yield-from-in-async-function/ as a
syntax semantic error

## Test Plan

<!-- How was it tested? -->
I have written a simple inline test as directed in
[https://github.com/astral-sh/ruff/issues/17412](https://github.com/astral-sh/ruff/issues/17412)

---------

Signed-off-by: 11happy <soni5happy@gmail.com>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-09-03 10:13:05 -04:00
Wei Lee
5d7c17c20a [airflow] Convert DatasetOrTimeSchedule(datasets=...) to AssetOrTimeSchedule(assets=...) (AIR311) (#20202)
<!--
Thank you for contributing to Ruff/ty! 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? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

update the argument `datasets` as `assets`

## Test Plan

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

update fixture accordingly
2025-09-03 10:12:11 -04:00
Andrew Gallant
c402bf8ae2 [ty] Correct default value for experimental rename setting
Ref https://github.com/astral-sh/ruff/pull/20207#discussion_r2318336964
2025-09-03 09:57:26 -04:00
Andrew Gallant
6bc33a041f [ty] Pick up typed text as a query for unimported completions
It's almost certainly bad juju to show literally every single possible
symbol when completions are requested but there is nothing typed yet.
Moreover, since there are so many symbols, it is likely beneficial to
try and winnow them down before sending them to the client.

This change tries to extract text that has been typed and then uses
that as a query to listing all available symbols.
2025-09-03 09:57:26 -04:00
Andrew Gallant
0a0eaf5a9b [ty] Make auto-import completions opt-in via an experimental option
Instead of waiting to land auto-import until it is "ready
for users," it'd be nicer to get incremental progress merged
to `main`. By making it an experimental opt-in, we avoid making
the default completion experience worse but permit developers
and motivated users to try it.
2025-09-03 09:57:26 -04:00
Andrew Gallant
8e52027a88 [ty] Add naive implementation of completions for unimported symbols
This re-works the `all_symbols` based added previously to work across
all modules available, and not just what is directly in the workspace.

Note that we always pass an empty string as a query, which makes the
results always empty. We'll fix this in a subsequent commit.
2025-09-03 09:57:26 -04:00
Andrew Gallant
78db56e362 [ty] Add base for "all symbols" implementation
This just copies the existing "workspace symbols" implementation and
re-names some things.
2025-09-03 09:57:26 -04:00
Andrew Gallant
046893c186 [ty] Make Module::all_submodules return Module instead of Name
This is to facilitate recursive traversal of all modules in an
environment. This way, we can keep asking for submodules.

This also simplifies how this is used in completions, and probably makes
it faster. Namely, since we return the `Module` itself, callers don't
need to invoke the full module resolver just to get the module type.

Note that this doesn't include namespace packages. (Which were
previously not supported in `Module::all_submodules`.) Given how they
can be spread out across multiple search paths, they will likely require
special consideration here.
2025-09-03 09:57:26 -04:00
Andrew Gallant
9cea752934 [ty] Require that we can find a root when listing sub-modules
This is similar to a change made in the "list top-level modules"
implementation that had been masked by poor Salsa failure modes.
Basically, if we can't find a root here, it *must* be a bug. And if we
just silently skip over it, we risk voiding Salsa's purity contract,
leading to more difficult to debug panics.

This did cause one test to fail, but only because the test wasn't
properly setting up roots.
2025-09-03 09:57:26 -04:00
Wei Lee
3b913ce652 [airflow] Improve the AIR002 error message (#20173)
<!--
Thank you for contributing to Ruff/ty! 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? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->


### What
Change the message from "DAG should have an explicit `schedule`
argument" to "`DAG` or `@dag` should have an explicit `schedule`
argument"

### Why
We're trying to get rid of the idea that DAG in airflow was Directed
acyclic graph. Thus, change it to refer to the class `DAG` or the
decorator `@dag` might help a bit.

## Test Plan

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

update the test fixtures accordly
2025-09-03 09:22:56 -04:00
Brent Westbrook
aee9350df1 [ty] Add GitLab output format (#20155)
## Summary

This wires up the GitLab output format moved into `ruff_db` in
https://github.com/astral-sh/ruff/pull/20117 to the ty CLI.

While I was here, I made one unrelated change to the CLI docs. Clap was
rendering the escapes around the `\[default\]` brackets for the `full`
output, so I just switched those to parentheses:

```
--output-format <OUTPUT_FORMAT>
    The format to use for printing diagnostic messages

    Possible values:
    - full:    Print diagnostics verbosely, with context and helpful hints \[default\]
    - concise: Print diagnostics concisely, one per line
    - gitlab:  Print diagnostics in the JSON format expected by GitLab Code Quality reports
```

## Test Plan

New CLI test, and a manual test with `--config 'terminal.output-format =
"gitlab"'` to make sure this works as a configuration option too. I also
tried piping the output through jq to make sure it's at least valid JSON
2025-09-03 09:08:12 -04:00
David Peter
4e97b97a76 [ty] Run ecosystem-analyzer in release mode (#20210)
## Summary

We already have `mypy_primer` running in debug mode, so this should
provide some additional coverage, and allows us produce reasonable
timing results.

## Test Plan

CI run on this PR
2025-09-03 12:51:30 +02:00
David Peter
00214fc60c [ty] Update ecosystem-analyzer (#20209)
This pulls in some new changes, like the possibility to run on failing
projects (timeouts, abnormal exits).
2025-09-03 12:28:34 +02:00
Aria Desires
ec5584219e [ty] Make initializer calls GotoTargets (#20014)
This introduces `GotoTarget::Call` that represents the kind of
ambiguous/overloaded click of a callable-being-called:

```py
x = mymodule.MyClass(1, 2)
             ^^^^^^^
```

This is equivalent to `GotoTarget::Expression` for the same span but
enriched
with information about the actual callable implementation.

That is, if you click on `MyClass` in `MyClass()` it is *both* a
reference to the class and to the initializer of the class. Therefore
it would be ideal for goto-* and docstrings to be some intelligent
merging of both the class and the initializer.

In particular the callable-implementation (initializer) is prioritized
over the callable-itself (class) so when showing docstrings we will
preferentially show the docs of the initializer if it exists, and then
fallback to the docs of the class.

For goto-definition/goto-declaration we will yield both the class and
the initializer, requiring you to pick which you want (this is perhaps
needlessly pedantic but...).

Fixes https://github.com/astral-sh/ty/issues/898
Fixes https://github.com/astral-sh/ty/issues/1010
2025-09-02 14:49:14 -04:00
chiri
d5e48a0f80 [flake8-use-pathlib] Make PTH119 and PTH120 fixes unsafe because they can change behavior (#20118)
## Summary

Fixes https://github.com/astral-sh/ruff/issues/20112

## Test Plan
`cargo nextest run flake8_use_pathlib`

---------

Co-authored-by: dylwil3 <dylwil3@gmail.com>
2025-09-02 10:55:24 -05:00
Aria Desires
f40a0b3800 [ty] add more lsp tests for overloads (#20148)
I decided to split out the addition of these tests from other PRs so
that it's easier to follow changes to the LSP's function call handling.
I'm not particularly concerned with whether the results produced by
these tests are "good" or "bad" in this PR, I'm just establishing a
baseline.
2025-09-02 10:38:26 -04:00
269 changed files with 10619 additions and 6219 deletions

View File

@@ -259,6 +259,10 @@ jobs:
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
with:
tool: cargo-insta
- name: "Install uv"
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
with:
enable-cache: "true"
- name: ty mdtests (GitHub annotations)
if: ${{ needs.determine_changes.outputs.ty == 'true' }}
env:
@@ -317,6 +321,10 @@ jobs:
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
with:
tool: cargo-insta
- name: "Install uv"
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
with:
enable-cache: "true"
- name: "Run tests"
shell: bash
env:
@@ -340,6 +348,10 @@ jobs:
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
with:
tool: cargo-nextest
- name: "Install uv"
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
with:
enable-cache: "true"
- name: "Run tests"
shell: bash
env:
@@ -441,9 +453,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@2bb61346d075e720d4c3da92f23b6d612d5a7543 # v1.15.3
with:
tool: cargo-fuzz@0.11.2
uses: cargo-bins/cargo-binstall@837578dfb436769f1e6669b2e23ffea9d9d2da8f # v1.15.4
- name: "Install cargo-fuzz"
# Download the latest version from quick install and not the github releases because github releases only has MUSL targets.
run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm
@@ -463,7 +473,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
name: Download Ruff binary to test
id: download-cached-binary
@@ -664,7 +674,7 @@ jobs:
branch: ${{ github.event.pull_request.base.ref }}
workflow: "ci.yaml"
check_artifacts: true
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
- name: Fuzz
env:
FORCE_COLOR: 1
@@ -694,7 +704,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@2bb61346d075e720d4c3da92f23b6d612d5a7543 # v1.15.3
- uses: cargo-bins/cargo-binstall@837578dfb436769f1e6669b2e23ffea9d9d2da8f # v1.15.4
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -734,7 +744,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
@@ -777,7 +787,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: Install uv
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: uv pip install -r docs/requirements-insiders.txt --system
@@ -909,7 +919,7 @@ jobs:
persist-credentials: false
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
- name: "Install Rust toolchain"
run: rustup show
@@ -942,7 +952,7 @@ jobs:
persist-credentials: false
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
- name: "Install Rust toolchain"
run: rustup show

View File

@@ -34,7 +34,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"

View File

@@ -39,7 +39,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:
@@ -82,7 +82,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:

View File

@@ -22,7 +22,7 @@ jobs:
id-token: write
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: wheels-*

View File

@@ -65,7 +65,7 @@ jobs:
run: |
git config --global user.name typeshedbot
git config --global user.email '<>'
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
- name: Sync typeshed stubs
run: |
rm -rf "ruff/${VENDORED_TYPESHED}"
@@ -117,7 +117,7 @@ jobs:
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
- name: Setup git
run: |
git config --global user.name typeshedbot
@@ -155,7 +155,7 @@ jobs:
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
- name: Setup git
run: |
git config --global user.name typeshedbot

View File

@@ -33,7 +33,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:
@@ -64,11 +64,12 @@ jobs:
cd ..
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@27dd66d9e397d986ef9c631119ee09556eab8af9"
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@1f560d07d672effae250e3d271da53d96c5260ff"
ecosystem-analyzer \
--repository ruff \
diff \
--profile=release \
--projects-old ruff/projects_old.txt \
--projects-new ruff/projects_new.txt \
--old old_commit \

View File

@@ -29,7 +29,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:

View File

@@ -1,5 +1,44 @@
# Breaking Changes
## 0.13.0
- **Several rules can now add `from __future__ import annotations` automatically**
`TC001`, `TC002`, `TC003`, `RUF013`, and `UP037` now add `from __future__ import annotations` as part of their fixes when the
`lint.future-annotations` setting is enabled. This allows the rules to move
more imports into `TYPE_CHECKING` blocks (`TC001`, `TC002`, and `TC003`),
use PEP 604 union syntax on Python versions before 3.10 (`RUF013`), and
unquote more annotations (`UP037`).
- **Full module paths are now used to verify first-party modules**
Ruff now checks that the full path to a module exists on disk before
categorizing it as a first-party import. This change makes first-party
import detection more accurate, helping to avoid false positives on local
directories with the same name as a third-party dependency, for example. See
the [FAQ
section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) on import categorization for more details.
- **Deprecated rules must now be selected by exact rule code**
Ruff will no longer activate deprecated rules selected by their group name
or prefix. As noted below, the two remaining deprecated rules were also
removed in this release, so this won't affect any current rules, but it will
still affect any deprecations in the future.
- **The deprecated macOS configuration directory fallback has been removed**
Ruff will no longer look for a user-level configuration file at
`~/Library/Application Support/ruff/ruff.toml` on macOS. This feature was
deprecated in v0.5 in favor of using the [XDG
specification](https://specifications.freedesktop.org/basedir-spec/latest/)
(usually resolving to `~/.config/ruff/ruff.toml`), like on Linux. The
fallback and accompanying deprecation warning have now been removed.
- **[`pandas-df-variable-name`](https://docs.astral.sh/ruff/rules/pandas-df-variable-name) (`PD901`) has been removed**
- **[`non-pep604-isinstance`](https://docs.astral.sh/ruff/rules/non-pep604-isinstance) (`UP038`) has been removed**
## 0.12.0
- **Detection of more syntax errors**

View File

@@ -1,517 +1,119 @@
# Changelog
## 0.12.11
## 0.13.0
### Preview features
- \[`airflow`\] Extend `AIR311` and `AIR312` rules ([#20082](https://github.com/astral-sh/ruff/pull/20082))
- \[`airflow`\] Replace wrong path `airflow.io.storage` with `airflow.io.store` (`AIR311`) ([#20081](https://github.com/astral-sh/ruff/pull/20081))
- \[`flake8-async`\] Implement `blocking-http-call-httpx-in-async-function` (`ASYNC212`) ([#20091](https://github.com/astral-sh/ruff/pull/20091))
- \[`flake8-logging-format`\] Add auto-fix for f-string logging calls (`G004`) ([#19303](https://github.com/astral-sh/ruff/pull/19303))
- \[`flake8-use-pathlib`\] Add autofix for `PTH211` ([#20009](https://github.com/astral-sh/ruff/pull/20009))
- \[`flake8-use-pathlib`\] Make `PTH100` fix unsafe because it can change behavior ([#20100](https://github.com/astral-sh/ruff/pull/20100))
### Bug fixes
- \[`pyflakes`, `pylint`\] Fix false positives caused by `__class__` cell handling (`F841`, `PLE0117`) ([#20048](https://github.com/astral-sh/ruff/pull/20048))
- \[`pyflakes`\] Fix `allowed-unused-imports` matching for top-level modules (`F401`) ([#20115](https://github.com/astral-sh/ruff/pull/20115))
- \[`ruff`\] Fix false positive for t-strings in `default-factory-kwarg` (`RUF026`) ([#20032](https://github.com/astral-sh/ruff/pull/20032))
- \[`ruff`\] Preserve relative whitespace in multi-line expressions (`RUF033`) ([#19647](https://github.com/astral-sh/ruff/pull/19647))
### Rule changes
- \[`ruff`\] Handle empty t-strings in `unnecessary-empty-iterable-within-deque-call` (`RUF037`) ([#20045](https://github.com/astral-sh/ruff/pull/20045))
### Documentation
- Fix incorrect `D413` links in docstrings convention FAQ ([#20089](https://github.com/astral-sh/ruff/pull/20089))
- \[`flake8-use-pathlib`\] Update links to the table showing the correspondence between `os` and `pathlib` ([#20103](https://github.com/astral-sh/ruff/pull/20103))
## 0.12.10
### Preview features
- \[`flake8-simplify`\] Implement fix for `maxsplit` without separator (`SIM905`) ([#19851](https://github.com/astral-sh/ruff/pull/19851))
- \[`flake8-use-pathlib`\] Add fixes for `PTH102` and `PTH103` ([#19514](https://github.com/astral-sh/ruff/pull/19514))
### Bug fixes
- \[`isort`\] Handle multiple continuation lines after module docstring (`I002`) ([#19818](https://github.com/astral-sh/ruff/pull/19818))
- \[`pyupgrade`\] Avoid reporting `__future__` features as unnecessary when they are used (`UP010`) ([#19769](https://github.com/astral-sh/ruff/pull/19769))
- \[`pyupgrade`\] Handle nested `Optional`s (`UP045`) ([#19770](https://github.com/astral-sh/ruff/pull/19770))
### Rule changes
- \[`pycodestyle`\] Make `E731` fix unsafe instead of display-only for class assignments ([#19700](https://github.com/astral-sh/ruff/pull/19700))
- \[`pyflakes`\] Add secondary annotation showing previous definition (`F811`) ([#19900](https://github.com/astral-sh/ruff/pull/19900))
### Documentation
- Fix description of global config file discovery strategy ([#19188](https://github.com/astral-sh/ruff/pull/19188))
- Update outdated links to <https://typing.python.org/en/latest/source/stubs.html> ([#19992](https://github.com/astral-sh/ruff/pull/19992))
- \[`flake8-annotations`\] Remove unused import in example (`ANN401`) ([#20000](https://github.com/astral-sh/ruff/pull/20000))
## 0.12.9
### Preview features
- \[`airflow`\] Add check for `airflow.secrets.cache.SecretCache` (`AIR301`) ([#17707](https://github.com/astral-sh/ruff/pull/17707))
- \[`ruff`\] Offer a safe fix for multi-digit zeros (`RUF064`) ([#19847](https://github.com/astral-sh/ruff/pull/19847))
### Bug fixes
- \[`flake8-blind-except`\] Fix `BLE001` false-positive on `raise ... from None` ([#19755](https://github.com/astral-sh/ruff/pull/19755))
- \[`flake8-comprehensions`\] Fix false positive for `C420` with attribute, subscript, or slice assignment targets ([#19513](https://github.com/astral-sh/ruff/pull/19513))
- \[`flake8-simplify`\] Fix handling of U+001C..U+001F whitespace (`SIM905`) ([#19849](https://github.com/astral-sh/ruff/pull/19849))
### Rule changes
- \[`pylint`\] Use lowercase hex characters to match the formatter (`PLE2513`) ([#19808](https://github.com/astral-sh/ruff/pull/19808))
### Documentation
- Fix `lint.future-annotations` link ([#19876](https://github.com/astral-sh/ruff/pull/19876))
### Other changes
- Build `riscv64` binaries for release ([#19819](https://github.com/astral-sh/ruff/pull/19819))
- Add rule code to error description in GitLab output ([#19896](https://github.com/astral-sh/ruff/pull/19896))
- Improve rendering of the `full` output format ([#19415](https://github.com/astral-sh/ruff/pull/19415))
Below is an example diff for [`F401`](https://docs.astral.sh/ruff/rules/unused-import/):
```diff
-unused.py:8:19: F401 [*] `pathlib` imported but unused
+F401 [*] `pathlib` imported but unused
+ --> unused.py:8:19
|
7 | # Unused, _not_ marked as required (due to the alias).
8 | import pathlib as non_alias
- | ^^^^^^^^^ F401
+ | ^^^^^^^^^
9 |
10 | # Unused, marked as required.
|
- = help: Remove unused import: `pathlib`
+help: Remove unused import: `pathlib`
```
For now, the primary difference is the movement of the filename, line number, and column information to a second line in the header. This new representation will allow us to make further additions to Ruff's diagnostics, such as adding sub-diagnostics and multiple annotations to the same snippet.
## 0.12.8
### Preview features
- \[`flake8-use-pathlib`\] Expand `PTH201` to check all `PurePath` subclasses ([#19440](https://github.com/astral-sh/ruff/pull/19440))
### Bug fixes
- \[`flake8-blind-except`\] Change `BLE001` to correctly parse exception tuples ([#19747](https://github.com/astral-sh/ruff/pull/19747))
- \[`flake8-errmsg`\] Exclude `typing.cast` from `EM101` ([#19656](https://github.com/astral-sh/ruff/pull/19656))
- \[`flake8-simplify`\] Fix raw string handling in `SIM905` for embedded quotes ([#19591](https://github.com/astral-sh/ruff/pull/19591))
- \[`flake8-import-conventions`\] Avoid false positives for NFKC-normalized `__debug__` import aliases in `ICN001` ([#19411](https://github.com/astral-sh/ruff/pull/19411))
- \[`isort`\] Fix syntax error after docstring ending with backslash (`I002`) ([#19505](https://github.com/astral-sh/ruff/pull/19505))
- \[`pylint`\] Mark `PLC0207` fixes as unsafe when `*args` unpacking is present ([#19679](https://github.com/astral-sh/ruff/pull/19679))
- \[`pyupgrade`\] Prevent infinite loop with `I002` (`UP010`, `UP035`) ([#19413](https://github.com/astral-sh/ruff/pull/19413))
- \[`ruff`\] Parenthesize generator expressions in f-strings (`RUF010`) ([#19434](https://github.com/astral-sh/ruff/pull/19434))
### Rule changes
- \[`eradicate`\] Don't flag `pyrefly` pragmas as unused code (`ERA001`) ([#19731](https://github.com/astral-sh/ruff/pull/19731))
### Documentation
- Replace "associative" with "commutative" in docs for `RUF036` ([#19706](https://github.com/astral-sh/ruff/pull/19706))
- Fix copy and line separator colors in dark mode ([#19630](https://github.com/astral-sh/ruff/pull/19630))
- Fix link to `typing` documentation ([#19648](https://github.com/astral-sh/ruff/pull/19648))
- \[`refurb`\] Make more examples error out-of-the-box ([#19695](https://github.com/astral-sh/ruff/pull/19695),[#19673](https://github.com/astral-sh/ruff/pull/19673),[#19672](https://github.com/astral-sh/ruff/pull/19672))
### Other changes
- Include column numbers in GitLab output format ([#19708](https://github.com/astral-sh/ruff/pull/19708))
- Always expand tabs to four spaces in diagnostics ([#19618](https://github.com/astral-sh/ruff/pull/19618))
- Update pre-commit's `ruff` id ([#19654](https://github.com/astral-sh/ruff/pull/19654))
## 0.12.7
This is a follow-up release to 0.12.6. Because of an issue in the package metadata, 0.12.6 failed to publish fully to PyPI and has been yanked. Similarly, there is no GitHub release or Git tag for 0.12.6. The contents of the 0.12.7 release are identical to 0.12.6, except for the updated metadata.
## 0.12.6
### Preview features
- \[`flake8-commas`\] Add support for trailing comma checks in type parameter lists (`COM812`, `COM819`) ([#19390](https://github.com/astral-sh/ruff/pull/19390))
- \[`pylint`\] Implement auto-fix for `missing-maxsplit-arg` (`PLC0207`) ([#19387](https://github.com/astral-sh/ruff/pull/19387))
- \[`ruff`\] Offer fixes for `RUF039` in more cases ([#19065](https://github.com/astral-sh/ruff/pull/19065))
### Bug fixes
- Support `.pyi` files in ruff analyze graph ([#19611](https://github.com/astral-sh/ruff/pull/19611))
- \[`flake8-pyi`\] Preserve inline comment in ellipsis removal (`PYI013`) ([#19399](https://github.com/astral-sh/ruff/pull/19399))
- \[`perflint`\] Ignore rule if target is `global` or `nonlocal` (`PERF401`) ([#19539](https://github.com/astral-sh/ruff/pull/19539))
- \[`pyupgrade`\] Fix `UP030` to avoid modifying double curly braces in format strings ([#19378](https://github.com/astral-sh/ruff/pull/19378))
- \[`refurb`\] Ignore decorated functions for `FURB118` ([#19339](https://github.com/astral-sh/ruff/pull/19339))
- \[`refurb`\] Mark `int` and `bool` cases for `Decimal.from_float` as safe fixes (`FURB164`) ([#19468](https://github.com/astral-sh/ruff/pull/19468))
- \[`ruff`\] Fix `RUF033` for named default expressions ([#19115](https://github.com/astral-sh/ruff/pull/19115))
### Rule changes
- \[`flake8-blind-except`\] Change `BLE001` to permit `logging.critical(..., exc_info=True)` ([#19520](https://github.com/astral-sh/ruff/pull/19520))
### Performance
- Add support for specifying minimum dots in detected string imports ([#19538](https://github.com/astral-sh/ruff/pull/19538))
## 0.12.5
### Preview features
- \[`flake8-use-pathlib`\] Add autofix for `PTH101`, `PTH104`, `PTH105`, `PTH121` ([#19404](https://github.com/astral-sh/ruff/pull/19404))
- \[`ruff`\] Support byte strings (`RUF055`) ([#18926](https://github.com/astral-sh/ruff/pull/18926))
### Bug fixes
- Fix `unreachable` panic in parser ([#19183](https://github.com/astral-sh/ruff/pull/19183))
- \[`flake8-pyi`\] Skip fix if all `Union` members are `None` (`PYI016`) ([#19416](https://github.com/astral-sh/ruff/pull/19416))
- \[`perflint`\] Parenthesize generator expressions (`PERF401`) ([#19325](https://github.com/astral-sh/ruff/pull/19325))
- \[`pylint`\] Handle empty comments after line continuation (`PLR2044`) ([#19405](https://github.com/astral-sh/ruff/pull/19405))
### Rule changes
- \[`pep8-naming`\] Fix `N802` false positives for `CGIHTTPRequestHandler` and `SimpleHTTPRequestHandler` ([#19432](https://github.com/astral-sh/ruff/pull/19432))
## 0.12.4
### Preview features
- \[`flake8-type-checking`, `pyupgrade`, `ruff`\] Add `from __future__ import annotations` when it would allow new fixes (`TC001`, `TC002`, `TC003`, `UP037`, `RUF013`) ([#19100](https://github.com/astral-sh/ruff/pull/19100))
- \[`flake8-use-pathlib`\] Add autofix for `PTH109` ([#19245](https://github.com/astral-sh/ruff/pull/19245))
- \[`pylint`\] Detect indirect `pathlib.Path` usages for `unspecified-encoding` (`PLW1514`) ([#19304](https://github.com/astral-sh/ruff/pull/19304))
### Bug fixes
- \[`flake8-bugbear`\] Fix `B017` false negatives for keyword exception arguments ([#19217](https://github.com/astral-sh/ruff/pull/19217))
- \[`flake8-use-pathlib`\] Fix false negative on direct `Path()` instantiation (`PTH210`) ([#19388](https://github.com/astral-sh/ruff/pull/19388))
- \[`flake8-django`\] Fix `DJ008` false positive for abstract models with type-annotated `abstract` field ([#19221](https://github.com/astral-sh/ruff/pull/19221))
- \[`isort`\] Fix `I002` import insertion after docstring with multiple string statements ([#19222](https://github.com/astral-sh/ruff/pull/19222))
- \[`isort`\] Treat form feed as valid whitespace before a semicolon ([#19343](https://github.com/astral-sh/ruff/pull/19343))
- \[`pydoclint`\] Fix `SyntaxError` from fixes with line continuations (`D201`, `D202`) ([#19246](https://github.com/astral-sh/ruff/pull/19246))
- \[`refurb`\] `FURB164` fix should validate arguments and should usually be marked unsafe ([#19136](https://github.com/astral-sh/ruff/pull/19136))
### Rule changes
- \[`flake8-use-pathlib`\] Skip single dots for `invalid-pathlib-with-suffix` (`PTH210`) on versions >= 3.14 ([#19331](https://github.com/astral-sh/ruff/pull/19331))
- \[`pep8_naming`\] Avoid false positives on standard library functions with uppercase names (`N802`) ([#18907](https://github.com/astral-sh/ruff/pull/18907))
- \[`pycodestyle`\] Handle brace escapes for t-strings in logical lines ([#19358](https://github.com/astral-sh/ruff/pull/19358))
- \[`pylint`\] Extend invalid string character rules to include t-strings ([#19355](https://github.com/astral-sh/ruff/pull/19355))
- \[`ruff`\] Allow `strict` kwarg when checking for `starmap-zip` (`RUF058`) in Python 3.14+ ([#19333](https://github.com/astral-sh/ruff/pull/19333))
### Documentation
- \[`flake8-type-checking`\] Make `TC010` docs example more realistic ([#19356](https://github.com/astral-sh/ruff/pull/19356))
- Make more documentation examples error out-of-the-box ([#19288](https://github.com/astral-sh/ruff/pull/19288),[#19272](https://github.com/astral-sh/ruff/pull/19272),[#19291](https://github.com/astral-sh/ruff/pull/19291),[#19296](https://github.com/astral-sh/ruff/pull/19296),[#19292](https://github.com/astral-sh/ruff/pull/19292),[#19295](https://github.com/astral-sh/ruff/pull/19295),[#19297](https://github.com/astral-sh/ruff/pull/19297),[#19309](https://github.com/astral-sh/ruff/pull/19309))
## 0.12.3
### Preview features
- \[`flake8-bugbear`\] Support non-context-manager calls in `B017` ([#19063](https://github.com/astral-sh/ruff/pull/19063))
- \[`flake8-use-pathlib`\] Add autofixes for `PTH100`, `PTH106`, `PTH107`, `PTH108`, `PTH110`, `PTH111`, `PTH112`, `PTH113`, `PTH114`, `PTH115`, `PTH117`, `PTH119`, `PTH120` ([#19213](https://github.com/astral-sh/ruff/pull/19213))
- \[`flake8-use-pathlib`\] Add autofixes for `PTH203`, `PTH204`, `PTH205` ([#18922](https://github.com/astral-sh/ruff/pull/18922))
### Bug fixes
- \[`flake8-return`\] Fix false-positive for variables used inside nested functions in `RET504` ([#18433](https://github.com/astral-sh/ruff/pull/18433))
- Treat form feed as valid whitespace before a line continuation ([#19220](https://github.com/astral-sh/ruff/pull/19220))
- \[`flake8-type-checking`\] Fix syntax error introduced by fix (`TC008`) ([#19150](https://github.com/astral-sh/ruff/pull/19150))
- \[`pyupgrade`\] Keyword arguments in `super` should suppress the `UP008` fix ([#19131](https://github.com/astral-sh/ruff/pull/19131))
### Documentation
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI007`, `PYI008`) ([#19103](https://github.com/astral-sh/ruff/pull/19103))
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM116`) ([#19111](https://github.com/astral-sh/ruff/pull/19111))
- \[`flake8-type-checking`\] Make example error out-of-the-box (`TC001`) ([#19151](https://github.com/astral-sh/ruff/pull/19151))
- \[`flake8-use-pathlib`\] Make example error out-of-the-box (`PTH210`) ([#19189](https://github.com/astral-sh/ruff/pull/19189))
- \[`pycodestyle`\] Make example error out-of-the-box (`E272`) ([#19191](https://github.com/astral-sh/ruff/pull/19191))
- \[`pycodestyle`\] Make example not raise unnecessary `SyntaxError` (`E114`) ([#19190](https://github.com/astral-sh/ruff/pull/19190))
- \[`pydoclint`\] Make example error out-of-the-box (`DOC501`) ([#19218](https://github.com/astral-sh/ruff/pull/19218))
- \[`pylint`, `pyupgrade`\] Fix syntax errors in examples (`PLW1501`, `UP028`) ([#19127](https://github.com/astral-sh/ruff/pull/19127))
- \[`pylint`\] Update `missing-maxsplit-arg` docs and error to suggest proper usage (`PLC0207`) ([#18949](https://github.com/astral-sh/ruff/pull/18949))
- \[`flake8-bandit`\] Make example error out-of-the-box (`S412`) ([#19241](https://github.com/astral-sh/ruff/pull/19241))
## 0.12.2
### Preview features
- \[`flake8-pyi`\] Expand `Optional[A]` to `A | None` (`PYI016`) ([#18572](https://github.com/astral-sh/ruff/pull/18572))
- \[`pyupgrade`\] Mark `UP008` fix safe if no comments are in range ([#18683](https://github.com/astral-sh/ruff/pull/18683))
### Bug fixes
- \[`flake8-comprehensions`\] Fix `C420` to prepend whitespace when needed ([#18616](https://github.com/astral-sh/ruff/pull/18616))
- \[`perflint`\] Fix `PERF403` panic on attribute or subscription loop variable ([#19042](https://github.com/astral-sh/ruff/pull/19042))
- \[`pydocstyle`\] Fix `D413` infinite loop for parenthesized docstring ([#18930](https://github.com/astral-sh/ruff/pull/18930))
- \[`pylint`\] Fix `PLW0108` autofix introducing a syntax error when the lambda's body contains an assignment expression ([#18678](https://github.com/astral-sh/ruff/pull/18678))
- \[`refurb`\] Fix false positive on empty tuples (`FURB168`) ([#19058](https://github.com/astral-sh/ruff/pull/19058))
- \[`ruff`\] Allow more `field` calls from `attrs` (`RUF009`) ([#19021](https://github.com/astral-sh/ruff/pull/19021))
- \[`ruff`\] Fix syntax error introduced for an empty string followed by a u-prefixed string (`UP025`) ([#18899](https://github.com/astral-sh/ruff/pull/18899))
### Rule changes
- \[`flake8-executable`\] Allow `uvx` in shebang line (`EXE003`) ([#18967](https://github.com/astral-sh/ruff/pull/18967))
- \[`pandas`\] Avoid flagging `PD002` if `pandas` is not imported ([#18963](https://github.com/astral-sh/ruff/pull/18963))
- \[`pyupgrade`\] Avoid PEP-604 unions with `typing.NamedTuple` (`UP007`, `UP045`) ([#18682](https://github.com/astral-sh/ruff/pull/18682))
### Documentation
- Document link between `import-outside-top-level (PLC0415)` and `lint.flake8-tidy-imports.banned-module-level-imports` ([#18733](https://github.com/astral-sh/ruff/pull/18733))
- Fix description of the `format.skip-magic-trailing-comma` example ([#19095](https://github.com/astral-sh/ruff/pull/19095))
- \[`airflow`\] Make `AIR302` example error out-of-the-box ([#18988](https://github.com/astral-sh/ruff/pull/18988))
- \[`airflow`\] Make `AIR312` example error out-of-the-box ([#18989](https://github.com/astral-sh/ruff/pull/18989))
- \[`flake8-annotations`\] Make `ANN401` example error out-of-the-box ([#18974](https://github.com/astral-sh/ruff/pull/18974))
- \[`flake8-async`\] Make `ASYNC100` example error out-of-the-box ([#18993](https://github.com/astral-sh/ruff/pull/18993))
- \[`flake8-async`\] Make `ASYNC105` example error out-of-the-box ([#19002](https://github.com/astral-sh/ruff/pull/19002))
- \[`flake8-async`\] Make `ASYNC110` example error out-of-the-box ([#18975](https://github.com/astral-sh/ruff/pull/18975))
- \[`flake8-async`\] Make `ASYNC210` example error out-of-the-box ([#18977](https://github.com/astral-sh/ruff/pull/18977))
- \[`flake8-async`\] Make `ASYNC220`, `ASYNC221`, and `ASYNC222` examples error out-of-the-box ([#18978](https://github.com/astral-sh/ruff/pull/18978))
- \[`flake8-async`\] Make `ASYNC251` example error out-of-the-box ([#18990](https://github.com/astral-sh/ruff/pull/18990))
- \[`flake8-bandit`\] Make `S201` example error out-of-the-box ([#19017](https://github.com/astral-sh/ruff/pull/19017))
- \[`flake8-bandit`\] Make `S604` and `S609` examples error out-of-the-box ([#19049](https://github.com/astral-sh/ruff/pull/19049))
- \[`flake8-bugbear`\] Make `B028` example error out-of-the-box ([#19054](https://github.com/astral-sh/ruff/pull/19054))
- \[`flake8-bugbear`\] Make `B911` example error out-of-the-box ([#19051](https://github.com/astral-sh/ruff/pull/19051))
- \[`flake8-datetimez`\] Make `DTZ011` example error out-of-the-box ([#19055](https://github.com/astral-sh/ruff/pull/19055))
- \[`flake8-datetimez`\] Make `DTZ901` example error out-of-the-box ([#19056](https://github.com/astral-sh/ruff/pull/19056))
- \[`flake8-pyi`\] Make `PYI032` example error out-of-the-box ([#19061](https://github.com/astral-sh/ruff/pull/19061))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI014`, `PYI015`) ([#19097](https://github.com/astral-sh/ruff/pull/19097))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI042`) ([#19101](https://github.com/astral-sh/ruff/pull/19101))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI059`) ([#19080](https://github.com/astral-sh/ruff/pull/19080))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI062`) ([#19079](https://github.com/astral-sh/ruff/pull/19079))
- \[`flake8-pytest-style`\] Make example error out-of-the-box (`PT023`) ([#19104](https://github.com/astral-sh/ruff/pull/19104))
- \[`flake8-pytest-style`\] Make example error out-of-the-box (`PT030`) ([#19105](https://github.com/astral-sh/ruff/pull/19105))
- \[`flake8-quotes`\] Make example error out-of-the-box (`Q003`) ([#19106](https://github.com/astral-sh/ruff/pull/19106))
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM110`) ([#19113](https://github.com/astral-sh/ruff/pull/19113))
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM113`) ([#19109](https://github.com/astral-sh/ruff/pull/19109))
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM401`) ([#19110](https://github.com/astral-sh/ruff/pull/19110))
- \[`pyflakes`\] Fix backslash in docs (`F621`) ([#19098](https://github.com/astral-sh/ruff/pull/19098))
- \[`pylint`\] Fix `PLC0415` example ([#18970](https://github.com/astral-sh/ruff/pull/18970))
## 0.12.1
### Preview features
- \[`flake8-errmsg`\] Extend `EM101` to support byte strings ([#18867](https://github.com/astral-sh/ruff/pull/18867))
- \[`flake8-use-pathlib`\] Add autofix for `PTH202` ([#18763](https://github.com/astral-sh/ruff/pull/18763))
- \[`pygrep-hooks`\] Add `AsyncMock` methods to `invalid-mock-access` (`PGH005`) ([#18547](https://github.com/astral-sh/ruff/pull/18547))
- \[`pylint`\] Ignore `__init__.py` files in (`PLC0414`) ([#18400](https://github.com/astral-sh/ruff/pull/18400))
- \[`ruff`\] Trigger `RUF037` for empty string and byte strings ([#18862](https://github.com/astral-sh/ruff/pull/18862))
- [formatter] Fix missing blank lines before decorated classes in `.pyi` files ([#18888](https://github.com/astral-sh/ruff/pull/18888))
### Bug fixes
- Avoid generating diagnostics with per-file ignores ([#18801](https://github.com/astral-sh/ruff/pull/18801))
- Handle parenthesized arguments in `remove_argument` ([#18805](https://github.com/astral-sh/ruff/pull/18805))
- \[`flake8-logging`\] Avoid false positive for `exc_info=True` outside `logger.exception` (`LOG014`) ([#18737](https://github.com/astral-sh/ruff/pull/18737))
- \[`flake8-pytest-style`\] Enforce `pytest` import for decorators ([#18779](https://github.com/astral-sh/ruff/pull/18779))
- \[`flake8-pytest-style`\] Mark autofix for `PT001` and `PT023` as unsafe if there's comments in the decorator ([#18792](https://github.com/astral-sh/ruff/pull/18792))
- \[`flake8-pytest-style`\] `PT001`/`PT023` fix makes syntax error on parenthesized decorator ([#18782](https://github.com/astral-sh/ruff/pull/18782))
- \[`flake8-raise`\] Make fix unsafe if it deletes comments (`RSE102`) ([#18788](https://github.com/astral-sh/ruff/pull/18788))
- \[`flake8-simplify`\] Fix `SIM911` autofix creating a syntax error ([#18793](https://github.com/astral-sh/ruff/pull/18793))
- \[`flake8-simplify`\] Fix false negatives for shadowed bindings (`SIM910`, `SIM911`) ([#18794](https://github.com/astral-sh/ruff/pull/18794))
- \[`flake8-simplify`\] Preserve original behavior for `except ()` and bare `except` (`SIM105`) ([#18213](https://github.com/astral-sh/ruff/pull/18213))
- \[`flake8-pyi`\] Fix `PYI041`'s fix causing `TypeError` with `None | None | ...` ([#18637](https://github.com/astral-sh/ruff/pull/18637))
- \[`perflint`\] Fix `PERF101` autofix creating a syntax error and mark autofix as unsafe if there are comments in the `list` call expr ([#18803](https://github.com/astral-sh/ruff/pull/18803))
- \[`perflint`\] Fix false negative in `PERF401` ([#18866](https://github.com/astral-sh/ruff/pull/18866))
- \[`pylint`\] Avoid flattening nested `min`/`max` when outer call has single argument (`PLW3301`) ([#16885](https://github.com/astral-sh/ruff/pull/16885))
- \[`pylint`\] Fix `PLC2801` autofix creating a syntax error ([#18857](https://github.com/astral-sh/ruff/pull/18857))
- \[`pylint`\] Mark `PLE0241` autofix as unsafe if there's comments in the base classes ([#18832](https://github.com/astral-sh/ruff/pull/18832))
- \[`pylint`\] Suppress `PLE2510`/`PLE2512`/`PLE2513`/`PLE2514`/`PLE2515` autofix if the text contains an odd number of backslashes ([#18856](https://github.com/astral-sh/ruff/pull/18856))
- \[`refurb`\] Detect more exotic float literals in `FURB164` ([#18925](https://github.com/astral-sh/ruff/pull/18925))
- \[`refurb`\] Fix `FURB163` autofix creating a syntax error for `yield` expressions ([#18756](https://github.com/astral-sh/ruff/pull/18756))
- \[`refurb`\] Mark `FURB129` autofix as unsafe if there's comments in the `readlines` call ([#18858](https://github.com/astral-sh/ruff/pull/18858))
- \[`ruff`\] Fix false positives and negatives in `RUF010` ([#18690](https://github.com/astral-sh/ruff/pull/18690))
- Fix casing of `analyze.direction` variant names ([#18892](https://github.com/astral-sh/ruff/pull/18892))
### Rule changes
- Fix f-string interpolation escaping in generated fixes ([#18882](https://github.com/astral-sh/ruff/pull/18882))
- \[`flake8-return`\] Mark `RET501` fix unsafe if comments are inside ([#18780](https://github.com/astral-sh/ruff/pull/18780))
- \[`flake8-async`\] Fix detection for large integer sleep durations in `ASYNC116` rule ([#18767](https://github.com/astral-sh/ruff/pull/18767))
- \[`flake8-async`\] Mark autofix for `ASYNC115` as unsafe if the call expression contains comments ([#18753](https://github.com/astral-sh/ruff/pull/18753))
- \[`flake8-bugbear`\] Mark autofix for `B004` as unsafe if the `hasattr` call expr contains comments ([#18755](https://github.com/astral-sh/ruff/pull/18755))
- \[`flake8-comprehension`\] Mark autofix for `C420` as unsafe if there's comments inside the dict comprehension ([#18768](https://github.com/astral-sh/ruff/pull/18768))
- \[`flake8-comprehensions`\] Handle template strings for comprehension fixes ([#18710](https://github.com/astral-sh/ruff/pull/18710))
- \[`flake8-future-annotations`\] Add autofix (`FA100`) ([#18903](https://github.com/astral-sh/ruff/pull/18903))
- \[`pyflakes`\] Mark `F504`/`F522`/`F523` autofix as unsafe if there's a call with side effect ([#18839](https://github.com/astral-sh/ruff/pull/18839))
- \[`pylint`\] Allow fix with comments and document performance implications (`PLW3301`) ([#18936](https://github.com/astral-sh/ruff/pull/18936))
- \[`pylint`\] Detect more exotic `NaN` literals in `PLW0177` ([#18630](https://github.com/astral-sh/ruff/pull/18630))
- \[`pylint`\] Fix `PLC1802` autofix creating a syntax error and mark autofix as unsafe if there's comments in the `len` call ([#18836](https://github.com/astral-sh/ruff/pull/18836))
- \[`pyupgrade`\] Extend version detection to include `sys.version_info.major` (`UP036`) ([#18633](https://github.com/astral-sh/ruff/pull/18633))
- \[`ruff`\] Add lint rule `RUF064` for calling `chmod` with non-octal integers ([#18541](https://github.com/astral-sh/ruff/pull/18541))
- \[`ruff`\] Added `cls.__dict__.get('__annotations__')` check (`RUF063`) ([#18233](https://github.com/astral-sh/ruff/pull/18233))
- \[`ruff`\] Frozen `dataclass` default should be valid (`RUF009`) ([#18735](https://github.com/astral-sh/ruff/pull/18735))
### Server
- Consider virtual path for various server actions ([#18910](https://github.com/astral-sh/ruff/pull/18910))
### Documentation
- Add fix safety sections ([#18940](https://github.com/astral-sh/ruff/pull/18940),[#18841](https://github.com/astral-sh/ruff/pull/18841),[#18802](https://github.com/astral-sh/ruff/pull/18802),[#18837](https://github.com/astral-sh/ruff/pull/18837),[#18800](https://github.com/astral-sh/ruff/pull/18800),[#18415](https://github.com/astral-sh/ruff/pull/18415),[#18853](https://github.com/astral-sh/ruff/pull/18853),[#18842](https://github.com/astral-sh/ruff/pull/18842))
- Use updated pre-commit id ([#18718](https://github.com/astral-sh/ruff/pull/18718))
- \[`perflint`\] Small docs improvement to `PERF401` ([#18786](https://github.com/astral-sh/ruff/pull/18786))
- \[`pyupgrade`\]: Use `super()`, not `__super__` in error messages (`UP008`) ([#18743](https://github.com/astral-sh/ruff/pull/18743))
- \[`flake8-pie`\] Small docs fix to `PIE794` ([#18829](https://github.com/astral-sh/ruff/pull/18829))
- \[`flake8-pyi`\] Correct `collections-named-tuple` example to use PascalCase assignment ([#16884](https://github.com/astral-sh/ruff/pull/16884))
- \[`flake8-pie`\] Add note on type checking benefits to `unnecessary-dict-kwargs` (`PIE804`) ([#18666](https://github.com/astral-sh/ruff/pull/18666))
- \[`pycodestyle`\] Clarify PEP 8 relationship to `whitespace-around-operator` rules ([#18870](https://github.com/astral-sh/ruff/pull/18870))
### Other changes
- Disallow newlines in format specifiers of single quoted f- or t-strings ([#18708](https://github.com/astral-sh/ruff/pull/18708))
- \[`flake8-logging`\] Add fix safety section to `LOG002` ([#18840](https://github.com/astral-sh/ruff/pull/18840))
- \[`pyupgrade`\] Add fix safety section to `UP010` ([#18838](https://github.com/astral-sh/ruff/pull/18838))
## 0.12.0
Check out the [blog post](https://astral.sh/blog/ruff-v0.12.0) for a migration
Check out the [blog post](https://astral.sh/blog/ruff-v0.13.0) for a migration
guide and overview of the changes!
### Breaking changes
- **Detection of more syntax errors**
- **Several rules can now add `from __future__ import annotations` automatically**
Ruff now detects version-related syntax errors, such as the use of the `match`
statement on Python versions before 3.10, and syntax errors emitted by
CPython's compiler, such as irrefutable `match` patterns before the final
`case` arm.
`TC001`, `TC002`, `TC003`, `RUF013`, and `UP037` now add `from __future__ import annotations` as part of their fixes when the
`lint.future-annotations` setting is enabled. This allows the rules to move
more imports into `TYPE_CHECKING` blocks (`TC001`, `TC002`, and `TC003`),
use PEP 604 union syntax on Python versions before 3.10 (`RUF013`), and
unquote more annotations (`UP037`).
- **New default Python version handling for syntax errors**
- **Full module paths are now used to verify first-party modules**
Ruff will default to the *latest* supported Python version (3.13) when
checking for the version-related syntax errors mentioned above to prevent
false positives in projects without a Python version configured. The default
in all other cases, like applying lint rules, is unchanged and remains at the
minimum supported Python version (3.9).
Ruff now checks that the full path to a module exists on disk before
categorizing it as a first-party import. This change makes first-party
import detection more accurate, helping to avoid false positives on local
directories with the same name as a third-party dependency, for example. See
the [FAQ
section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) on import categorization for more details.
- **Updated f-string formatting**
- **Deprecated rules must now be selected by exact rule code**
Ruff now formats multi-line f-strings with format specifiers to avoid adding a
line break after the format specifier. This addresses a change to the Python
grammar in version 3.13.4 that made such a line break a syntax error.
Ruff will no longer activate deprecated rules selected by their group name
or prefix. As noted below, the two remaining deprecated rules were also
removed in this release, so this won't affect any current rules, but it will
still affect any deprecations in the future.
- **`rust-toolchain.toml` is no longer included in source distributions**
- **The deprecated macOS configuration directory fallback has been removed**
The `rust-toolchain.toml` is used to specify a higher Rust version than Ruff's
minimum supported Rust version (MSRV) for development and building release
artifacts. However, when present in source distributions, it would also cause
downstream package maintainers to pull in the same Rust toolchain, even if
their available toolchain was MSRV-compatible.
Ruff will no longer look for a user-level configuration file at
`~/Library/Application Support/ruff/ruff.toml` on macOS. This feature was
deprecated in v0.5 in favor of using the [XDG
specification](https://specifications.freedesktop.org/basedir-spec/latest/)
(usually resolving to `~/.config/ruff/ruff.toml`), like on Linux. The
fallback and accompanying deprecation warning have now been removed.
### Removed Rules
The following rules have been removed:
- [`suspicious-xmle-tree-usage`](https://docs.astral.sh/ruff/rules/suspicious-xmle-tree-usage/)
(`S320`)
### Deprecated Rules
The following rules have been deprecated:
- [`pandas-df-variable-name`](https://docs.astral.sh/ruff/rules/pandas-df-variable-name/)
- [`pandas-df-variable-name`](https://docs.astral.sh/ruff/rules/pandas-df-variable-name) (`PD901`)
- [`non-pep604-isinstance`](https://docs.astral.sh/ruff/rules/non-pep604-isinstance) (`UP038`)
### Stabilization
The following rules have been stabilized and are no longer in preview:
- [`for-loop-writes`](https://docs.astral.sh/ruff/rules/for-loop-writes) (`FURB122`)
- [`check-and-remove-from-set`](https://docs.astral.sh/ruff/rules/check-and-remove-from-set) (`FURB132`)
- [`verbose-decimal-constructor`](https://docs.astral.sh/ruff/rules/verbose-decimal-constructor) (`FURB157`)
- [`fromisoformat-replace-z`](https://docs.astral.sh/ruff/rules/fromisoformat-replace-z) (`FURB162`)
- [`int-on-sliced-str`](https://docs.astral.sh/ruff/rules/int-on-sliced-str) (`FURB166`)
- [`exc-info-outside-except-handler`](https://docs.astral.sh/ruff/rules/exc-info-outside-except-handler) (`LOG014`)
- [`import-outside-top-level`](https://docs.astral.sh/ruff/rules/import-outside-top-level) (`PLC0415`)
- [`unnecessary-dict-index-lookup`](https://docs.astral.sh/ruff/rules/unnecessary-dict-index-lookup) (`PLR1733`)
- [`nan-comparison`](https://docs.astral.sh/ruff/rules/nan-comparison) (`PLW0177`)
- [`eq-without-hash`](https://docs.astral.sh/ruff/rules/eq-without-hash) (`PLW1641`)
- [`pytest-parameter-with-default-argument`](https://docs.astral.sh/ruff/rules/pytest-parameter-with-default-argument) (`PT028`)
- [`pytest-warns-too-broad`](https://docs.astral.sh/ruff/rules/pytest-warns-too-broad) (`PT030`)
- [`pytest-warns-with-multiple-statements`](https://docs.astral.sh/ruff/rules/pytest-warns-with-multiple-statements) (`PT031`)
- [`invalid-formatter-suppression-comment`](https://docs.astral.sh/ruff/rules/invalid-formatter-suppression-comment) (`RUF028`)
- [`dataclass-enum`](https://docs.astral.sh/ruff/rules/dataclass-enum) (`RUF049`)
- [`class-with-mixed-type-vars`](https://docs.astral.sh/ruff/rules/class-with-mixed-type-vars) (`RUF053`)
- [`unnecessary-round`](https://docs.astral.sh/ruff/rules/unnecessary-round) (`RUF057`)
- [`starmap-zip`](https://docs.astral.sh/ruff/rules/starmap-zip) (`RUF058`)
- [`non-pep604-annotation-optional`] (`UP045`)
- [`non-pep695-generic-class`](https://docs.astral.sh/ruff/rules/non-pep695-generic-class) (`UP046`)
- [`non-pep695-generic-function`](https://docs.astral.sh/ruff/rules/non-pep695-generic-function) (`UP047`)
- [`private-type-parameter`](https://docs.astral.sh/ruff/rules/private-type-parameter) (`UP049`)
- [`airflow-dag-no-schedule-argument`](https://docs.astral.sh/ruff/rules/airflow-dag-no-schedule-argument)
(`AIR002`)
- [`airflow3-removal`](https://docs.astral.sh/ruff/rules/airflow3-removal) (`AIR301`)
- [`airflow3-moved-to-provider`](https://docs.astral.sh/ruff/rules/airflow3-moved-to-provider)
(`AIR302`)
- [`airflow3-suggested-update`](https://docs.astral.sh/ruff/rules/airflow3-suggested-update)
(`AIR311`)
- [`airflow3-suggested-to-move-to-provider`](https://docs.astral.sh/ruff/rules/airflow3-suggested-to-move-to-provider)
(`AIR312`)
- [`long-sleep-not-forever`](https://docs.astral.sh/ruff/rules/long-sleep-not-forever) (`ASYNC116`)
- [`f-string-number-format`](https://docs.astral.sh/ruff/rules/f-string-number-format) (`FURB116`)
- [`os-symlink`](https://docs.astral.sh/ruff/rules/os-symlink) (`PTH211`)
- [`generic-not-last-base-class`](https://docs.astral.sh/ruff/rules/generic-not-last-base-class)
(`PYI059`)
- [`redundant-none-literal`](https://docs.astral.sh/ruff/rules/redundant-none-literal) (`PYI061`)
- [`pytest-raises-ambiguous-pattern`](https://docs.astral.sh/ruff/rules/pytest-raises-ambiguous-pattern)
(`RUF043`)
- [`unused-unpacked-variable`](https://docs.astral.sh/ruff/rules/unused-unpacked-variable)
(`RUF059`)
- [`useless-class-metaclass-type`](https://docs.astral.sh/ruff/rules/useless-class-metaclass-type)
(`UP050`)
The following behaviors have been stabilized:
- [`collection-literal-concatenation`] (`RUF005`) now recognizes slices, in
addition to list literals and variables.
- The fix for [`readlines-in-for`] (`FURB129`) is now marked as always safe.
- [`if-else-block-instead-of-if-exp`] (`SIM108`) will now further simplify
expressions to use `or` instead of an `if` expression, where possible.
- [`unused-noqa`] (`RUF100`) now checks for file-level `noqa` comments as well
as inline comments.
- [`subprocess-without-shell-equals-true`] (`S603`) now accepts literal strings,
as well as lists and tuples of literal strings, as trusted input.
- [`boolean-type-hint-positional-argument`] (`FBT001`) now applies to types that
include `bool`, like `bool | int` or `typing.Optional[bool]`, in addition to
plain `bool` annotations.
- [`non-pep604-annotation-union`] (`UP007`) has now been split into two rules.
`UP007` now applies only to `typing.Union`, while
[`non-pep604-annotation-optional`] (`UP045`) checks for use of
`typing.Optional`. `UP045` has also been stabilized in this release, but you
may need to update existing `include`, `ignore`, or `noqa` settings to
accommodate this change.
- [`assert-raises-exception`](https://docs.astral.sh/ruff/rules/assert-raises-exception) (`B017`)
now checks for direct calls to `unittest.TestCase.assert_raises` and `pytest.raises` instead of
only the context manager forms.
- [`missing-trailing-comma`](https://docs.astral.sh/ruff/rules/missing-trailing-comma) (`COM812`)
and [`prohibited-trailing-comma`](https://docs.astral.sh/ruff/rules/prohibited-trailing-comma)
(`COM819`) now check for trailing commas in PEP 695 type parameter lists.
- [`raw-string-in-exception`](https://docs.astral.sh/ruff/rules/raw-string-in-exception) (`EM101`)
now also checks for byte strings in exception messages.
- [`invalid-mock-access`](https://docs.astral.sh/ruff/rules/invalid-mock-access) (`PGH005`) now
checks for `AsyncMock` methods like `not_awaited` in addition to the synchronous variants.
- [`useless-import-alias`](https://docs.astral.sh/ruff/rules/useless-import-alias) (`PLC0414`) no
longer applies to `__init__.py` files, where it conflicted with one of the suggested fixes for
[`unused-import`](https://docs.astral.sh/ruff/rules/unused-import) (`F401`).
- [`bidirectional-unicode`](https://docs.astral.sh/ruff/rules/bidirectional-unicode) (`PLE2502`) now
also checks for U+061C (Arabic Letter Mark).
- The fix for
[`multiple-with-statements`](https://docs.astral.sh/ruff/rules/multiple-with-statements)
(`SIM117`) is now marked as always safe.
### Preview features
- \[`ruff`\] Check for non-context-manager use of `pytest.raises`, `pytest.warns`, and `pytest.deprecated_call` (`RUF061`) ([#17368](https://github.com/astral-sh/ruff/pull/17368))
- [syntax-errors] Raise unsupported syntax error for template strings prior to Python 3.14 ([#18664](https://github.com/astral-sh/ruff/pull/18664))
- \[`pyupgrade`\] Enable `UP043` in stub files ([#20027](https://github.com/astral-sh/ruff/pull/20027))
### Bug fixes
- Add syntax error when conversion flag does not immediately follow exclamation mark ([#18706](https://github.com/astral-sh/ruff/pull/18706))
- Add trailing space around `readlines` ([#18542](https://github.com/astral-sh/ruff/pull/18542))
- Fix `\r` and `\r\n` handling in t- and f-string debug texts ([#18673](https://github.com/astral-sh/ruff/pull/18673))
- Hug closing `}` when f-string expression has a format specifier ([#18704](https://github.com/astral-sh/ruff/pull/18704))
- \[`flake8-pyi`\] Avoid syntax error in the case of starred and keyword arguments (`PYI059`) ([#18611](https://github.com/astral-sh/ruff/pull/18611))
- \[`flake8-return`\] Fix `RET504` autofix generating a syntax error ([#18428](https://github.com/astral-sh/ruff/pull/18428))
- \[`pep8-naming`\] Suppress fix for `N804` and `N805` if the recommended name is already used ([#18472](https://github.com/astral-sh/ruff/pull/18472))
- \[`pycodestyle`\] Avoid causing a syntax error in expressions spanning multiple lines (`E731`) ([#18479](https://github.com/astral-sh/ruff/pull/18479))
- \[`pyupgrade`\] Suppress `UP008` if `super` is shadowed ([#18688](https://github.com/astral-sh/ruff/pull/18688))
- \[`refurb`\] Parenthesize lambda and ternary expressions (`FURB122`, `FURB142`) ([#18592](https://github.com/astral-sh/ruff/pull/18592))
- \[`ruff`\] Handle extra arguments to `deque` (`RUF037`) ([#18614](https://github.com/astral-sh/ruff/pull/18614))
- \[`ruff`\] Preserve parentheses around `deque` in fix for `unnecessary-empty-iterable-within-deque-call` (`RUF037`) ([#18598](https://github.com/astral-sh/ruff/pull/18598))
- \[`ruff`\] Validate arguments before offering a fix (`RUF056`) ([#18631](https://github.com/astral-sh/ruff/pull/18631))
- \[`ruff`\] Skip fix for `RUF059` if dummy name is already bound ([#18509](https://github.com/astral-sh/ruff/pull/18509))
- \[`pylint`\] Fix `PLW0128` to check assignment targets in square brackets and after asterisks ([#18665](https://github.com/astral-sh/ruff/pull/18665))
### Rule changes
- Fix false positive on mutations in `return` statements (`B909`) ([#18408](https://github.com/astral-sh/ruff/pull/18408))
- Treat `ty:` comments as pragma comments ([#18532](https://github.com/astral-sh/ruff/pull/18532))
- \[`flake8-pyi`\] Apply `custom-typevar-for-self` to string annotations (`PYI019`) ([#18311](https://github.com/astral-sh/ruff/pull/18311))
- \[`pyupgrade`\] Don't offer a fix for `Optional[None]` (`UP007`, `UP045)` ([#18545](https://github.com/astral-sh/ruff/pull/18545))
- \[`pyupgrade`\] Fix `super(__class__, self)` detection (`UP008`) ([#18478](https://github.com/astral-sh/ruff/pull/18478))
- \[`refurb`\] Make the fix for `FURB163` unsafe for `log2`, `log10`, `*args`, and deleted comments ([#18645](https://github.com/astral-sh/ruff/pull/18645))
- \[`pyupgrade`\] Apply `UP008` only when the `__class__` cell exists ([#19424](https://github.com/astral-sh/ruff/pull/19424))
- \[`ruff`\] Fix empty f-string detection in `in-empty-collection` (`RUF060`) ([#20249](https://github.com/astral-sh/ruff/pull/20249))
### Server
- Support cancellation requests ([#18627](https://github.com/astral-sh/ruff/pull/18627))
- Add support for using uv as an alternative formatter backend ([#19665](https://github.com/astral-sh/ruff/pull/19665))
### Documentation
- Drop confusing second `*` from glob pattern example for `per-file-target-version` ([#18709](https://github.com/astral-sh/ruff/pull/18709))
- Update Neovim configuration examples ([#18491](https://github.com/astral-sh/ruff/pull/18491))
- \[`pylint`\] De-emphasize `__hash__ = Parent.__hash__` (`PLW1641`) ([#18613](https://github.com/astral-sh/ruff/pull/18613))
- \[`refurb`\] Add a note about float literal handling (`FURB157`) ([#18615](https://github.com/astral-sh/ruff/pull/18615))
- \[`pep8-naming`\] Fix formatting of `__all__` (`N816`) ([#20301](https://github.com/astral-sh/ruff/pull/20301))
## 0.12.x
See [changelogs/0.12.x](./changelogs/0.12.x.md)
## 0.11.x
@@ -556,12 +158,3 @@ See [changelogs/0.2.x](./changelogs/0.2.x.md)
## 0.1.x
See [changelogs/0.1.x](./changelogs/0.1.x.md)
[`boolean-type-hint-positional-argument`]: https://docs.astral.sh/ruff/rules/boolean-type-hint-positional-argument
[`collection-literal-concatenation`]: https://docs.astral.sh/ruff/rules/collection-literal-concatenation
[`if-else-block-instead-of-if-exp`]: https://docs.astral.sh/ruff/rules/if-else-block-instead-of-if-exp
[`non-pep604-annotation-optional`]: https://docs.astral.sh/ruff/rules/non-pep604-annotation-optional
[`non-pep604-annotation-union`]: https://docs.astral.sh/ruff/rules/non-pep604-annotation-union
[`readlines-in-for`]: https://docs.astral.sh/ruff/rules/readlines-in-for
[`subprocess-without-shell-equals-true`]: https://docs.astral.sh/ruff/rules/subprocess-without-shell-equals-true
[`unused-noqa`]: https://docs.astral.sh/ruff/rules/unused-noqa

223
Cargo.lock generated
View File

@@ -257,9 +257,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.3"
version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
[[package]]
name = "bitvec"
@@ -295,7 +295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
dependencies = [
"memchr",
"regex-automata 0.4.10",
"regex-automata",
"serde",
]
@@ -408,9 +408,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.46"
version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57"
checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
dependencies = [
"clap_builder",
"clap_derive",
@@ -418,9 +418,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.46"
version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41"
checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
dependencies = [
"anstream",
"anstyle",
@@ -461,9 +461,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.45"
version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
dependencies = [
"heck",
"proc-macro2",
@@ -603,7 +603,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -612,7 +612,7 @@ version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -1035,7 +1035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys 0.60.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -1231,8 +1231,8 @@ dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata 0.4.10",
"regex-syntax 0.8.5",
"regex-automata",
"regex-syntax",
]
[[package]]
@@ -1241,7 +1241,7 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.4",
"ignore",
"walkdir",
]
@@ -1459,7 +1459,7 @@ dependencies = [
"globset",
"log",
"memchr",
"regex-automata 0.4.10",
"regex-automata",
"same-file",
"walkdir",
"winapi-util",
@@ -1521,7 +1521,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.4",
"inotify-sys",
"libc",
]
@@ -1537,9 +1537,9 @@ dependencies = [
[[package]]
name = "insta"
version = "1.43.1"
version = "1.43.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "154934ea70c58054b556dd430b99a98c2a7ff5309ac9891597e339b5c28f4371"
checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0"
dependencies = [
"console 0.15.11",
"globset",
@@ -1617,7 +1617,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -1681,7 +1681,7 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -1728,9 +1728,9 @@ checksum = "a037eddb7d28de1d0fc42411f501b53b75838d313908078d6698d064f3029b24"
[[package]]
name = "js-sys"
version = "0.3.77"
version = "0.3.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738"
dependencies = [
"once_cell",
"wasm-bindgen",
@@ -1770,9 +1770,9 @@ checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "libcst"
version = "1.8.2"
version = "1.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae28ddc5b90c3e3146a21d051ca095cbc8d932ad8714cf65ddf71a9abb35684c"
checksum = "052ef5d9fc958a51aeebdf3713573b36c6fd6eed0bf0e60e204d2c0f8cf19b9f"
dependencies = [
"annotate-snippets",
"libcst_derive",
@@ -1785,9 +1785,9 @@ dependencies = [
[[package]]
name = "libcst_derive"
version = "1.8.2"
version = "1.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc2de5c2f62bcf8a4f7290b1854388b262c4b68f1db1a3ee3ef6d4c1319b00a3"
checksum = "a91a751afee92cbdd59d4bc6754c7672712eec2d30a308f23de4e3287b2929cb"
dependencies = [
"quote",
"syn",
@@ -1809,7 +1809,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.4",
"libc",
"redox_syscall",
]
@@ -1850,9 +1850,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.27"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "lsp-server"
@@ -1913,11 +1913,11 @@ dependencies = [
[[package]]
name = "matchers"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
dependencies = [
"regex-automata 0.1.10",
"regex-automata",
]
[[package]]
@@ -2014,7 +2014,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.4",
"cfg-if",
"cfg_aliases",
"libc",
@@ -2026,7 +2026,7 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.4",
"cfg-if",
"cfg_aliases",
"libc",
@@ -2054,7 +2054,7 @@ version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.4",
"fsevent-sys",
"inotify",
"kqueue",
@@ -2074,12 +2074,11 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
version = "0.50.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
dependencies = [
"overload",
"winapi",
"windows-sys 0.52.0",
]
[[package]]
@@ -2154,12 +2153,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking_lot"
version = "0.12.4"
@@ -2666,7 +2659,7 @@ version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.4",
]
[[package]]
@@ -2688,17 +2681,8 @@ checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.10",
"regex-syntax 0.8.5",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
"regex-automata",
"regex-syntax",
]
[[package]]
@@ -2709,7 +2693,7 @@ checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.8.5",
"regex-syntax",
]
[[package]]
@@ -2718,12 +2702,6 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.8.5"
@@ -2743,13 +2721,13 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.12.11"
version = "0.13.0"
dependencies = [
"anyhow",
"argfile",
"assert_fs",
"bincode 2.0.1",
"bitflags 2.9.3",
"bitflags 2.9.4",
"cachedir",
"clap",
"clap_complete_command",
@@ -2999,11 +2977,11 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.12.11"
version = "0.13.0"
dependencies = [
"aho-corasick",
"anyhow",
"bitflags 2.9.3",
"bitflags 2.9.4",
"clap",
"colored 3.0.0",
"fern",
@@ -3108,7 +3086,7 @@ name = "ruff_python_ast"
version = "0.0.0"
dependencies = [
"aho-corasick",
"bitflags 2.9.3",
"bitflags 2.9.4",
"compact_str",
"get-size2",
"is-macro",
@@ -3196,7 +3174,7 @@ dependencies = [
name = "ruff_python_literal"
version = "0.0.0"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.4",
"itertools 0.14.0",
"ruff_python_ast",
"unic-ucd-category",
@@ -3207,7 +3185,7 @@ name = "ruff_python_parser"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.3",
"bitflags 2.9.4",
"bstr",
"compact_str",
"get-size2",
@@ -3232,7 +3210,7 @@ dependencies = [
name = "ruff_python_semantic"
version = "0.0.0"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.4",
"insta",
"is-macro",
"ruff_cache",
@@ -3253,7 +3231,7 @@ dependencies = [
name = "ruff_python_stdlib"
version = "0.0.0"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.4",
"unicode-ident",
]
@@ -3337,7 +3315,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.12.11"
version = "0.13.0"
dependencies = [
"console_error_panic_hook",
"console_log",
@@ -3430,11 +3408,11 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.4",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.60.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -3827,7 +3805,7 @@ dependencies = [
"getrandom 0.3.3",
"once_cell",
"rustix",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -4161,15 +4139,15 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.19"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
dependencies = [
"chrono",
"matchers",
"nu-ansi-term",
"once_cell",
"regex",
"regex-automata",
"sharded-slab",
"smallvec",
"thread_local",
@@ -4240,7 +4218,8 @@ dependencies = [
name = "ty_ide"
version = "0.0.0"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.4",
"camino",
"get-size2",
"insta",
"itertools 0.14.0",
@@ -4278,7 +4257,7 @@ dependencies = [
"pep440_rs",
"rayon",
"regex",
"regex-automata 0.4.10",
"regex-automata",
"ruff_cache",
"ruff_db",
"ruff_macros",
@@ -4304,7 +4283,7 @@ name = "ty_python_semantic"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.3",
"bitflags 2.9.4",
"bitvec",
"camino",
"colored 3.0.0",
@@ -4357,7 +4336,7 @@ name = "ty_server"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.3",
"bitflags 2.9.4",
"crossbeam",
"dunce",
"insta",
@@ -4400,7 +4379,7 @@ name = "ty_test"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.3",
"bitflags 2.9.4",
"camino",
"colored 3.0.0",
"insta",
@@ -4759,21 +4738,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb"
dependencies = [
"bumpalo",
"log",
@@ -4785,9 +4765,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.50"
version = "0.4.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
checksum = "0ca85039a9b469b38336411d6d6ced91f3fc87109a2a27b0c197663f5144dffe"
dependencies = [
"cfg-if",
"js-sys",
@@ -4798,9 +4778,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -4808,9 +4788,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa"
dependencies = [
"proc-macro2",
"quote",
@@ -4821,18 +4801,18 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-bindgen-test"
version = "0.3.50"
version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3"
checksum = "80cc7f8a4114fdaa0c58383caf973fc126cf004eba25c9dc639bccd3880d55ad"
dependencies = [
"js-sys",
"minicov",
@@ -4843,9 +4823,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.50"
version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b"
checksum = "c5ada2ab788d46d4bda04c9d567702a79c8ced14f51f221646a16ed39d0e6a5d"
dependencies = [
"proc-macro2",
"quote",
@@ -4854,9 +4834,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.77"
version = "0.3.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -4892,37 +4872,15 @@ dependencies = [
"glob",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.61.2"
@@ -4982,6 +4940,15 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
@@ -5150,7 +5117,7 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.4",
]
[[package]]

View File

@@ -115,7 +115,7 @@ jiff = { version = "0.2.0" }
js-sys = { version = "0.3.69" }
jod-thread = { version = "1.0.0" }
libc = { version = "0.2.153" }
libcst = { version = "1.1.0", default-features = false }
libcst = { version = "1.8.4", default-features = false }
log = { version = "0.4.17" }
lsp-server = { version = "0.7.6" }
lsp-types = { git = "https://github.com/astral-sh/lsp-types.git", rev = "3512a9f", features = [
@@ -251,6 +251,14 @@ rest_pat_in_fully_bound_structs = "warn"
redundant_clone = "warn"
debug_assert_with_mut_call = "warn"
unused_peekable = "warn"
# This lint sometimes flags code whose `if` and `else`
# bodies could be flipped when a `!` operator is removed.
# While perhaps sometimes a good idea, it is also often
# not a good idea due to other factors impacting
# readability. For example, if flipping the bodies results
# in the `if` being an order of magnitude bigger than the
# `else`, then some might consider that harder to read.
if_not_else = "allow"
# Diagnostics are not actionable: Enable once https://github.com/rust-lang/rust-clippy/issues/13774 is resolved.
large_stack_arrays = "allow"

1160
LICENSE

File diff suppressed because it is too large Load Diff

View File

@@ -148,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version.
curl -LsSf https://astral.sh/ruff/0.12.11/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.12.11/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.13.0/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.13.0/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -182,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.11
rev: v0.13.0
hooks:
# Run the linter.
- id: ruff-check

551
changelogs/0.12.x.md Normal file
View File

@@ -0,0 +1,551 @@
# Changelog 0.12.x
## 0.12.0
Check out the [blog post](https://astral.sh/blog/ruff-v0.12.0) for a migration
guide and overview of the changes!
### Breaking changes
- **Detection of more syntax errors**
Ruff now detects version-related syntax errors, such as the use of the `match`
statement on Python versions before 3.10, and syntax errors emitted by
CPython's compiler, such as irrefutable `match` patterns before the final
`case` arm.
- **New default Python version handling for syntax errors**
Ruff will default to the *latest* supported Python version (3.13) when
checking for the version-related syntax errors mentioned above to prevent
false positives in projects without a Python version configured. The default
in all other cases, like applying lint rules, is unchanged and remains at the
minimum supported Python version (3.9).
- **Updated f-string formatting**
Ruff now formats multi-line f-strings with format specifiers to avoid adding a
line break after the format specifier. This addresses a change to the Python
grammar in version 3.13.4 that made such a line break a syntax error.
- **`rust-toolchain.toml` is no longer included in source distributions**
The `rust-toolchain.toml` is used to specify a higher Rust version than Ruff's
minimum supported Rust version (MSRV) for development and building release
artifacts. However, when present in source distributions, it would also cause
downstream package maintainers to pull in the same Rust toolchain, even if
their available toolchain was MSRV-compatible.
### Removed Rules
The following rules have been removed:
- [`suspicious-xmle-tree-usage`](https://docs.astral.sh/ruff/rules/suspicious-xmle-tree-usage/)
(`S320`)
### Deprecated Rules
The following rules have been deprecated:
- [`pandas-df-variable-name`](https://docs.astral.sh/ruff/rules/pandas-df-variable-name/)
### Stabilization
The following rules have been stabilized and are no longer in preview:
- [`for-loop-writes`](https://docs.astral.sh/ruff/rules/for-loop-writes) (`FURB122`)
- [`check-and-remove-from-set`](https://docs.astral.sh/ruff/rules/check-and-remove-from-set) (`FURB132`)
- [`verbose-decimal-constructor`](https://docs.astral.sh/ruff/rules/verbose-decimal-constructor) (`FURB157`)
- [`fromisoformat-replace-z`](https://docs.astral.sh/ruff/rules/fromisoformat-replace-z) (`FURB162`)
- [`int-on-sliced-str`](https://docs.astral.sh/ruff/rules/int-on-sliced-str) (`FURB166`)
- [`exc-info-outside-except-handler`](https://docs.astral.sh/ruff/rules/exc-info-outside-except-handler) (`LOG014`)
- [`import-outside-top-level`](https://docs.astral.sh/ruff/rules/import-outside-top-level) (`PLC0415`)
- [`unnecessary-dict-index-lookup`](https://docs.astral.sh/ruff/rules/unnecessary-dict-index-lookup) (`PLR1733`)
- [`nan-comparison`](https://docs.astral.sh/ruff/rules/nan-comparison) (`PLW0177`)
- [`eq-without-hash`](https://docs.astral.sh/ruff/rules/eq-without-hash) (`PLW1641`)
- [`pytest-parameter-with-default-argument`](https://docs.astral.sh/ruff/rules/pytest-parameter-with-default-argument) (`PT028`)
- [`pytest-warns-too-broad`](https://docs.astral.sh/ruff/rules/pytest-warns-too-broad) (`PT030`)
- [`pytest-warns-with-multiple-statements`](https://docs.astral.sh/ruff/rules/pytest-warns-with-multiple-statements) (`PT031`)
- [`invalid-formatter-suppression-comment`](https://docs.astral.sh/ruff/rules/invalid-formatter-suppression-comment) (`RUF028`)
- [`dataclass-enum`](https://docs.astral.sh/ruff/rules/dataclass-enum) (`RUF049`)
- [`class-with-mixed-type-vars`](https://docs.astral.sh/ruff/rules/class-with-mixed-type-vars) (`RUF053`)
- [`unnecessary-round`](https://docs.astral.sh/ruff/rules/unnecessary-round) (`RUF057`)
- [`starmap-zip`](https://docs.astral.sh/ruff/rules/starmap-zip) (`RUF058`)
- [`non-pep604-annotation-optional`] (`UP045`)
- [`non-pep695-generic-class`](https://docs.astral.sh/ruff/rules/non-pep695-generic-class) (`UP046`)
- [`non-pep695-generic-function`](https://docs.astral.sh/ruff/rules/non-pep695-generic-function) (`UP047`)
- [`private-type-parameter`](https://docs.astral.sh/ruff/rules/private-type-parameter) (`UP049`)
The following behaviors have been stabilized:
- [`collection-literal-concatenation`] (`RUF005`) now recognizes slices, in
addition to list literals and variables.
- The fix for [`readlines-in-for`] (`FURB129`) is now marked as always safe.
- [`if-else-block-instead-of-if-exp`] (`SIM108`) will now further simplify
expressions to use `or` instead of an `if` expression, where possible.
- [`unused-noqa`] (`RUF100`) now checks for file-level `noqa` comments as well
as inline comments.
- [`subprocess-without-shell-equals-true`] (`S603`) now accepts literal strings,
as well as lists and tuples of literal strings, as trusted input.
- [`boolean-type-hint-positional-argument`] (`FBT001`) now applies to types that
include `bool`, like `bool | int` or `typing.Optional[bool]`, in addition to
plain `bool` annotations.
- [`non-pep604-annotation-union`] (`UP007`) has now been split into two rules.
`UP007` now applies only to `typing.Union`, while
[`non-pep604-annotation-optional`] (`UP045`) checks for use of
`typing.Optional`. `UP045` has also been stabilized in this release, but you
may need to update existing `include`, `ignore`, or `noqa` settings to
accommodate this change.
### Preview features
- \[`ruff`\] Check for non-context-manager use of `pytest.raises`, `pytest.warns`, and `pytest.deprecated_call` (`RUF061`) ([#17368](https://github.com/astral-sh/ruff/pull/17368))
- [syntax-errors] Raise unsupported syntax error for template strings prior to Python 3.14 ([#18664](https://github.com/astral-sh/ruff/pull/18664))
### Bug fixes
- Add syntax error when conversion flag does not immediately follow exclamation mark ([#18706](https://github.com/astral-sh/ruff/pull/18706))
- Add trailing space around `readlines` ([#18542](https://github.com/astral-sh/ruff/pull/18542))
- Fix `\r` and `\r\n` handling in t- and f-string debug texts ([#18673](https://github.com/astral-sh/ruff/pull/18673))
- Hug closing `}` when f-string expression has a format specifier ([#18704](https://github.com/astral-sh/ruff/pull/18704))
- \[`flake8-pyi`\] Avoid syntax error in the case of starred and keyword arguments (`PYI059`) ([#18611](https://github.com/astral-sh/ruff/pull/18611))
- \[`flake8-return`\] Fix `RET504` autofix generating a syntax error ([#18428](https://github.com/astral-sh/ruff/pull/18428))
- \[`pep8-naming`\] Suppress fix for `N804` and `N805` if the recommended name is already used ([#18472](https://github.com/astral-sh/ruff/pull/18472))
- \[`pycodestyle`\] Avoid causing a syntax error in expressions spanning multiple lines (`E731`) ([#18479](https://github.com/astral-sh/ruff/pull/18479))
- \[`pyupgrade`\] Suppress `UP008` if `super` is shadowed ([#18688](https://github.com/astral-sh/ruff/pull/18688))
- \[`refurb`\] Parenthesize lambda and ternary expressions (`FURB122`, `FURB142`) ([#18592](https://github.com/astral-sh/ruff/pull/18592))
- \[`ruff`\] Handle extra arguments to `deque` (`RUF037`) ([#18614](https://github.com/astral-sh/ruff/pull/18614))
- \[`ruff`\] Preserve parentheses around `deque` in fix for `unnecessary-empty-iterable-within-deque-call` (`RUF037`) ([#18598](https://github.com/astral-sh/ruff/pull/18598))
- \[`ruff`\] Validate arguments before offering a fix (`RUF056`) ([#18631](https://github.com/astral-sh/ruff/pull/18631))
- \[`ruff`\] Skip fix for `RUF059` if dummy name is already bound ([#18509](https://github.com/astral-sh/ruff/pull/18509))
- \[`pylint`\] Fix `PLW0128` to check assignment targets in square brackets and after asterisks ([#18665](https://github.com/astral-sh/ruff/pull/18665))
### Rule changes
- Fix false positive on mutations in `return` statements (`B909`) ([#18408](https://github.com/astral-sh/ruff/pull/18408))
- Treat `ty:` comments as pragma comments ([#18532](https://github.com/astral-sh/ruff/pull/18532))
- \[`flake8-pyi`\] Apply `custom-typevar-for-self` to string annotations (`PYI019`) ([#18311](https://github.com/astral-sh/ruff/pull/18311))
- \[`pyupgrade`\] Don't offer a fix for `Optional[None]` (`UP007`, `UP045)` ([#18545](https://github.com/astral-sh/ruff/pull/18545))
- \[`pyupgrade`\] Fix `super(__class__, self)` detection (`UP008`) ([#18478](https://github.com/astral-sh/ruff/pull/18478))
- \[`refurb`\] Make the fix for `FURB163` unsafe for `log2`, `log10`, `*args`, and deleted comments ([#18645](https://github.com/astral-sh/ruff/pull/18645))
### Server
- Support cancellation requests ([#18627](https://github.com/astral-sh/ruff/pull/18627))
### Documentation
- Drop confusing second `*` from glob pattern example for `per-file-target-version` ([#18709](https://github.com/astral-sh/ruff/pull/18709))
- Update Neovim configuration examples ([#18491](https://github.com/astral-sh/ruff/pull/18491))
- \[`pylint`\] De-emphasize `__hash__ = Parent.__hash__` (`PLW1641`) ([#18613](https://github.com/astral-sh/ruff/pull/18613))
- \[`refurb`\] Add a note about float literal handling (`FURB157`) ([#18615](https://github.com/astral-sh/ruff/pull/18615))
## 0.12.1
### Preview features
- \[`flake8-errmsg`\] Extend `EM101` to support byte strings ([#18867](https://github.com/astral-sh/ruff/pull/18867))
- \[`flake8-use-pathlib`\] Add autofix for `PTH202` ([#18763](https://github.com/astral-sh/ruff/pull/18763))
- \[`pygrep-hooks`\] Add `AsyncMock` methods to `invalid-mock-access` (`PGH005`) ([#18547](https://github.com/astral-sh/ruff/pull/18547))
- \[`pylint`\] Ignore `__init__.py` files in (`PLC0414`) ([#18400](https://github.com/astral-sh/ruff/pull/18400))
- \[`ruff`\] Trigger `RUF037` for empty string and byte strings ([#18862](https://github.com/astral-sh/ruff/pull/18862))
- [formatter] Fix missing blank lines before decorated classes in `.pyi` files ([#18888](https://github.com/astral-sh/ruff/pull/18888))
### Bug fixes
- Avoid generating diagnostics with per-file ignores ([#18801](https://github.com/astral-sh/ruff/pull/18801))
- Handle parenthesized arguments in `remove_argument` ([#18805](https://github.com/astral-sh/ruff/pull/18805))
- \[`flake8-logging`\] Avoid false positive for `exc_info=True` outside `logger.exception` (`LOG014`) ([#18737](https://github.com/astral-sh/ruff/pull/18737))
- \[`flake8-pytest-style`\] Enforce `pytest` import for decorators ([#18779](https://github.com/astral-sh/ruff/pull/18779))
- \[`flake8-pytest-style`\] Mark autofix for `PT001` and `PT023` as unsafe if there's comments in the decorator ([#18792](https://github.com/astral-sh/ruff/pull/18792))
- \[`flake8-pytest-style`\] `PT001`/`PT023` fix makes syntax error on parenthesized decorator ([#18782](https://github.com/astral-sh/ruff/pull/18782))
- \[`flake8-raise`\] Make fix unsafe if it deletes comments (`RSE102`) ([#18788](https://github.com/astral-sh/ruff/pull/18788))
- \[`flake8-simplify`\] Fix `SIM911` autofix creating a syntax error ([#18793](https://github.com/astral-sh/ruff/pull/18793))
- \[`flake8-simplify`\] Fix false negatives for shadowed bindings (`SIM910`, `SIM911`) ([#18794](https://github.com/astral-sh/ruff/pull/18794))
- \[`flake8-simplify`\] Preserve original behavior for `except ()` and bare `except` (`SIM105`) ([#18213](https://github.com/astral-sh/ruff/pull/18213))
- \[`flake8-pyi`\] Fix `PYI041`'s fix causing `TypeError` with `None | None | ...` ([#18637](https://github.com/astral-sh/ruff/pull/18637))
- \[`perflint`\] Fix `PERF101` autofix creating a syntax error and mark autofix as unsafe if there are comments in the `list` call expr ([#18803](https://github.com/astral-sh/ruff/pull/18803))
- \[`perflint`\] Fix false negative in `PERF401` ([#18866](https://github.com/astral-sh/ruff/pull/18866))
- \[`pylint`\] Avoid flattening nested `min`/`max` when outer call has single argument (`PLW3301`) ([#16885](https://github.com/astral-sh/ruff/pull/16885))
- \[`pylint`\] Fix `PLC2801` autofix creating a syntax error ([#18857](https://github.com/astral-sh/ruff/pull/18857))
- \[`pylint`\] Mark `PLE0241` autofix as unsafe if there's comments in the base classes ([#18832](https://github.com/astral-sh/ruff/pull/18832))
- \[`pylint`\] Suppress `PLE2510`/`PLE2512`/`PLE2513`/`PLE2514`/`PLE2515` autofix if the text contains an odd number of backslashes ([#18856](https://github.com/astral-sh/ruff/pull/18856))
- \[`refurb`\] Detect more exotic float literals in `FURB164` ([#18925](https://github.com/astral-sh/ruff/pull/18925))
- \[`refurb`\] Fix `FURB163` autofix creating a syntax error for `yield` expressions ([#18756](https://github.com/astral-sh/ruff/pull/18756))
- \[`refurb`\] Mark `FURB129` autofix as unsafe if there's comments in the `readlines` call ([#18858](https://github.com/astral-sh/ruff/pull/18858))
- \[`ruff`\] Fix false positives and negatives in `RUF010` ([#18690](https://github.com/astral-sh/ruff/pull/18690))
- Fix casing of `analyze.direction` variant names ([#18892](https://github.com/astral-sh/ruff/pull/18892))
### Rule changes
- Fix f-string interpolation escaping in generated fixes ([#18882](https://github.com/astral-sh/ruff/pull/18882))
- \[`flake8-return`\] Mark `RET501` fix unsafe if comments are inside ([#18780](https://github.com/astral-sh/ruff/pull/18780))
- \[`flake8-async`\] Fix detection for large integer sleep durations in `ASYNC116` rule ([#18767](https://github.com/astral-sh/ruff/pull/18767))
- \[`flake8-async`\] Mark autofix for `ASYNC115` as unsafe if the call expression contains comments ([#18753](https://github.com/astral-sh/ruff/pull/18753))
- \[`flake8-bugbear`\] Mark autofix for `B004` as unsafe if the `hasattr` call expr contains comments ([#18755](https://github.com/astral-sh/ruff/pull/18755))
- \[`flake8-comprehension`\] Mark autofix for `C420` as unsafe if there's comments inside the dict comprehension ([#18768](https://github.com/astral-sh/ruff/pull/18768))
- \[`flake8-comprehensions`\] Handle template strings for comprehension fixes ([#18710](https://github.com/astral-sh/ruff/pull/18710))
- \[`flake8-future-annotations`\] Add autofix (`FA100`) ([#18903](https://github.com/astral-sh/ruff/pull/18903))
- \[`pyflakes`\] Mark `F504`/`F522`/`F523` autofix as unsafe if there's a call with side effect ([#18839](https://github.com/astral-sh/ruff/pull/18839))
- \[`pylint`\] Allow fix with comments and document performance implications (`PLW3301`) ([#18936](https://github.com/astral-sh/ruff/pull/18936))
- \[`pylint`\] Detect more exotic `NaN` literals in `PLW0177` ([#18630](https://github.com/astral-sh/ruff/pull/18630))
- \[`pylint`\] Fix `PLC1802` autofix creating a syntax error and mark autofix as unsafe if there's comments in the `len` call ([#18836](https://github.com/astral-sh/ruff/pull/18836))
- \[`pyupgrade`\] Extend version detection to include `sys.version_info.major` (`UP036`) ([#18633](https://github.com/astral-sh/ruff/pull/18633))
- \[`ruff`\] Add lint rule `RUF064` for calling `chmod` with non-octal integers ([#18541](https://github.com/astral-sh/ruff/pull/18541))
- \[`ruff`\] Added `cls.__dict__.get('__annotations__')` check (`RUF063`) ([#18233](https://github.com/astral-sh/ruff/pull/18233))
- \[`ruff`\] Frozen `dataclass` default should be valid (`RUF009`) ([#18735](https://github.com/astral-sh/ruff/pull/18735))
### Server
- Consider virtual path for various server actions ([#18910](https://github.com/astral-sh/ruff/pull/18910))
### Documentation
- Add fix safety sections ([#18940](https://github.com/astral-sh/ruff/pull/18940),[#18841](https://github.com/astral-sh/ruff/pull/18841),[#18802](https://github.com/astral-sh/ruff/pull/18802),[#18837](https://github.com/astral-sh/ruff/pull/18837),[#18800](https://github.com/astral-sh/ruff/pull/18800),[#18415](https://github.com/astral-sh/ruff/pull/18415),[#18853](https://github.com/astral-sh/ruff/pull/18853),[#18842](https://github.com/astral-sh/ruff/pull/18842))
- Use updated pre-commit id ([#18718](https://github.com/astral-sh/ruff/pull/18718))
- \[`perflint`\] Small docs improvement to `PERF401` ([#18786](https://github.com/astral-sh/ruff/pull/18786))
- \[`pyupgrade`\]: Use `super()`, not `__super__` in error messages (`UP008`) ([#18743](https://github.com/astral-sh/ruff/pull/18743))
- \[`flake8-pie`\] Small docs fix to `PIE794` ([#18829](https://github.com/astral-sh/ruff/pull/18829))
- \[`flake8-pyi`\] Correct `collections-named-tuple` example to use PascalCase assignment ([#16884](https://github.com/astral-sh/ruff/pull/16884))
- \[`flake8-pie`\] Add note on type checking benefits to `unnecessary-dict-kwargs` (`PIE804`) ([#18666](https://github.com/astral-sh/ruff/pull/18666))
- \[`pycodestyle`\] Clarify PEP 8 relationship to `whitespace-around-operator` rules ([#18870](https://github.com/astral-sh/ruff/pull/18870))
### Other changes
- Disallow newlines in format specifiers of single quoted f- or t-strings ([#18708](https://github.com/astral-sh/ruff/pull/18708))
- \[`flake8-logging`\] Add fix safety section to `LOG002` ([#18840](https://github.com/astral-sh/ruff/pull/18840))
- \[`pyupgrade`\] Add fix safety section to `UP010` ([#18838](https://github.com/astral-sh/ruff/pull/18838))
## 0.12.2
### Preview features
- \[`flake8-pyi`\] Expand `Optional[A]` to `A | None` (`PYI016`) ([#18572](https://github.com/astral-sh/ruff/pull/18572))
- \[`pyupgrade`\] Mark `UP008` fix safe if no comments are in range ([#18683](https://github.com/astral-sh/ruff/pull/18683))
### Bug fixes
- \[`flake8-comprehensions`\] Fix `C420` to prepend whitespace when needed ([#18616](https://github.com/astral-sh/ruff/pull/18616))
- \[`perflint`\] Fix `PERF403` panic on attribute or subscription loop variable ([#19042](https://github.com/astral-sh/ruff/pull/19042))
- \[`pydocstyle`\] Fix `D413` infinite loop for parenthesized docstring ([#18930](https://github.com/astral-sh/ruff/pull/18930))
- \[`pylint`\] Fix `PLW0108` autofix introducing a syntax error when the lambda's body contains an assignment expression ([#18678](https://github.com/astral-sh/ruff/pull/18678))
- \[`refurb`\] Fix false positive on empty tuples (`FURB168`) ([#19058](https://github.com/astral-sh/ruff/pull/19058))
- \[`ruff`\] Allow more `field` calls from `attrs` (`RUF009`) ([#19021](https://github.com/astral-sh/ruff/pull/19021))
- \[`ruff`\] Fix syntax error introduced for an empty string followed by a u-prefixed string (`UP025`) ([#18899](https://github.com/astral-sh/ruff/pull/18899))
### Rule changes
- \[`flake8-executable`\] Allow `uvx` in shebang line (`EXE003`) ([#18967](https://github.com/astral-sh/ruff/pull/18967))
- \[`pandas`\] Avoid flagging `PD002` if `pandas` is not imported ([#18963](https://github.com/astral-sh/ruff/pull/18963))
- \[`pyupgrade`\] Avoid PEP-604 unions with `typing.NamedTuple` (`UP007`, `UP045`) ([#18682](https://github.com/astral-sh/ruff/pull/18682))
### Documentation
- Document link between `import-outside-top-level (PLC0415)` and `lint.flake8-tidy-imports.banned-module-level-imports` ([#18733](https://github.com/astral-sh/ruff/pull/18733))
- Fix description of the `format.skip-magic-trailing-comma` example ([#19095](https://github.com/astral-sh/ruff/pull/19095))
- \[`airflow`\] Make `AIR302` example error out-of-the-box ([#18988](https://github.com/astral-sh/ruff/pull/18988))
- \[`airflow`\] Make `AIR312` example error out-of-the-box ([#18989](https://github.com/astral-sh/ruff/pull/18989))
- \[`flake8-annotations`\] Make `ANN401` example error out-of-the-box ([#18974](https://github.com/astral-sh/ruff/pull/18974))
- \[`flake8-async`\] Make `ASYNC100` example error out-of-the-box ([#18993](https://github.com/astral-sh/ruff/pull/18993))
- \[`flake8-async`\] Make `ASYNC105` example error out-of-the-box ([#19002](https://github.com/astral-sh/ruff/pull/19002))
- \[`flake8-async`\] Make `ASYNC110` example error out-of-the-box ([#18975](https://github.com/astral-sh/ruff/pull/18975))
- \[`flake8-async`\] Make `ASYNC210` example error out-of-the-box ([#18977](https://github.com/astral-sh/ruff/pull/18977))
- \[`flake8-async`\] Make `ASYNC220`, `ASYNC221`, and `ASYNC222` examples error out-of-the-box ([#18978](https://github.com/astral-sh/ruff/pull/18978))
- \[`flake8-async`\] Make `ASYNC251` example error out-of-the-box ([#18990](https://github.com/astral-sh/ruff/pull/18990))
- \[`flake8-bandit`\] Make `S201` example error out-of-the-box ([#19017](https://github.com/astral-sh/ruff/pull/19017))
- \[`flake8-bandit`\] Make `S604` and `S609` examples error out-of-the-box ([#19049](https://github.com/astral-sh/ruff/pull/19049))
- \[`flake8-bugbear`\] Make `B028` example error out-of-the-box ([#19054](https://github.com/astral-sh/ruff/pull/19054))
- \[`flake8-bugbear`\] Make `B911` example error out-of-the-box ([#19051](https://github.com/astral-sh/ruff/pull/19051))
- \[`flake8-datetimez`\] Make `DTZ011` example error out-of-the-box ([#19055](https://github.com/astral-sh/ruff/pull/19055))
- \[`flake8-datetimez`\] Make `DTZ901` example error out-of-the-box ([#19056](https://github.com/astral-sh/ruff/pull/19056))
- \[`flake8-pyi`\] Make `PYI032` example error out-of-the-box ([#19061](https://github.com/astral-sh/ruff/pull/19061))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI014`, `PYI015`) ([#19097](https://github.com/astral-sh/ruff/pull/19097))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI042`) ([#19101](https://github.com/astral-sh/ruff/pull/19101))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI059`) ([#19080](https://github.com/astral-sh/ruff/pull/19080))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI062`) ([#19079](https://github.com/astral-sh/ruff/pull/19079))
- \[`flake8-pytest-style`\] Make example error out-of-the-box (`PT023`) ([#19104](https://github.com/astral-sh/ruff/pull/19104))
- \[`flake8-pytest-style`\] Make example error out-of-the-box (`PT030`) ([#19105](https://github.com/astral-sh/ruff/pull/19105))
- \[`flake8-quotes`\] Make example error out-of-the-box (`Q003`) ([#19106](https://github.com/astral-sh/ruff/pull/19106))
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM110`) ([#19113](https://github.com/astral-sh/ruff/pull/19113))
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM113`) ([#19109](https://github.com/astral-sh/ruff/pull/19109))
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM401`) ([#19110](https://github.com/astral-sh/ruff/pull/19110))
- \[`pyflakes`\] Fix backslash in docs (`F621`) ([#19098](https://github.com/astral-sh/ruff/pull/19098))
- \[`pylint`\] Fix `PLC0415` example ([#18970](https://github.com/astral-sh/ruff/pull/18970))
## 0.12.3
### Preview features
- \[`flake8-bugbear`\] Support non-context-manager calls in `B017` ([#19063](https://github.com/astral-sh/ruff/pull/19063))
- \[`flake8-use-pathlib`\] Add autofixes for `PTH100`, `PTH106`, `PTH107`, `PTH108`, `PTH110`, `PTH111`, `PTH112`, `PTH113`, `PTH114`, `PTH115`, `PTH117`, `PTH119`, `PTH120` ([#19213](https://github.com/astral-sh/ruff/pull/19213))
- \[`flake8-use-pathlib`\] Add autofixes for `PTH203`, `PTH204`, `PTH205` ([#18922](https://github.com/astral-sh/ruff/pull/18922))
### Bug fixes
- \[`flake8-return`\] Fix false-positive for variables used inside nested functions in `RET504` ([#18433](https://github.com/astral-sh/ruff/pull/18433))
- Treat form feed as valid whitespace before a line continuation ([#19220](https://github.com/astral-sh/ruff/pull/19220))
- \[`flake8-type-checking`\] Fix syntax error introduced by fix (`TC008`) ([#19150](https://github.com/astral-sh/ruff/pull/19150))
- \[`pyupgrade`\] Keyword arguments in `super` should suppress the `UP008` fix ([#19131](https://github.com/astral-sh/ruff/pull/19131))
### Documentation
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI007`, `PYI008`) ([#19103](https://github.com/astral-sh/ruff/pull/19103))
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM116`) ([#19111](https://github.com/astral-sh/ruff/pull/19111))
- \[`flake8-type-checking`\] Make example error out-of-the-box (`TC001`) ([#19151](https://github.com/astral-sh/ruff/pull/19151))
- \[`flake8-use-pathlib`\] Make example error out-of-the-box (`PTH210`) ([#19189](https://github.com/astral-sh/ruff/pull/19189))
- \[`pycodestyle`\] Make example error out-of-the-box (`E272`) ([#19191](https://github.com/astral-sh/ruff/pull/19191))
- \[`pycodestyle`\] Make example not raise unnecessary `SyntaxError` (`E114`) ([#19190](https://github.com/astral-sh/ruff/pull/19190))
- \[`pydoclint`\] Make example error out-of-the-box (`DOC501`) ([#19218](https://github.com/astral-sh/ruff/pull/19218))
- \[`pylint`, `pyupgrade`\] Fix syntax errors in examples (`PLW1501`, `UP028`) ([#19127](https://github.com/astral-sh/ruff/pull/19127))
- \[`pylint`\] Update `missing-maxsplit-arg` docs and error to suggest proper usage (`PLC0207`) ([#18949](https://github.com/astral-sh/ruff/pull/18949))
- \[`flake8-bandit`\] Make example error out-of-the-box (`S412`) ([#19241](https://github.com/astral-sh/ruff/pull/19241))
## 0.12.4
### Preview features
- \[`flake8-type-checking`, `pyupgrade`, `ruff`\] Add `from __future__ import annotations` when it would allow new fixes (`TC001`, `TC002`, `TC003`, `UP037`, `RUF013`) ([#19100](https://github.com/astral-sh/ruff/pull/19100))
- \[`flake8-use-pathlib`\] Add autofix for `PTH109` ([#19245](https://github.com/astral-sh/ruff/pull/19245))
- \[`pylint`\] Detect indirect `pathlib.Path` usages for `unspecified-encoding` (`PLW1514`) ([#19304](https://github.com/astral-sh/ruff/pull/19304))
### Bug fixes
- \[`flake8-bugbear`\] Fix `B017` false negatives for keyword exception arguments ([#19217](https://github.com/astral-sh/ruff/pull/19217))
- \[`flake8-use-pathlib`\] Fix false negative on direct `Path()` instantiation (`PTH210`) ([#19388](https://github.com/astral-sh/ruff/pull/19388))
- \[`flake8-django`\] Fix `DJ008` false positive for abstract models with type-annotated `abstract` field ([#19221](https://github.com/astral-sh/ruff/pull/19221))
- \[`isort`\] Fix `I002` import insertion after docstring with multiple string statements ([#19222](https://github.com/astral-sh/ruff/pull/19222))
- \[`isort`\] Treat form feed as valid whitespace before a semicolon ([#19343](https://github.com/astral-sh/ruff/pull/19343))
- \[`pydoclint`\] Fix `SyntaxError` from fixes with line continuations (`D201`, `D202`) ([#19246](https://github.com/astral-sh/ruff/pull/19246))
- \[`refurb`\] `FURB164` fix should validate arguments and should usually be marked unsafe ([#19136](https://github.com/astral-sh/ruff/pull/19136))
### Rule changes
- \[`flake8-use-pathlib`\] Skip single dots for `invalid-pathlib-with-suffix` (`PTH210`) on versions >= 3.14 ([#19331](https://github.com/astral-sh/ruff/pull/19331))
- \[`pep8_naming`\] Avoid false positives on standard library functions with uppercase names (`N802`) ([#18907](https://github.com/astral-sh/ruff/pull/18907))
- \[`pycodestyle`\] Handle brace escapes for t-strings in logical lines ([#19358](https://github.com/astral-sh/ruff/pull/19358))
- \[`pylint`\] Extend invalid string character rules to include t-strings ([#19355](https://github.com/astral-sh/ruff/pull/19355))
- \[`ruff`\] Allow `strict` kwarg when checking for `starmap-zip` (`RUF058`) in Python 3.14+ ([#19333](https://github.com/astral-sh/ruff/pull/19333))
### Documentation
- \[`flake8-type-checking`\] Make `TC010` docs example more realistic ([#19356](https://github.com/astral-sh/ruff/pull/19356))
- Make more documentation examples error out-of-the-box ([#19288](https://github.com/astral-sh/ruff/pull/19288),[#19272](https://github.com/astral-sh/ruff/pull/19272),[#19291](https://github.com/astral-sh/ruff/pull/19291),[#19296](https://github.com/astral-sh/ruff/pull/19296),[#19292](https://github.com/astral-sh/ruff/pull/19292),[#19295](https://github.com/astral-sh/ruff/pull/19295),[#19297](https://github.com/astral-sh/ruff/pull/19297),[#19309](https://github.com/astral-sh/ruff/pull/19309))
## 0.12.5
### Preview features
- \[`flake8-use-pathlib`\] Add autofix for `PTH101`, `PTH104`, `PTH105`, `PTH121` ([#19404](https://github.com/astral-sh/ruff/pull/19404))
- \[`ruff`\] Support byte strings (`RUF055`) ([#18926](https://github.com/astral-sh/ruff/pull/18926))
### Bug fixes
- Fix `unreachable` panic in parser ([#19183](https://github.com/astral-sh/ruff/pull/19183))
- \[`flake8-pyi`\] Skip fix if all `Union` members are `None` (`PYI016`) ([#19416](https://github.com/astral-sh/ruff/pull/19416))
- \[`perflint`\] Parenthesize generator expressions (`PERF401`) ([#19325](https://github.com/astral-sh/ruff/pull/19325))
- \[`pylint`\] Handle empty comments after line continuation (`PLR2044`) ([#19405](https://github.com/astral-sh/ruff/pull/19405))
### Rule changes
- \[`pep8-naming`\] Fix `N802` false positives for `CGIHTTPRequestHandler` and `SimpleHTTPRequestHandler` ([#19432](https://github.com/astral-sh/ruff/pull/19432))
## 0.12.6
### Preview features
- \[`flake8-commas`\] Add support for trailing comma checks in type parameter lists (`COM812`, `COM819`) ([#19390](https://github.com/astral-sh/ruff/pull/19390))
- \[`pylint`\] Implement auto-fix for `missing-maxsplit-arg` (`PLC0207`) ([#19387](https://github.com/astral-sh/ruff/pull/19387))
- \[`ruff`\] Offer fixes for `RUF039` in more cases ([#19065](https://github.com/astral-sh/ruff/pull/19065))
### Bug fixes
- Support `.pyi` files in ruff analyze graph ([#19611](https://github.com/astral-sh/ruff/pull/19611))
- \[`flake8-pyi`\] Preserve inline comment in ellipsis removal (`PYI013`) ([#19399](https://github.com/astral-sh/ruff/pull/19399))
- \[`perflint`\] Ignore rule if target is `global` or `nonlocal` (`PERF401`) ([#19539](https://github.com/astral-sh/ruff/pull/19539))
- \[`pyupgrade`\] Fix `UP030` to avoid modifying double curly braces in format strings ([#19378](https://github.com/astral-sh/ruff/pull/19378))
- \[`refurb`\] Ignore decorated functions for `FURB118` ([#19339](https://github.com/astral-sh/ruff/pull/19339))
- \[`refurb`\] Mark `int` and `bool` cases for `Decimal.from_float` as safe fixes (`FURB164`) ([#19468](https://github.com/astral-sh/ruff/pull/19468))
- \[`ruff`\] Fix `RUF033` for named default expressions ([#19115](https://github.com/astral-sh/ruff/pull/19115))
### Rule changes
- \[`flake8-blind-except`\] Change `BLE001` to permit `logging.critical(..., exc_info=True)` ([#19520](https://github.com/astral-sh/ruff/pull/19520))
### Performance
- Add support for specifying minimum dots in detected string imports ([#19538](https://github.com/astral-sh/ruff/pull/19538))
## 0.12.7
This is a follow-up release to 0.12.6. Because of an issue in the package metadata, 0.12.6 failed to publish fully to PyPI and has been yanked. Similarly, there is no GitHub release or Git tag for 0.12.6. The contents of the 0.12.7 release are identical to 0.12.6, except for the updated metadata.
## 0.12.8
### Preview features
- \[`flake8-use-pathlib`\] Expand `PTH201` to check all `PurePath` subclasses ([#19440](https://github.com/astral-sh/ruff/pull/19440))
### Bug fixes
- \[`flake8-blind-except`\] Change `BLE001` to correctly parse exception tuples ([#19747](https://github.com/astral-sh/ruff/pull/19747))
- \[`flake8-errmsg`\] Exclude `typing.cast` from `EM101` ([#19656](https://github.com/astral-sh/ruff/pull/19656))
- \[`flake8-simplify`\] Fix raw string handling in `SIM905` for embedded quotes ([#19591](https://github.com/astral-sh/ruff/pull/19591))
- \[`flake8-import-conventions`\] Avoid false positives for NFKC-normalized `__debug__` import aliases in `ICN001` ([#19411](https://github.com/astral-sh/ruff/pull/19411))
- \[`isort`\] Fix syntax error after docstring ending with backslash (`I002`) ([#19505](https://github.com/astral-sh/ruff/pull/19505))
- \[`pylint`\] Mark `PLC0207` fixes as unsafe when `*args` unpacking is present ([#19679](https://github.com/astral-sh/ruff/pull/19679))
- \[`pyupgrade`\] Prevent infinite loop with `I002` (`UP010`, `UP035`) ([#19413](https://github.com/astral-sh/ruff/pull/19413))
- \[`ruff`\] Parenthesize generator expressions in f-strings (`RUF010`) ([#19434](https://github.com/astral-sh/ruff/pull/19434))
### Rule changes
- \[`eradicate`\] Don't flag `pyrefly` pragmas as unused code (`ERA001`) ([#19731](https://github.com/astral-sh/ruff/pull/19731))
### Documentation
- Replace "associative" with "commutative" in docs for `RUF036` ([#19706](https://github.com/astral-sh/ruff/pull/19706))
- Fix copy and line separator colors in dark mode ([#19630](https://github.com/astral-sh/ruff/pull/19630))
- Fix link to `typing` documentation ([#19648](https://github.com/astral-sh/ruff/pull/19648))
- \[`refurb`\] Make more examples error out-of-the-box ([#19695](https://github.com/astral-sh/ruff/pull/19695),[#19673](https://github.com/astral-sh/ruff/pull/19673),[#19672](https://github.com/astral-sh/ruff/pull/19672))
### Other changes
- Include column numbers in GitLab output format ([#19708](https://github.com/astral-sh/ruff/pull/19708))
- Always expand tabs to four spaces in diagnostics ([#19618](https://github.com/astral-sh/ruff/pull/19618))
- Update pre-commit's `ruff` id ([#19654](https://github.com/astral-sh/ruff/pull/19654))
## 0.12.9
### Preview features
- \[`airflow`\] Add check for `airflow.secrets.cache.SecretCache` (`AIR301`) ([#17707](https://github.com/astral-sh/ruff/pull/17707))
- \[`ruff`\] Offer a safe fix for multi-digit zeros (`RUF064`) ([#19847](https://github.com/astral-sh/ruff/pull/19847))
### Bug fixes
- \[`flake8-blind-except`\] Fix `BLE001` false-positive on `raise ... from None` ([#19755](https://github.com/astral-sh/ruff/pull/19755))
- \[`flake8-comprehensions`\] Fix false positive for `C420` with attribute, subscript, or slice assignment targets ([#19513](https://github.com/astral-sh/ruff/pull/19513))
- \[`flake8-simplify`\] Fix handling of U+001C..U+001F whitespace (`SIM905`) ([#19849](https://github.com/astral-sh/ruff/pull/19849))
### Rule changes
- \[`pylint`\] Use lowercase hex characters to match the formatter (`PLE2513`) ([#19808](https://github.com/astral-sh/ruff/pull/19808))
### Documentation
- Fix `lint.future-annotations` link ([#19876](https://github.com/astral-sh/ruff/pull/19876))
### Other changes
- Build `riscv64` binaries for release ([#19819](https://github.com/astral-sh/ruff/pull/19819))
- Add rule code to error description in GitLab output ([#19896](https://github.com/astral-sh/ruff/pull/19896))
- Improve rendering of the `full` output format ([#19415](https://github.com/astral-sh/ruff/pull/19415))
Below is an example diff for [`F401`](https://docs.astral.sh/ruff/rules/unused-import/):
```diff
-unused.py:8:19: F401 [*] `pathlib` imported but unused
+F401 [*] `pathlib` imported but unused
+ --> unused.py:8:19
|
7 | # Unused, _not_ marked as required (due to the alias).
8 | import pathlib as non_alias
- | ^^^^^^^^^ F401
+ | ^^^^^^^^^
9 |
10 | # Unused, marked as required.
|
- = help: Remove unused import: `pathlib`
+help: Remove unused import: `pathlib`
```
For now, the primary difference is the movement of the filename, line number, and column information to a second line in the header. This new representation will allow us to make further additions to Ruff's diagnostics, such as adding sub-diagnostics and multiple annotations to the same snippet.
## 0.12.10
### Preview features
- \[`flake8-simplify`\] Implement fix for `maxsplit` without separator (`SIM905`) ([#19851](https://github.com/astral-sh/ruff/pull/19851))
- \[`flake8-use-pathlib`\] Add fixes for `PTH102` and `PTH103` ([#19514](https://github.com/astral-sh/ruff/pull/19514))
### Bug fixes
- \[`isort`\] Handle multiple continuation lines after module docstring (`I002`) ([#19818](https://github.com/astral-sh/ruff/pull/19818))
- \[`pyupgrade`\] Avoid reporting `__future__` features as unnecessary when they are used (`UP010`) ([#19769](https://github.com/astral-sh/ruff/pull/19769))
- \[`pyupgrade`\] Handle nested `Optional`s (`UP045`) ([#19770](https://github.com/astral-sh/ruff/pull/19770))
### Rule changes
- \[`pycodestyle`\] Make `E731` fix unsafe instead of display-only for class assignments ([#19700](https://github.com/astral-sh/ruff/pull/19700))
- \[`pyflakes`\] Add secondary annotation showing previous definition (`F811`) ([#19900](https://github.com/astral-sh/ruff/pull/19900))
### Documentation
- Fix description of global config file discovery strategy ([#19188](https://github.com/astral-sh/ruff/pull/19188))
- Update outdated links to <https://typing.python.org/en/latest/source/stubs.html> ([#19992](https://github.com/astral-sh/ruff/pull/19992))
- \[`flake8-annotations`\] Remove unused import in example (`ANN401`) ([#20000](https://github.com/astral-sh/ruff/pull/20000))
## 0.12.11
### Preview features
- \[`airflow`\] Extend `AIR311` and `AIR312` rules ([#20082](https://github.com/astral-sh/ruff/pull/20082))
- \[`airflow`\] Replace wrong path `airflow.io.storage` with `airflow.io.store` (`AIR311`) ([#20081](https://github.com/astral-sh/ruff/pull/20081))
- \[`flake8-async`\] Implement `blocking-http-call-httpx-in-async-function` (`ASYNC212`) ([#20091](https://github.com/astral-sh/ruff/pull/20091))
- \[`flake8-logging-format`\] Add auto-fix for f-string logging calls (`G004`) ([#19303](https://github.com/astral-sh/ruff/pull/19303))
- \[`flake8-use-pathlib`\] Add autofix for `PTH211` ([#20009](https://github.com/astral-sh/ruff/pull/20009))
- \[`flake8-use-pathlib`\] Make `PTH100` fix unsafe because it can change behavior ([#20100](https://github.com/astral-sh/ruff/pull/20100))
### Bug fixes
- \[`pyflakes`, `pylint`\] Fix false positives caused by `__class__` cell handling (`F841`, `PLE0117`) ([#20048](https://github.com/astral-sh/ruff/pull/20048))
- \[`pyflakes`\] Fix `allowed-unused-imports` matching for top-level modules (`F401`) ([#20115](https://github.com/astral-sh/ruff/pull/20115))
- \[`ruff`\] Fix false positive for t-strings in `default-factory-kwarg` (`RUF026`) ([#20032](https://github.com/astral-sh/ruff/pull/20032))
- \[`ruff`\] Preserve relative whitespace in multi-line expressions (`RUF033`) ([#19647](https://github.com/astral-sh/ruff/pull/19647))
### Rule changes
- \[`ruff`\] Handle empty t-strings in `unnecessary-empty-iterable-within-deque-call` (`RUF037`) ([#20045](https://github.com/astral-sh/ruff/pull/20045))
### Documentation
- Fix incorrect `D413` links in docstrings convention FAQ ([#20089](https://github.com/astral-sh/ruff/pull/20089))
- \[`flake8-use-pathlib`\] Update links to the table showing the correspondence between `os` and `pathlib` ([#20103](https://github.com/astral-sh/ruff/pull/20103))
## 0.12.12
### Preview features
- Show fixes by default ([#19919](https://github.com/astral-sh/ruff/pull/19919))
- \[`airflow`\] Convert `DatasetOrTimeSchedule(datasets=...)` to `AssetOrTimeSchedule(assets=...)` (`AIR311`) ([#20202](https://github.com/astral-sh/ruff/pull/20202))
- \[`airflow`\] Improve the `AIR002` error message ([#20173](https://github.com/astral-sh/ruff/pull/20173))
- \[`airflow`\] Move `airflow.operators.postgres_operator.Mapping` from `AIR302` to `AIR301` ([#20172](https://github.com/astral-sh/ruff/pull/20172))
- \[`flake8-async`\] Implement `blocking-input` rule (`ASYNC250`) ([#20122](https://github.com/astral-sh/ruff/pull/20122))
- \[`flake8-use-pathlib`\] Make `PTH119` and `PTH120` fixes unsafe because they can change behavior ([#20118](https://github.com/astral-sh/ruff/pull/20118))
- \[`pylint`\] Add U+061C to `PLE2502` ([#20106](https://github.com/astral-sh/ruff/pull/20106))
- \[`ruff`\] Fix false negative for empty f-strings in `deque` calls (`RUF037`) ([#20109](https://github.com/astral-sh/ruff/pull/20109))
### Bug fixes
- Less confidently mark f-strings as empty when inferring truthiness ([#20152](https://github.com/astral-sh/ruff/pull/20152))
- \[`fastapi`\] Fix false positive for paths with spaces around parameters (`FAST003`) ([#20077](https://github.com/astral-sh/ruff/pull/20077))
- \[`flake8-comprehensions`\] Skip `C417` when lambda contains `yield`/`yield from` ([#20201](https://github.com/astral-sh/ruff/pull/20201))
- \[`perflint`\] Handle tuples in dictionary comprehensions (`PERF403`) ([#19934](https://github.com/astral-sh/ruff/pull/19934))
### Rule changes
- \[`pycodestyle`\] Preserve return type annotation for `ParamSpec` (`E731`) ([#20108](https://github.com/astral-sh/ruff/pull/20108))
### Documentation
- Add fix safety sections to docs ([#17490](https://github.com/astral-sh/ruff/pull/17490),[#17499](https://github.com/astral-sh/ruff/pull/17499))
[`boolean-type-hint-positional-argument`]: https://docs.astral.sh/ruff/rules/boolean-type-hint-positional-argument
[`collection-literal-concatenation`]: https://docs.astral.sh/ruff/rules/collection-literal-concatenation
[`if-else-block-instead-of-if-exp`]: https://docs.astral.sh/ruff/rules/if-else-block-instead-of-if-exp
[`non-pep604-annotation-optional`]: https://docs.astral.sh/ruff/rules/non-pep604-annotation-optional
[`non-pep604-annotation-union`]: https://docs.astral.sh/ruff/rules/non-pep604-annotation-union
[`readlines-in-for`]: https://docs.astral.sh/ruff/rules/readlines-in-for
[`subprocess-without-shell-equals-true`]: https://docs.astral.sh/ruff/rules/subprocess-without-shell-equals-true
[`unused-noqa`]: https://docs.astral.sh/ruff/rules/unused-noqa

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.12.11"
version = "0.13.0"
publish = true
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -1489,6 +1489,8 @@ fn deprecated_direct() {
#[test]
fn deprecated_multiple_direct() {
// Multiple deprecated rules selected by exact code should be included
// but a warning should be displayed
let mut cmd = RuffCheck::default()
.args(["--select", "RUF920", "--select", "RUF921"])
.build();
@@ -1516,16 +1518,10 @@ fn deprecated_indirect() {
// since it is not a "direct" selection
let mut cmd = RuffCheck::default().args(["--select", "RUF92"]).build();
assert_cmd_snapshot!(cmd, @r"
success: false
exit_code: 1
success: true
exit_code: 0
----- stdout -----
RUF920 Hey this is a deprecated test rule.
--> -:1:1
RUF921 Hey this is another deprecated test rule.
--> -:1:1
Found 2 errors.
All checks passed!
----- stderr -----
");
@@ -2155,16 +2151,10 @@ extend-safe-fixes = ["RUF9"]
RUF903 Hey this is a stable test rule with a display only fix.
--> -:1:1
RUF920 Hey this is a deprecated test rule.
--> -:1:1
RUF921 Hey this is another deprecated test rule.
--> -:1:1
RUF950 Hey this is a test rule that was redirected from another.
--> -:1:1
Found 7 errors.
Found 5 errors.
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
----- stderr -----

View File

@@ -5780,28 +5780,6 @@ match 42: # invalid-syntax
Ok(())
}
#[test]
fn future_annotations_preview_warning() {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(["--config", "lint.future-annotations = true"])
.args(["--select", "F"])
.arg("--no-preview")
.arg("-")
.pass_stdin("1"),
@r"
success: true
exit_code: 0
----- stdout -----
All checks passed!
----- stderr -----
warning: The `lint.future-annotations` setting will have no effect because `preview` is disabled
",
);
}
#[test]
fn up045_nested_optional_flatten_all() {
let contents = "\

View File

@@ -55,6 +55,10 @@ either a redundant alias or, if already present in the file, an `__all__` entry.
to remove third-party and standard library imports -- the fix is unsafe because the module's
interface changes.
See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)
for more details on how Ruff
determines whether an import is first or third-party.
## Example
```python
@@ -83,11 +87,6 @@ else:
print("numpy is not installed")
```
## Preview
When [preview](https://docs.astral.sh/ruff/preview/) is enabled,
the criterion for determining whether an import is first-party
is stricter, which could affect the suggested fix. See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) for more details.
## Options
- `lint.ignore-init-module-imports`
- `lint.pyflakes.allowed-unused-imports`

View File

@@ -95,7 +95,7 @@ exit_code: 1
"rules": [
{
"fullDescription": {
"text": "## What it does\nChecks for unused imports.\n\n## Why is this bad?\nUnused imports add a performance overhead at runtime, and risk creating\nimport cycles. They also increase the cognitive load of reading the code.\n\nIf an import statement is used to check for the availability or existence\nof a module, consider using `importlib.util.find_spec` instead.\n\nIf an import statement is used to re-export a symbol as part of a module's\npublic interface, consider using a \"redundant\" import alias, which\ninstructs Ruff (and other tools) to respect the re-export, and avoid\nmarking it as unused, as in:\n\n```python\nfrom module import member as member\n```\n\nAlternatively, you can use `__all__` to declare a symbol as part of the module's\ninterface, as in:\n\n```python\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n```\n\n## Fix safety\n\nFixes to remove unused imports are safe, except in `__init__.py` files.\n\nApplying fixes to `__init__.py` files is currently in preview. The fix offered depends on the\ntype of the unused import. Ruff will suggest a safe fix to export first-party imports with\neither a redundant alias or, if already present in the file, an `__all__` entry. If multiple\n`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix\nto remove third-party and standard library imports -- the fix is unsafe because the module's\ninterface changes.\n\n## Example\n\n```python\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n```\n\nUse instead:\n\n```python\ndef area(radius):\n return 3.14 * radius**2\n```\n\nTo check the availability of a module, use `importlib.util.find_spec`:\n\n```python\nfrom importlib.util import find_spec\n\nif find_spec(\"numpy\") is not None:\n print(\"numpy is installed\")\nelse:\n print(\"numpy is not installed\")\n```\n\n## Preview\nWhen [preview](https://docs.astral.sh/ruff/preview/) is enabled,\nthe criterion for determining whether an import is first-party\nis stricter, which could affect the suggested fix. See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) for more details.\n\n## Options\n- `lint.ignore-init-module-imports`\n- `lint.pyflakes.allowed-unused-imports`\n\n## References\n- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)\n- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)\n- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)\n"
"text": "## What it does\nChecks for unused imports.\n\n## Why is this bad?\nUnused imports add a performance overhead at runtime, and risk creating\nimport cycles. They also increase the cognitive load of reading the code.\n\nIf an import statement is used to check for the availability or existence\nof a module, consider using `importlib.util.find_spec` instead.\n\nIf an import statement is used to re-export a symbol as part of a module's\npublic interface, consider using a \"redundant\" import alias, which\ninstructs Ruff (and other tools) to respect the re-export, and avoid\nmarking it as unused, as in:\n\n```python\nfrom module import member as member\n```\n\nAlternatively, you can use `__all__` to declare a symbol as part of the module's\ninterface, as in:\n\n```python\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n```\n\n## Fix safety\n\nFixes to remove unused imports are safe, except in `__init__.py` files.\n\nApplying fixes to `__init__.py` files is currently in preview. The fix offered depends on the\ntype of the unused import. Ruff will suggest a safe fix to export first-party imports with\neither a redundant alias or, if already present in the file, an `__all__` entry. If multiple\n`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix\nto remove third-party and standard library imports -- the fix is unsafe because the module's\ninterface changes.\n\nSee [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)\nfor more details on how Ruff\ndetermines whether an import is first or third-party.\n\n## Example\n\n```python\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n```\n\nUse instead:\n\n```python\ndef area(radius):\n return 3.14 * radius**2\n```\n\nTo check the availability of a module, use `importlib.util.find_spec`:\n\n```python\nfrom importlib.util import find_spec\n\nif find_spec(\"numpy\") is not None:\n print(\"numpy is installed\")\nelse:\n print(\"numpy is not installed\")\n```\n\n## Options\n- `lint.ignore-init-module-imports`\n- `lint.pyflakes.allowed-unused-imports`\n\n## References\n- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)\n- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)\n- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)\n"
},
"help": {
"text": "`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability"

View File

@@ -454,24 +454,26 @@ impl Diagnostic {
/// Computes the start source location for the message.
///
/// Panics if the diagnostic has no primary span, if its file is not a `SourceFile`, or if the
/// span has no range.
pub fn expect_ruff_start_location(&self) -> LineColumn {
self.expect_primary_span()
.expect_ruff_file()
.to_source_code()
.line_column(self.expect_range().start())
/// Returns None if the diagnostic has no primary span, if its file is not a `SourceFile`,
/// or if the span has no range.
pub fn ruff_start_location(&self) -> Option<LineColumn> {
Some(
self.ruff_source_file()?
.to_source_code()
.line_column(self.range()?.start()),
)
}
/// Computes the end source location for the message.
///
/// Panics if the diagnostic has no primary span, if its file is not a `SourceFile`, or if the
/// span has no range.
pub fn expect_ruff_end_location(&self) -> LineColumn {
self.expect_primary_span()
.expect_ruff_file()
.to_source_code()
.line_column(self.expect_range().end())
/// Returns None if the diagnostic has no primary span, if its file is not a `SourceFile`,
/// or if the span has no range.
pub fn ruff_end_location(&self) -> Option<LineColumn> {
Some(
self.ruff_source_file()?
.to_source_code()
.line_column(self.range()?.end()),
)
}
/// Returns the [`SourceFile`] which the message belongs to.
@@ -501,13 +503,18 @@ impl Diagnostic {
/// Returns the ordering of diagnostics based on the start of their ranges, if they have any.
///
/// Panics if either diagnostic has no primary span, if the span has no range, or if its file is
/// not a `SourceFile`.
/// Panics if either diagnostic has no primary span, or if its file is not a `SourceFile`.
pub fn ruff_start_ordering(&self, other: &Self) -> std::cmp::Ordering {
(self.expect_ruff_source_file(), self.expect_range().start()).cmp(&(
let a = (
self.expect_ruff_source_file(),
self.range().map(|r| r.start()),
);
let b = (
other.expect_ruff_source_file(),
other.expect_range().start(),
))
other.range().map(|r| r.start()),
);
a.cmp(&b)
}
}
@@ -1444,7 +1451,7 @@ pub enum DiagnosticFormat {
Junit,
/// Print diagnostics in the JSON format used by GitLab [Code Quality] reports.
///
/// [Code Quality]: https://docs.gitlab.com/ee/ci/testing/code_quality.html#implement-a-custom-tool
/// [Code Quality]: https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format
#[cfg(feature = "serde")]
Gitlab,
}

View File

@@ -81,14 +81,19 @@ impl IndentStyle {
pub const fn is_space(&self) -> bool {
matches!(self, IndentStyle::Space)
}
/// Returns the string representation of the indent style.
pub const fn as_str(&self) -> &'static str {
match self {
IndentStyle::Tab => "tab",
IndentStyle::Space => "space",
}
}
}
impl std::fmt::Display for IndentStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IndentStyle::Tab => std::write!(f, "tab"),
IndentStyle::Space => std::write!(f, "space"),
}
f.write_str(self.as_str())
}
}

View File

@@ -139,4 +139,16 @@ impl LineEnding {
LineEnding::CarriageReturn => "\r",
}
}
/// Returns the string used to configure this line ending.
///
/// See [`LineEnding::as_str`] for the actual string representation of the line ending.
#[inline]
pub const fn as_setting_str(&self) -> &'static str {
match self {
LineEnding::LineFeed => "lf",
LineEnding::CarriageReturnLineFeed => "crlf",
LineEnding::CarriageReturn => "cr",
}
}
}

View File

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

View File

@@ -12,6 +12,7 @@ from airflow import (
from airflow.api_connexion.security import requires_access
from airflow.contrib.aws_athena_hook import AWSAthenaHook
from airflow.datasets import DatasetAliasEvent
from airflow.operators.postgres_operator import Mapping
from airflow.operators.subdag import SubDagOperator
from airflow.secrets.cache import SecretCache
from airflow.secrets.local_filesystem import LocalFilesystemBackend
@@ -52,6 +53,8 @@ DatasetAliasEvent()
# airflow.operators.subdag.*
SubDagOperator()
# airflow.operators.postgres_operator
Mapping()
# airflow.secrets
# get_connection

View File

@@ -70,7 +70,7 @@ from airflow.timetables.datasets import DatasetOrTimeSchedule
from airflow.utils.dag_parsing_context import get_parsing_context
# airflow.timetables.datasets
DatasetOrTimeSchedule()
DatasetOrTimeSchedule(datasets=[])
# airflow.utils.dag_parsing_context
get_parsing_context()

View File

@@ -75,3 +75,7 @@ list(map(lambda x, y: x, [(1, 2), (3, 4)]))
_ = t"{set(map(lambda x: x % 2 == 0, nums))}"
_ = t"{dict(map(lambda v: (v, v**2), nums))}"
# See https://github.com/astral-sh/ruff/issues/20198
# No error: lambda contains `yield`, so map() should not be rewritten
map(lambda x: (yield x), [1, 2, 3])

View File

@@ -141,3 +141,133 @@ class ExampleWithKeywords:
def method3(self):
super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
# See: https://github.com/astral-sh/ruff/issues/19357
# Must be detected
class ParentD:
def f(self):
print("D")
class ChildD1(ParentD):
def f(self):
if False: __class__ # Python injects __class__ into scope
builtins.super(ChildD1, self).f()
class ChildD2(ParentD):
def f(self):
if False: super # Python injects __class__ into scope
builtins.super(ChildD2, self).f()
class ChildD3(ParentD):
def f(self):
builtins.super(ChildD3, self).f()
super # Python injects __class__ into scope
import builtins as builtins_alias
class ChildD4(ParentD):
def f(self):
builtins_alias.super(ChildD4, self).f()
super # Python injects __class__ into scope
class ChildD5(ParentD):
def f(self):
super = 1
super # Python injects __class__ into scope
builtins.super(ChildD5, self).f()
class ChildD6(ParentD):
def f(self):
super: "Any"
__class__ # Python injects __class__ into scope
builtins.super(ChildD6, self).f()
class ChildD7(ParentD):
def f(self):
def x():
__class__ # Python injects __class__ into scope
builtins.super(ChildD7, self).f()
class ChildD8(ParentD):
def f(self):
def x():
super = 1
super # Python injects __class__ into scope
builtins.super(ChildD8, self).f()
class ChildD9(ParentD):
def f(self):
def x():
__class__ = 1
__class__ # Python injects __class__ into scope
builtins.super(ChildD9, self).f()
class ChildD10(ParentD):
def f(self):
def x():
__class__ = 1
super # Python injects __class__ into scope
builtins.super(ChildD10, self).f()
# Must be ignored
class ParentI:
def f(self):
print("I")
class ChildI1(ParentI):
def f(self):
builtins.super(ChildI1, self).f() # no __class__ in the local scope
class ChildI2(ParentI):
def b(self):
x = __class__
if False: super
def f(self):
self.b()
builtins.super(ChildI2, self).f() # no __class__ in the local scope
class ChildI3(ParentI):
def f(self):
if False: super
def x(_):
builtins.super(ChildI3, self).f() # no __class__ in the local scope
x(None)
class ChildI4(ParentI):
def f(self):
super: "str"
builtins.super(ChildI4, self).f() # no __class__ in the local scope
class ChildI5(ParentI):
def f(self):
super = 1
__class__ = 3
builtins.super(ChildI5, self).f() # no __class__ in the local scope
class ChildI6(ParentI):
def f(self):
__class__ = None
__class__
builtins.super(ChildI6, self).f() # no __class__ in the local scope
class ChildI7(ParentI):
def f(self):
__class__ = None
super
builtins.super(ChildI7, self).f()
class ChildI8(ParentI):
def f(self):
__class__: "Any"
super
builtins.super(ChildI8, self).f()
class ChildI9(ParentI):
def f(self):
class A:
def foo(self):
if False: super
if False: __class__
builtins.super(ChildI9, self).f()

View File

@@ -0,0 +1,59 @@
from collections.abc import Generator, AsyncGenerator
def func() -> Generator[int, None, None]:
yield 42
def func() -> Generator[int, None]:
yield 42
def func() -> Generator[int]:
yield 42
def func() -> Generator[int, int, int]:
foo = yield 42
return foo
def func() -> Generator[int, int, None]:
_ = yield 42
return None
def func() -> Generator[int, None, int]:
yield 42
return 42
async def func() -> AsyncGenerator[int, None]:
yield 42
async def func() -> AsyncGenerator[int]:
yield 42
async def func() -> AsyncGenerator[int, int]:
foo = yield 42
return foo
from typing import Generator, AsyncGenerator
def func() -> Generator[str, None, None]:
yield "hello"
async def func() -> AsyncGenerator[str, None]:
yield "hello"
async def func() -> AsyncGenerator[ # type: ignore
str,
None
]:
yield "hello"

View File

@@ -42,3 +42,7 @@ b"a" in bytes("a", "utf-8")
1 in set(set([1]))
'' in {""}
frozenset() in {frozenset()}
# https://github.com/astral-sh/ruff/issues/20238
"b" in f"" "" # Error
"b" in f"" "x" # OK

View File

@@ -0,0 +1 @@
async def f(): yield from x # error

View File

@@ -8,7 +8,7 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::preview::{
is_assert_raises_exception_call_enabled, is_optional_as_none_in_union_enabled,
is_optional_as_none_in_union_enabled, is_unnecessary_default_type_args_stubs_enabled,
};
use crate::registry::Rule;
use crate::rules::{
@@ -142,7 +142,10 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
}
if checker.is_rule_enabled(Rule::UnnecessaryDefaultTypeArgs) {
if checker.target_version() >= PythonVersion::PY313 {
if checker.target_version() >= PythonVersion::PY313
|| is_unnecessary_default_type_args_stubs_enabled(checker.settings())
&& checker.semantic().in_stub_file()
{
pyupgrade::rules::unnecessary_default_type_args(checker, expr);
}
}
@@ -1292,9 +1295,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
if checker.is_rule_enabled(Rule::NonOctalPermissions) {
ruff::rules::non_octal_permissions(checker, call);
}
if checker.is_rule_enabled(Rule::AssertRaisesException)
&& is_assert_raises_exception_call_enabled(checker.settings())
{
if checker.is_rule_enabled(Rule::AssertRaisesException) {
flake8_bugbear::rules::assert_raises_exception_call(checker, call);
}
}
@@ -1319,13 +1320,10 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
pylint::rules::yield_in_init(checker, expr);
}
}
Expr::YieldFrom(yield_from) => {
Expr::YieldFrom(_) => {
if checker.is_rule_enabled(Rule::YieldInInit) {
pylint::rules::yield_in_init(checker, expr);
}
if checker.is_rule_enabled(Rule::YieldFromInAsyncFunction) {
pylint::rules::yield_from_in_async_function(checker, yield_from);
}
}
Expr::FString(f_string_expr @ ast::ExprFString { value, .. }) => {
if checker.is_rule_enabled(Rule::FStringMissingPlaceholders) {

View File

@@ -71,7 +71,9 @@ use crate::registry::Rule;
use crate::rules::pyflakes::rules::{
LateFutureImport, ReturnOutsideFunction, YieldOutsideFunction,
};
use crate::rules::pylint::rules::{AwaitOutsideAsync, LoadBeforeGlobalDeclaration};
use crate::rules::pylint::rules::{
AwaitOutsideAsync, LoadBeforeGlobalDeclaration, YieldFromInAsyncFunction,
};
use crate::rules::{flake8_pyi, flake8_type_checking, pyflakes, pyupgrade};
use crate::settings::rule_table::RuleTable;
use crate::settings::{LinterSettings, TargetVersion, flags};
@@ -668,6 +670,12 @@ impl SemanticSyntaxContext for Checker<'_> {
self.report_diagnostic(AwaitOutsideAsync, error.range);
}
}
SemanticSyntaxErrorKind::YieldFromInAsyncFunction => {
// PLE1700
if self.is_rule_enabled(Rule::YieldFromInAsyncFunction) {
self.report_diagnostic(YieldFromInAsyncFunction, error.range);
}
}
SemanticSyntaxErrorKind::ReboundComprehensionVariable
| SemanticSyntaxErrorKind::DuplicateTypeParameter
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_)

View File

@@ -334,7 +334,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Async, "109") => (RuleGroup::Stable, rules::flake8_async::rules::AsyncFunctionWithTimeout),
(Flake8Async, "110") => (RuleGroup::Stable, rules::flake8_async::rules::AsyncBusyWait),
(Flake8Async, "115") => (RuleGroup::Stable, rules::flake8_async::rules::AsyncZeroSleep),
(Flake8Async, "116") => (RuleGroup::Preview, rules::flake8_async::rules::LongSleepNotForever),
(Flake8Async, "116") => (RuleGroup::Stable, rules::flake8_async::rules::LongSleepNotForever),
(Flake8Async, "210") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction),
(Flake8Async, "212") => (RuleGroup::Preview, rules::flake8_async::rules::BlockingHttpCallHttpxInAsyncFunction),
(Flake8Async, "220") => (RuleGroup::Stable, rules::flake8_async::rules::CreateSubprocessInAsyncFunction),
@@ -563,7 +563,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pyupgrade, "035") => (RuleGroup::Stable, rules::pyupgrade::rules::DeprecatedImport),
(Pyupgrade, "036") => (RuleGroup::Stable, rules::pyupgrade::rules::OutdatedVersionBlock),
(Pyupgrade, "037") => (RuleGroup::Stable, rules::pyupgrade::rules::QuotedAnnotation),
(Pyupgrade, "038") => (RuleGroup::Deprecated, rules::pyupgrade::rules::NonPEP604Isinstance),
(Pyupgrade, "038") => (RuleGroup::Removed, rules::pyupgrade::rules::NonPEP604Isinstance),
(Pyupgrade, "039") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryClassParentheses),
(Pyupgrade, "040") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695TypeAlias),
(Pyupgrade, "041") => (RuleGroup::Stable, rules::pyupgrade::rules::TimeoutErrorAlias),
@@ -574,7 +574,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pyupgrade, "046") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695GenericClass),
(Pyupgrade, "047") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695GenericFunction),
(Pyupgrade, "049") => (RuleGroup::Stable, rules::pyupgrade::rules::PrivateTypeParameter),
(Pyupgrade, "050") => (RuleGroup::Preview, rules::pyupgrade::rules::UselessClassMetaclassType),
(Pyupgrade, "050") => (RuleGroup::Stable, rules::pyupgrade::rules::UselessClassMetaclassType),
// pydocstyle
(Pydocstyle, "100") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedPublicModule),
@@ -773,7 +773,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(PandasVet, "013") => (RuleGroup::Stable, rules::pandas_vet::rules::PandasUseOfDotStack),
(PandasVet, "015") => (RuleGroup::Stable, rules::pandas_vet::rules::PandasUseOfPdMerge),
(PandasVet, "101") => (RuleGroup::Stable, rules::pandas_vet::rules::PandasNuniqueConstantSeriesCheck),
(PandasVet, "901") => (RuleGroup::Deprecated, rules::pandas_vet::rules::PandasDfVariableName),
(PandasVet, "901") => (RuleGroup::Removed, rules::pandas_vet::rules::PandasDfVariableName),
// flake8-errmsg
(Flake8ErrMsg, "101") => (RuleGroup::Stable, rules::flake8_errmsg::rules::RawStringInException),
@@ -830,8 +830,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Pyi, "056") => (RuleGroup::Stable, rules::flake8_pyi::rules::UnsupportedMethodCallOnAll),
(Flake8Pyi, "058") => (RuleGroup::Stable, rules::flake8_pyi::rules::GeneratorReturnFromIterMethod),
(Flake8Pyi, "057") => (RuleGroup::Stable, rules::flake8_pyi::rules::ByteStringUsage),
(Flake8Pyi, "059") => (RuleGroup::Preview, rules::flake8_pyi::rules::GenericNotLastBaseClass),
(Flake8Pyi, "061") => (RuleGroup::Preview, rules::flake8_pyi::rules::RedundantNoneLiteral),
(Flake8Pyi, "059") => (RuleGroup::Stable, rules::flake8_pyi::rules::GenericNotLastBaseClass),
(Flake8Pyi, "061") => (RuleGroup::Stable, rules::flake8_pyi::rules::RedundantNoneLiteral),
(Flake8Pyi, "062") => (RuleGroup::Stable, rules::flake8_pyi::rules::DuplicateLiteralMember),
(Flake8Pyi, "063") => (RuleGroup::Stable, rules::flake8_pyi::rules::Pep484StylePositionalOnlyParameter),
(Flake8Pyi, "064") => (RuleGroup::Stable, rules::flake8_pyi::rules::RedundantFinalLiteral),
@@ -956,7 +956,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8UsePathlib, "207") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::Glob),
(Flake8UsePathlib, "208") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsListdir),
(Flake8UsePathlib, "210") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::InvalidPathlibWithSuffix),
(Flake8UsePathlib, "211") => (RuleGroup::Preview, rules::flake8_use_pathlib::rules::OsSymlink),
(Flake8UsePathlib, "211") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsSymlink),
// flake8-logging-format
(Flake8LoggingFormat, "001") => (RuleGroup::Stable, rules::flake8_logging_format::violations::LoggingStringFormat),
@@ -1032,7 +1032,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "039") => (RuleGroup::Preview, rules::ruff::rules::UnrawRePattern),
(Ruff, "040") => (RuleGroup::Stable, rules::ruff::rules::InvalidAssertMessageLiteralArgument),
(Ruff, "041") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryNestedLiteral),
(Ruff, "043") => (RuleGroup::Preview, rules::ruff::rules::PytestRaisesAmbiguousPattern),
(Ruff, "043") => (RuleGroup::Stable, rules::ruff::rules::PytestRaisesAmbiguousPattern),
(Ruff, "045") => (RuleGroup::Preview, rules::ruff::rules::ImplicitClassVarInDataclass),
(Ruff, "046") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryCastToInt),
(Ruff, "047") => (RuleGroup::Preview, rules::ruff::rules::NeedlessElse),
@@ -1046,7 +1046,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "056") => (RuleGroup::Preview, rules::ruff::rules::FalsyDictGetFallback),
(Ruff, "057") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryRound),
(Ruff, "058") => (RuleGroup::Stable, rules::ruff::rules::StarmapZip),
(Ruff, "059") => (RuleGroup::Preview, rules::ruff::rules::UnusedUnpackedVariable),
(Ruff, "059") => (RuleGroup::Stable, rules::ruff::rules::UnusedUnpackedVariable),
(Ruff, "060") => (RuleGroup::Preview, rules::ruff::rules::InEmptyCollection),
(Ruff, "061") => (RuleGroup::Preview, rules::ruff::rules::LegacyFormPytestRaises),
(Ruff, "063") => (RuleGroup::Preview, rules::ruff::rules::AccessAnnotationsFromClassDict),
@@ -1106,11 +1106,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
// airflow
(Airflow, "001") => (RuleGroup::Stable, rules::airflow::rules::AirflowVariableNameTaskIdMismatch),
(Airflow, "002") => (RuleGroup::Preview, rules::airflow::rules::AirflowDagNoScheduleArgument),
(Airflow, "301") => (RuleGroup::Preview, rules::airflow::rules::Airflow3Removal),
(Airflow, "302") => (RuleGroup::Preview, rules::airflow::rules::Airflow3MovedToProvider),
(Airflow, "311") => (RuleGroup::Preview, rules::airflow::rules::Airflow3SuggestedUpdate),
(Airflow, "312") => (RuleGroup::Preview, rules::airflow::rules::Airflow3SuggestedToMoveToProvider),
(Airflow, "002") => (RuleGroup::Stable, rules::airflow::rules::AirflowDagNoScheduleArgument),
(Airflow, "301") => (RuleGroup::Stable, rules::airflow::rules::Airflow3Removal),
(Airflow, "302") => (RuleGroup::Stable, rules::airflow::rules::Airflow3MovedToProvider),
(Airflow, "311") => (RuleGroup::Stable, rules::airflow::rules::Airflow3SuggestedUpdate),
(Airflow, "312") => (RuleGroup::Stable, rules::airflow::rules::Airflow3SuggestedToMoveToProvider),
// perflint
(Perflint, "101") => (RuleGroup::Stable, rules::perflint::rules::UnnecessaryListCast),
@@ -1137,7 +1137,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Refurb, "105") => (RuleGroup::Stable, rules::refurb::rules::PrintEmptyString),
(Refurb, "110") => (RuleGroup::Preview, rules::refurb::rules::IfExpInsteadOfOrOperator),
(Refurb, "113") => (RuleGroup::Preview, rules::refurb::rules::RepeatedAppend),
(Refurb, "116") => (RuleGroup::Preview, rules::refurb::rules::FStringNumberFormat),
(Refurb, "116") => (RuleGroup::Stable, rules::refurb::rules::FStringNumberFormat),
(Refurb, "118") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedOperator),
(Refurb, "122") => (RuleGroup::Stable, rules::refurb::rules::ForLoopWrites),
(Refurb, "129") => (RuleGroup::Stable, rules::refurb::rules::ReadlinesInFor),

View File

@@ -1231,6 +1231,10 @@ mod tests {
)]
#[test_case(Rule::AwaitOutsideAsync, Path::new("await_outside_async_function.py"))]
#[test_case(Rule::AwaitOutsideAsync, Path::new("async_comprehension.py"))]
#[test_case(
Rule::YieldFromInAsyncFunction,
Path::new("yield_from_in_async_function.py")
)]
fn test_syntax_errors(rule: Rule, path: &Path) -> Result<()> {
let snapshot = path.to_string_lossy().to_string();
let path = Path::new("resources/test/fixtures/syntax_errors").join(path);

View File

@@ -19,7 +19,7 @@ impl Emitter for GithubEmitter {
context: &EmitterContext,
) -> anyhow::Result<()> {
for diagnostic in diagnostics {
let source_location = diagnostic.expect_ruff_start_location();
let source_location = diagnostic.ruff_start_location().unwrap_or_default();
let filename = diagnostic.expect_ruff_filename();
let location = if context.is_notebook(&filename) {
// We can't give a reasonable location for the structured formats,
@@ -29,7 +29,7 @@ impl Emitter for GithubEmitter {
source_location
};
let end_location = diagnostic.expect_ruff_end_location();
let end_location = diagnostic.ruff_end_location().unwrap_or_default();
write!(
writer,

View File

@@ -105,7 +105,7 @@ fn group_diagnostics_by_filename(
.or_insert_with(Vec::new)
.push(MessageWithLocation {
message: diagnostic,
start_location: diagnostic.expect_ruff_start_location(),
start_location: diagnostic.ruff_start_location().unwrap_or_default(),
});
}
grouped_messages

View File

@@ -158,8 +158,8 @@ struct SarifResult<'a> {
impl<'a> SarifResult<'a> {
#[cfg(not(target_arch = "wasm32"))]
fn from_message(message: &'a Diagnostic) -> Result<Self> {
let start_location = message.expect_ruff_start_location();
let end_location = message.expect_ruff_end_location();
let start_location = message.ruff_start_location().unwrap_or_default();
let end_location = message.ruff_end_location().unwrap_or_default();
let path = normalize_path(&*message.expect_ruff_filename());
Ok(Self {
code: RuleCode::from(message),
@@ -178,8 +178,8 @@ impl<'a> SarifResult<'a> {
#[cfg(target_arch = "wasm32")]
#[expect(clippy::unnecessary_wraps)]
fn from_message(message: &'a Diagnostic) -> Result<Self> {
let start_location = message.expect_ruff_start_location();
let end_location = message.expect_ruff_end_location();
let start_location = message.ruff_start_location().unwrap_or_default();
let end_location = message.ruff_end_location().unwrap_or_default();
let path = normalize_path(&*message.expect_ruff_filename());
Ok(Self {
code: RuleCode::from(message),

View File

@@ -81,7 +81,7 @@ expression: value
"rules": [
{
"fullDescription": {
"text": "## What it does\nChecks for unused imports.\n\n## Why is this bad?\nUnused imports add a performance overhead at runtime, and risk creating\nimport cycles. They also increase the cognitive load of reading the code.\n\nIf an import statement is used to check for the availability or existence\nof a module, consider using `importlib.util.find_spec` instead.\n\nIf an import statement is used to re-export a symbol as part of a module's\npublic interface, consider using a \"redundant\" import alias, which\ninstructs Ruff (and other tools) to respect the re-export, and avoid\nmarking it as unused, as in:\n\n```python\nfrom module import member as member\n```\n\nAlternatively, you can use `__all__` to declare a symbol as part of the module's\ninterface, as in:\n\n```python\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n```\n\n## Fix safety\n\nFixes to remove unused imports are safe, except in `__init__.py` files.\n\nApplying fixes to `__init__.py` files is currently in preview. The fix offered depends on the\ntype of the unused import. Ruff will suggest a safe fix to export first-party imports with\neither a redundant alias or, if already present in the file, an `__all__` entry. If multiple\n`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix\nto remove third-party and standard library imports -- the fix is unsafe because the module's\ninterface changes.\n\n## Example\n\n```python\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n```\n\nUse instead:\n\n```python\ndef area(radius):\n return 3.14 * radius**2\n```\n\nTo check the availability of a module, use `importlib.util.find_spec`:\n\n```python\nfrom importlib.util import find_spec\n\nif find_spec(\"numpy\") is not None:\n print(\"numpy is installed\")\nelse:\n print(\"numpy is not installed\")\n```\n\n## Preview\nWhen [preview](https://docs.astral.sh/ruff/preview/) is enabled,\nthe criterion for determining whether an import is first-party\nis stricter, which could affect the suggested fix. See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) for more details.\n\n## Options\n- `lint.ignore-init-module-imports`\n- `lint.pyflakes.allowed-unused-imports`\n\n## References\n- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)\n- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)\n- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)\n"
"text": "## What it does\nChecks for unused imports.\n\n## Why is this bad?\nUnused imports add a performance overhead at runtime, and risk creating\nimport cycles. They also increase the cognitive load of reading the code.\n\nIf an import statement is used to check for the availability or existence\nof a module, consider using `importlib.util.find_spec` instead.\n\nIf an import statement is used to re-export a symbol as part of a module's\npublic interface, consider using a \"redundant\" import alias, which\ninstructs Ruff (and other tools) to respect the re-export, and avoid\nmarking it as unused, as in:\n\n```python\nfrom module import member as member\n```\n\nAlternatively, you can use `__all__` to declare a symbol as part of the module's\ninterface, as in:\n\n```python\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n```\n\n## Fix safety\n\nFixes to remove unused imports are safe, except in `__init__.py` files.\n\nApplying fixes to `__init__.py` files is currently in preview. The fix offered depends on the\ntype of the unused import. Ruff will suggest a safe fix to export first-party imports with\neither a redundant alias or, if already present in the file, an `__all__` entry. If multiple\n`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix\nto remove third-party and standard library imports -- the fix is unsafe because the module's\ninterface changes.\n\nSee [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)\nfor more details on how Ruff\ndetermines whether an import is first or third-party.\n\n## Example\n\n```python\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n```\n\nUse instead:\n\n```python\ndef area(radius):\n return 3.14 * radius**2\n```\n\nTo check the availability of a module, use `importlib.util.find_spec`:\n\n```python\nfrom importlib.util import find_spec\n\nif find_spec(\"numpy\") is not None:\n print(\"numpy is installed\")\nelse:\n print(\"numpy is not installed\")\n```\n\n## Options\n- `lint.ignore-init-module-imports`\n- `lint.pyflakes.allowed-unused-imports`\n\n## References\n- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)\n- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)\n- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)\n"
},
"help": {
"text": "`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability"
@@ -119,7 +119,7 @@ expression: value
},
{
"fullDescription": {
"text": "## What it does\nChecks for the presence of unused variables in function scopes.\n\n## Why is this bad?\nA variable that is defined but not used is likely a mistake, and should\nbe removed to avoid confusion.\n\nIf a variable is intentionally defined-but-not-used, it should be\nprefixed with an underscore, or some other value that adheres to the\n[`lint.dummy-variable-rgx`] pattern.\n\n## Example\n```python\ndef foo():\n x = 1\n y = 2\n return x\n```\n\nUse instead:\n```python\ndef foo():\n x = 1\n return x\n```\n\n## Fix safety\n\nThis rule's fix is marked as unsafe because removing an unused variable assignment may\ndelete comments that are attached to the assignment.\n\n## Options\n- `lint.dummy-variable-rgx`\n"
"text": "## What it does\nChecks for the presence of unused variables in function scopes.\n\n## Why is this bad?\nA variable that is defined but not used is likely a mistake, and should\nbe removed to avoid confusion.\n\nIf a variable is intentionally defined-but-not-used, it should be\nprefixed with an underscore, or some other value that adheres to the\n[`lint.dummy-variable-rgx`] pattern.\n\n## Example\n```python\ndef foo():\n x = 1\n y = 2\n return x\n```\n\nUse instead:\n```python\ndef foo():\n x = 1\n return x\n```\n\n## Fix safety\n\nThis rule's fix is marked as unsafe because removing an unused variable assignment may\ndelete comments that are attached to the assignment.\n\n## See also\n\nThis rule does not apply to bindings in unpacked assignments (e.g. `x, y = 1, 2`). See\n[`unused-unpacked-variable`][RUF059] for this case.\n\n## Options\n- `lint.dummy-variable-rgx`\n\n[RUF059]: https://docs.astral.sh/ruff/rules/unused-unpacked-variable/\n"
},
"help": {
"text": "Local variable `{name}` is assigned to but never used"

View File

@@ -11,11 +11,6 @@ pub(crate) const fn is_py314_support_enabled(settings: &LinterSettings) -> bool
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/16565
pub(crate) const fn is_full_path_match_source_strategy_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// Rule-specific behavior
// https://github.com/astral-sh/ruff/pull/15541
@@ -200,35 +195,11 @@ pub(crate) const fn is_allow_nested_roots_enabled(settings: &LinterSettings) ->
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/18208
pub(crate) const fn is_multiple_with_statements_fix_safe_enabled(
settings: &LinterSettings,
) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/18400
pub(crate) const fn is_ignore_init_files_in_useless_alias_enabled(
settings: &LinterSettings,
) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/18572
pub(crate) const fn is_optional_as_none_in_union_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/18547
pub(crate) const fn is_invalid_async_mock_access_check_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/18867
pub(crate) const fn is_raise_exception_byte_string_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/18683
pub(crate) const fn is_safe_super_call_with_parameters_fix_enabled(
settings: &LinterSettings,
@@ -236,27 +207,14 @@ pub(crate) const fn is_safe_super_call_with_parameters_fix_enabled(
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19063
pub(crate) const fn is_assert_raises_exception_call_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19100
pub(crate) const fn is_add_future_annotations_imports_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19390
pub(crate) const fn is_trailing_comma_type_params_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19851
pub(crate) const fn is_maxsplit_without_separator_fix_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/20106
pub(crate) const fn is_bidi_forbid_arabic_letter_mark_enabled(settings: &LinterSettings) -> bool {
// https://github.com/astral-sh/ruff/pull/20027
pub(crate) const fn is_unnecessary_default_type_args_stubs_enabled(
settings: &LinterSettings,
) -> bool {
settings.preview.is_enabled()
}

View File

@@ -214,10 +214,8 @@ impl RuleSelector {
RuleGroup::Preview => {
preview_enabled && (self.is_exact() || !preview_require_explicit)
}
// Deprecated rules are excluded in preview mode and with 'All' option unless explicitly selected
RuleGroup::Deprecated => {
(!preview_enabled || self.is_exact()) && !matches!(self, RuleSelector::All)
}
// Deprecated rules are excluded by default unless explicitly selected
RuleGroup::Deprecated => !preview_enabled && self.is_exact(),
// Removed rules are included if explicitly selected but will error downstream
RuleGroup::Removed => self.is_exact(),
}

View File

@@ -37,7 +37,6 @@ pub(crate) enum Replacement {
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum ProviderReplacement {
None,
AutoImport {
module: &'static str,
name: &'static str,

View File

@@ -46,7 +46,7 @@ pub(crate) struct AirflowDagNoScheduleArgument;
impl Violation for AirflowDagNoScheduleArgument {
#[derive_message_formats]
fn message(&self) -> String {
"DAG should have an explicit `schedule` argument".to_string()
"`DAG` or `@dag` should have an explicit `schedule` argument".to_string()
}
}

View File

@@ -13,13 +13,13 @@ use ruff_text_size::TextRange;
use crate::{FixAvailability, Violation};
/// ## What it does
/// Checks for uses of Airflow functions and values that have been moved to it providers.
/// (e.g., apache-airflow-providers-fab)
/// Checks for uses of Airflow functions and values that have been moved to its providers
/// (e.g., `apache-airflow-providers-fab`).
///
/// ## Why is this bad?
/// Airflow 3.0 moved various deprecated functions, members, and other
/// values to its providers. The user needs to install the corresponding provider and replace
/// the original usage with the one in the provider
/// the original usage with the one in the provider.
///
/// ## Example
/// ```python
@@ -50,9 +50,6 @@ impl Violation for Airflow3MovedToProvider<'_> {
replacement,
} = self;
match replacement {
ProviderReplacement::None => {
format!("`{deprecated}` is removed in Airflow 3.0")
}
ProviderReplacement::AutoImport {
name: _,
module: _,
@@ -85,7 +82,6 @@ impl Violation for Airflow3MovedToProvider<'_> {
provider,
version,
} => Some((module, name.as_str(), provider, version)),
ProviderReplacement::None => None,
} {
Some(format!(
"Install `apache-airflow-providers-{provider}>={version}` and use `{name}` from `{module}` instead."
@@ -1020,7 +1016,6 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
provider: "postgres",
version: "1.0.0",
},
["airflow", "operators", "postgres_operator", "Mapping"] => ProviderReplacement::None,
// apache-airflow-providers-presto
["airflow", "hooks", "presto_hook", "PrestoHook"] => ProviderReplacement::AutoImport {
@@ -1209,16 +1204,6 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
ProviderReplacement::SourceModuleMovedToProvider { module, name, .. } => {
(module, name.as_str())
}
ProviderReplacement::None => {
checker.report_diagnostic(
Airflow3MovedToProvider {
deprecated: qualified_name,
replacement,
},
ranged,
);
return;
}
};
if is_guarded_by_try_except(expr, module, name, checker.semantic()) {

View File

@@ -23,7 +23,7 @@ use ruff_text_size::TextRange;
/// ## Why is this bad?
/// Airflow 3.0 removed various deprecated functions, members, and other
/// values. Some have more modern replacements. Others are considered too niche
/// and not worth to be maintained in Airflow.
/// and not worth continued maintenance in Airflow.
///
/// ## Example
/// ```python
@@ -704,6 +704,7 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
["airflow", "operators", "subdag", ..] => {
Replacement::Message("The whole `airflow.subdag` module has been removed.")
}
["airflow", "operators", "postgres_operator", "Mapping"] => Replacement::None,
["airflow", "operators", "python", "get_current_context"] => Replacement::AutoImport {
module: "airflow.sdk",
name: "get_current_context",

View File

@@ -65,9 +65,6 @@ impl Violation for Airflow3SuggestedToMoveToProvider<'_> {
replacement,
} = self;
match replacement {
ProviderReplacement::None => {
format!("`{deprecated}` is removed in Airflow 3.0")
}
ProviderReplacement::AutoImport {
name: _,
module: _,
@@ -91,7 +88,6 @@ impl Violation for Airflow3SuggestedToMoveToProvider<'_> {
fn fix_title(&self) -> Option<String> {
let Airflow3SuggestedToMoveToProvider { replacement, .. } = self;
match replacement {
ProviderReplacement::None => None,
ProviderReplacement::AutoImport {
module,
name,
@@ -319,16 +315,6 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
ProviderReplacement::SourceModuleMovedToProvider { module, name, .. } => {
(module, name.as_str())
}
ProviderReplacement::None => {
checker.report_diagnostic(
Airflow3SuggestedToMoveToProvider {
deprecated: qualified_name,
replacement: replacement.clone(),
},
ranged.range(),
);
return;
}
};
if is_guarded_by_try_except(expr, module, name, checker.semantic()) {

View File

@@ -17,9 +17,9 @@ use ruff_text_size::TextRange;
/// ## Why is this bad?
/// Airflow 3.0 removed various deprecated functions, members, and other
/// values. Some have more modern replacements. Others are considered too niche
/// and not worth to be maintained in Airflow.
/// and not worth continued maintenance in Airflow.
/// Even though these symbols still work fine on Airflow 3.0, they are expected to be removed in a future version.
/// The user is suggested to replace the original usage with the new ones.
/// Where available, users should replace the removed functionality with the new alternatives.
///
/// ## Example
/// ```python
@@ -157,6 +157,9 @@ fn check_call_arguments(checker: &Checker, qualified_name: &QualifiedName, argum
["airflow", .., "DAG" | "dag"] => {
diagnostic_for_argument(checker, arguments, "sla_miss_callback", None);
}
["airflow", "timetables", "datasets", "DatasetOrTimeSchedule"] => {
diagnostic_for_argument(checker, arguments, "datasets", Some("assets"));
}
segments => {
if is_airflow_builtin_or_provider(segments, "operators", "Operator") {
diagnostic_for_argument(checker, arguments, "sla", None);

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR002 DAG should have an explicit `schedule` argument
AIR002 `DAG` or `@dag` should have an explicit `schedule` argument
--> AIR002.py:4:1
|
2 | from airflow.timetables.simple import NullTimetable
@@ -12,7 +12,7 @@ AIR002 DAG should have an explicit `schedule` argument
6 | DAG(dag_id="class_schedule", schedule="@hourly")
|
AIR002 DAG should have an explicit `schedule` argument
AIR002 `DAG` or `@dag` should have an explicit `schedule` argument
--> AIR002.py:13:2
|
13 | @dag()

View File

@@ -2,350 +2,362 @@
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR301 `airflow.PY36` is removed in Airflow 3.0
--> AIR301_names.py:39:1
--> AIR301_names.py:40:1
|
38 | # airflow root
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
39 | # airflow root
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^
40 |
41 | # airflow.api_connexion.security
41 |
42 | # airflow.api_connexion.security
|
help: Use `sys.version_info` instead
AIR301 `airflow.PY37` is removed in Airflow 3.0
--> AIR301_names.py:39:7
--> AIR301_names.py:40:7
|
38 | # airflow root
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
39 | # airflow root
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^
40 |
41 | # airflow.api_connexion.security
41 |
42 | # airflow.api_connexion.security
|
help: Use `sys.version_info` instead
AIR301 `airflow.PY38` is removed in Airflow 3.0
--> AIR301_names.py:39:13
--> AIR301_names.py:40:13
|
38 | # airflow root
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
39 | # airflow root
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^
40 |
41 | # airflow.api_connexion.security
41 |
42 | # airflow.api_connexion.security
|
help: Use `sys.version_info` instead
AIR301 `airflow.PY39` is removed in Airflow 3.0
--> AIR301_names.py:39:19
--> AIR301_names.py:40:19
|
38 | # airflow root
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
39 | # airflow root
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^
40 |
41 | # airflow.api_connexion.security
41 |
42 | # airflow.api_connexion.security
|
help: Use `sys.version_info` instead
AIR301 `airflow.PY310` is removed in Airflow 3.0
--> AIR301_names.py:39:25
--> AIR301_names.py:40:25
|
38 | # airflow root
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
39 | # airflow root
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^^
40 |
41 | # airflow.api_connexion.security
41 |
42 | # airflow.api_connexion.security
|
help: Use `sys.version_info` instead
AIR301 `airflow.PY311` is removed in Airflow 3.0
--> AIR301_names.py:39:32
--> AIR301_names.py:40:32
|
38 | # airflow root
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
39 | # airflow root
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^^
40 |
41 | # airflow.api_connexion.security
41 |
42 | # airflow.api_connexion.security
|
help: Use `sys.version_info` instead
AIR301 `airflow.PY312` is removed in Airflow 3.0
--> AIR301_names.py:39:39
--> AIR301_names.py:40:39
|
38 | # airflow root
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
39 | # airflow root
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^^
40 |
41 | # airflow.api_connexion.security
41 |
42 | # airflow.api_connexion.security
|
help: Use `sys.version_info` instead
AIR301 `airflow.api_connexion.security.requires_access` is removed in Airflow 3.0
--> AIR301_names.py:42:1
--> AIR301_names.py:43:1
|
41 | # airflow.api_connexion.security
42 | requires_access
42 | # airflow.api_connexion.security
43 | requires_access
| ^^^^^^^^^^^^^^^
43 |
44 | # airflow.contrib.*
44 |
45 | # airflow.contrib.*
|
help: Use `airflow.api_fastapi.core_api.security.requires_access_*` instead
AIR301 `airflow.contrib.aws_athena_hook.AWSAthenaHook` is removed in Airflow 3.0
--> AIR301_names.py:45:1
--> AIR301_names.py:46:1
|
44 | # airflow.contrib.*
45 | AWSAthenaHook()
45 | # airflow.contrib.*
46 | AWSAthenaHook()
| ^^^^^^^^^^^^^
|
help: The whole `airflow.contrib` module has been removed.
AIR301 `airflow.datasets.DatasetAliasEvent` is removed in Airflow 3.0
--> AIR301_names.py:49:1
--> AIR301_names.py:50:1
|
48 | # airflow.datasets
49 | DatasetAliasEvent()
49 | # airflow.datasets
50 | DatasetAliasEvent()
| ^^^^^^^^^^^^^^^^^
|
AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0
--> AIR301_names.py:53:1
--> AIR301_names.py:54:1
|
52 | # airflow.operators.subdag.*
53 | SubDagOperator()
53 | # airflow.operators.subdag.*
54 | SubDagOperator()
| ^^^^^^^^^^^^^^
55 |
56 | # airflow.operators.postgres_operator
|
help: The whole `airflow.subdag` module has been removed.
AIR301 [*] `airflow.secrets.cache.SecretCache` is removed in Airflow 3.0
--> AIR301_names.py:61:1
AIR301 `airflow.operators.postgres_operator.Mapping` is removed in Airflow 3.0
--> AIR301_names.py:57:1
|
60 | # airflow.secrets.cache
61 | SecretCache()
56 | # airflow.operators.postgres_operator
57 | Mapping()
| ^^^^^^^
58 |
59 | # airflow.secrets
|
AIR301 [*] `airflow.secrets.cache.SecretCache` is removed in Airflow 3.0
--> AIR301_names.py:64:1
|
63 | # airflow.secrets.cache
64 | SecretCache()
| ^^^^^^^^^^^
|
help: Use `SecretCache` from `airflow.sdk` instead.
13 | from airflow.contrib.aws_athena_hook import AWSAthenaHook
14 | from airflow.datasets import DatasetAliasEvent
15 | from airflow.operators.subdag import SubDagOperator
15 | from airflow.operators.postgres_operator import Mapping
16 | from airflow.operators.subdag import SubDagOperator
- from airflow.secrets.cache import SecretCache
16 | from airflow.secrets.local_filesystem import LocalFilesystemBackend
17 | from airflow.triggers.external_task import TaskStateTrigger
18 | from airflow.utils import dates
17 | from airflow.secrets.local_filesystem import LocalFilesystemBackend
18 | from airflow.triggers.external_task import TaskStateTrigger
19 | from airflow.utils import dates
--------------------------------------------------------------------------------
33 | from airflow.utils.trigger_rule import TriggerRule
34 | from airflow.www.auth import has_access, has_access_dataset
35 | from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key
36 + from airflow.sdk import SecretCache
37 |
38 | # airflow root
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
34 | from airflow.utils.trigger_rule import TriggerRule
35 | from airflow.www.auth import has_access, has_access_dataset
36 | from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key
37 + from airflow.sdk import SecretCache
38 |
39 | # airflow root
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
note: This is an unsafe fix and may change runtime behavior
AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0
--> AIR301_names.py:65:1
|
64 | # airflow.triggers.external_task
65 | TaskStateTrigger()
| ^^^^^^^^^^^^^^^^
66 |
67 | # airflow.utils.date
|
AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
--> AIR301_names.py:68:1
|
67 | # airflow.utils.date
68 | dates.date_range
67 | # airflow.triggers.external_task
68 | TaskStateTrigger()
| ^^^^^^^^^^^^^^^^
69 | dates.days_ago
69 |
70 | # airflow.utils.date
|
AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
--> AIR301_names.py:69:1
|
67 | # airflow.utils.date
68 | dates.date_range
69 | dates.days_ago
| ^^^^^^^^^^^^^^
70 |
71 | date_range
|
help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
--> AIR301_names.py:71:1
|
69 | dates.days_ago
70 |
71 | date_range
| ^^^^^^^^^^
72 | days_ago
73 | infer_time_unit
70 | # airflow.utils.date
71 | dates.date_range
| ^^^^^^^^^^^^^^^^
72 | dates.days_ago
|
AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
--> AIR301_names.py:72:1
|
71 | date_range
72 | days_ago
70 | # airflow.utils.date
71 | dates.date_range
72 | dates.days_ago
| ^^^^^^^^^^^^^^
73 |
74 | date_range
|
help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
--> AIR301_names.py:74:1
|
72 | dates.days_ago
73 |
74 | date_range
| ^^^^^^^^^^
75 | days_ago
76 | infer_time_unit
|
AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
--> AIR301_names.py:75:1
|
74 | date_range
75 | days_ago
| ^^^^^^^^
73 | infer_time_unit
74 | parse_execution_date
76 | infer_time_unit
77 | parse_execution_date
|
help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
AIR301 `airflow.utils.dates.infer_time_unit` is removed in Airflow 3.0
--> AIR301_names.py:73:1
--> AIR301_names.py:76:1
|
71 | date_range
72 | days_ago
73 | infer_time_unit
74 | date_range
75 | days_ago
76 | infer_time_unit
| ^^^^^^^^^^^^^^^
74 | parse_execution_date
75 | round_time
77 | parse_execution_date
78 | round_time
|
AIR301 `airflow.utils.dates.parse_execution_date` is removed in Airflow 3.0
--> AIR301_names.py:74:1
--> AIR301_names.py:77:1
|
72 | days_ago
73 | infer_time_unit
74 | parse_execution_date
75 | days_ago
76 | infer_time_unit
77 | parse_execution_date
| ^^^^^^^^^^^^^^^^^^^^
75 | round_time
76 | scale_time_units
78 | round_time
79 | scale_time_units
|
AIR301 `airflow.utils.dates.round_time` is removed in Airflow 3.0
--> AIR301_names.py:75:1
--> AIR301_names.py:78:1
|
73 | infer_time_unit
74 | parse_execution_date
75 | round_time
76 | infer_time_unit
77 | parse_execution_date
78 | round_time
| ^^^^^^^^^^
76 | scale_time_units
79 | scale_time_units
|
AIR301 `airflow.utils.dates.scale_time_units` is removed in Airflow 3.0
--> AIR301_names.py:76:1
--> AIR301_names.py:79:1
|
74 | parse_execution_date
75 | round_time
76 | scale_time_units
77 | parse_execution_date
78 | round_time
79 | scale_time_units
| ^^^^^^^^^^^^^^^^
77 |
78 | # This one was not deprecated.
80 |
81 | # This one was not deprecated.
|
AIR301 `airflow.utils.dag_cycle_tester.test_cycle` is removed in Airflow 3.0
--> AIR301_names.py:83:1
--> AIR301_names.py:86:1
|
82 | # airflow.utils.dag_cycle_tester
83 | test_cycle
85 | # airflow.utils.dag_cycle_tester
86 | test_cycle
| ^^^^^^^^^^
|
AIR301 `airflow.utils.db.create_session` is removed in Airflow 3.0
--> AIR301_names.py:87:1
--> AIR301_names.py:90:1
|
86 | # airflow.utils.db
87 | create_session
89 | # airflow.utils.db
90 | create_session
| ^^^^^^^^^^^^^^
88 |
89 | # airflow.utils.decorators
91 |
92 | # airflow.utils.decorators
|
AIR301 `airflow.utils.decorators.apply_defaults` is removed in Airflow 3.0
--> AIR301_names.py:90:1
--> AIR301_names.py:93:1
|
89 | # airflow.utils.decorators
90 | apply_defaults
92 | # airflow.utils.decorators
93 | apply_defaults
| ^^^^^^^^^^^^^^
91 |
92 | # airflow.utils.file
94 |
95 | # airflow.utils.file
|
help: `apply_defaults` is now unconditionally done and can be safely removed.
AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0
--> AIR301_names.py:93:1
--> AIR301_names.py:96:1
|
92 | # airflow.utils.file
93 | mkdirs
95 | # airflow.utils.file
96 | mkdirs
| ^^^^^^
|
help: Use `pathlib.Path({path}).mkdir` instead
AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0
--> AIR301_names.py:97:1
|
96 | # airflow.utils.state
97 | SHUTDOWN
| ^^^^^^^^
98 | terminating_states
|
--> AIR301_names.py:100:1
|
99 | # airflow.utils.state
100 | SHUTDOWN
| ^^^^^^^^
101 | terminating_states
|
AIR301 `airflow.utils.state.terminating_states` is removed in Airflow 3.0
--> AIR301_names.py:98:1
--> AIR301_names.py:101:1
|
96 | # airflow.utils.state
97 | SHUTDOWN
98 | terminating_states
99 | # airflow.utils.state
100 | SHUTDOWN
101 | terminating_states
| ^^^^^^^^^^^^^^^^^^
99 |
100 | # airflow.utils.trigger_rule
102 |
103 | # airflow.utils.trigger_rule
|
AIR301 `airflow.utils.trigger_rule.TriggerRule.DUMMY` is removed in Airflow 3.0
--> AIR301_names.py:101:1
--> AIR301_names.py:104:1
|
100 | # airflow.utils.trigger_rule
101 | TriggerRule.DUMMY
103 | # airflow.utils.trigger_rule
104 | TriggerRule.DUMMY
| ^^^^^^^^^^^^^^^^^
102 | TriggerRule.NONE_FAILED_OR_SKIPPED
105 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
AIR301 `airflow.utils.trigger_rule.TriggerRule.NONE_FAILED_OR_SKIPPED` is removed in Airflow 3.0
--> AIR301_names.py:102:1
--> AIR301_names.py:105:1
|
100 | # airflow.utils.trigger_rule
101 | TriggerRule.DUMMY
102 | TriggerRule.NONE_FAILED_OR_SKIPPED
103 | # airflow.utils.trigger_rule
104 | TriggerRule.DUMMY
105 | TriggerRule.NONE_FAILED_OR_SKIPPED
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
AIR301 `airflow.www.auth.has_access` is removed in Airflow 3.0
--> AIR301_names.py:106:1
--> AIR301_names.py:109:1
|
105 | # airflow.www.auth
106 | has_access
108 | # airflow.www.auth
109 | has_access
| ^^^^^^^^^^
107 | has_access_dataset
110 | has_access_dataset
|
AIR301 `airflow.www.auth.has_access_dataset` is removed in Airflow 3.0
--> AIR301_names.py:107:1
--> AIR301_names.py:110:1
|
105 | # airflow.www.auth
106 | has_access
107 | has_access_dataset
108 | # airflow.www.auth
109 | has_access
110 | has_access_dataset
| ^^^^^^^^^^^^^^^^^^
108 |
109 | # airflow.www.utils
111 |
112 | # airflow.www.utils
|
AIR301 `airflow.www.utils.get_sensitive_variables_fields` is removed in Airflow 3.0
--> AIR301_names.py:110:1
--> AIR301_names.py:113:1
|
109 | # airflow.www.utils
110 | get_sensitive_variables_fields
112 | # airflow.www.utils
113 | get_sensitive_variables_fields
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
111 | should_hide_value_for_key
114 | should_hide_value_for_key
|
AIR301 `airflow.www.utils.should_hide_value_for_key` is removed in Airflow 3.0
--> AIR301_names.py:111:1
--> AIR301_names.py:114:1
|
109 | # airflow.www.utils
110 | get_sensitive_variables_fields
111 | should_hide_value_for_key
112 | # airflow.www.utils
113 | get_sensitive_variables_fields
114 | should_hide_value_for_key
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|

View File

@@ -20,11 +20,3 @@ help: Install `apache-airflow-providers-postgres>=1.0.0` and use `PostgresHook`
6 | PostgresHook()
7 | Mapping()
note: This is an unsafe fix and may change runtime behavior
AIR302 `airflow.operators.postgres_operator.Mapping` is removed in Airflow 3.0
--> AIR302_postgres.py:7:1
|
6 | PostgresHook()
7 | Mapping()
| ^^^^^^^
|

View File

@@ -558,7 +558,7 @@ AIR311 [*] `airflow.timetables.datasets.DatasetOrTimeSchedule` is removed in Air
--> AIR311_names.py:73:1
|
72 | # airflow.timetables.datasets
73 | DatasetOrTimeSchedule()
73 | DatasetOrTimeSchedule(datasets=[])
| ^^^^^^^^^^^^^^^^^^^^^
74 |
75 | # airflow.utils.dag_parsing_context
@@ -570,12 +570,31 @@ help: Use `AssetOrTimeSchedule` from `airflow.timetables.assets` instead.
71 + from airflow.timetables.assets import AssetOrTimeSchedule
72 |
73 | # airflow.timetables.datasets
- DatasetOrTimeSchedule()
74 + AssetOrTimeSchedule()
- DatasetOrTimeSchedule(datasets=[])
74 + AssetOrTimeSchedule(datasets=[])
75 |
76 | # airflow.utils.dag_parsing_context
77 | get_parsing_context()
AIR311 [*] `datasets` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
--> AIR311_names.py:73:23
|
72 | # airflow.timetables.datasets
73 | DatasetOrTimeSchedule(datasets=[])
| ^^^^^^^^
74 |
75 | # airflow.utils.dag_parsing_context
|
help: Use `assets` instead
70 | from airflow.utils.dag_parsing_context import get_parsing_context
71 |
72 | # airflow.timetables.datasets
- DatasetOrTimeSchedule(datasets=[])
73 + DatasetOrTimeSchedule(assets=[])
74 |
75 | # airflow.utils.dag_parsing_context
76 | get_parsing_context()
AIR311 [*] `airflow.utils.dag_parsing_context.get_parsing_context` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
--> AIR311_names.py:76:1
|
@@ -593,7 +612,7 @@ help: Use `get_parsing_context` from `airflow.sdk` instead.
70 + from airflow.sdk import get_parsing_context
71 |
72 | # airflow.timetables.datasets
73 | DatasetOrTimeSchedule()
73 | DatasetOrTimeSchedule(datasets=[])
note: This is an unsafe fix and may change runtime behavior
AIR311 [*] `airflow.decorators.base.DecoratedMappedOperator` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.

View File

@@ -79,7 +79,6 @@ impl Violation for FastApiUnusedPathParameter {
function_name,
is_positional,
} = self;
#[expect(clippy::if_not_else)]
if !is_positional {
format!(
"Parameter `{arg_name}` appears in route path, but not in `{function_name}` signature"

View File

@@ -16,8 +16,6 @@ mod tests {
use crate::settings::LinterSettings;
use crate::test::test_path;
use crate::settings::types::PreviewMode;
use ruff_python_ast::PythonVersion;
#[test_case(Rule::AbstractBaseClassWithoutAbstractMethod, Path::new("B024.py"))]
@@ -177,23 +175,4 @@ mod tests {
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::AssertRaisesException, Path::new("B017_0.py"))]
#[test_case(Rule::AssertRaisesException, Path::new("B017_1.py"))]
fn rules_preview(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("flake8_bugbear").join(path).as_path(),
&LinterSettings {
preview: PreviewMode::Enabled,
..LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -1,4 +1,41 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
---
B017 Do not assert blind exception: `Exception`
--> B017_1.py:20:9
|
18 | class Foobar(unittest.TestCase):
19 | def call_form_raises(self) -> None:
20 | self.assertRaises(Exception, something_else)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
21 | self.assertRaises(BaseException, something_else)
|
B017 Do not assert blind exception: `BaseException`
--> B017_1.py:21:9
|
19 | def call_form_raises(self) -> None:
20 | self.assertRaises(Exception, something_else)
21 | self.assertRaises(BaseException, something_else)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
B017 Do not assert blind exception: `Exception`
--> B017_1.py:25:5
|
24 | def test_pytest_call_form() -> None:
25 | pytest.raises(Exception, something_else)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
26 | pytest.raises(BaseException, something_else)
|
B017 Do not assert blind exception: `BaseException`
--> B017_1.py:26:5
|
24 | def test_pytest_call_form() -> None:
25 | pytest.raises(Exception, something_else)
26 | pytest.raises(BaseException, something_else)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
27 |
28 | pytest.raises(Exception, something_else, match="hello")
|

View File

@@ -1,79 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
---
B017 Do not assert blind exception: `Exception`
--> B017_0.py:23:14
|
21 | class Foobar(unittest.TestCase):
22 | def evil_raises(self) -> None:
23 | with self.assertRaises(Exception):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
24 | raise Exception("Evil I say!")
|
B017 Do not assert blind exception: `BaseException`
--> B017_0.py:27:14
|
26 | def also_evil_raises(self) -> None:
27 | with self.assertRaises(BaseException):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28 | raise Exception("Evil I say!")
|
B017 Do not assert blind exception: `Exception`
--> B017_0.py:45:10
|
44 | def test_pytest_raises():
45 | with pytest.raises(Exception):
| ^^^^^^^^^^^^^^^^^^^^^^^^
46 | raise ValueError("Hello")
|
B017 Do not assert blind exception: `Exception`
--> B017_0.py:48:10
|
46 | raise ValueError("Hello")
47 |
48 | with pytest.raises(Exception), pytest.raises(ValueError):
| ^^^^^^^^^^^^^^^^^^^^^^^^
49 | raise ValueError("Hello")
|
B017 Do not assert blind exception: `Exception`
--> B017_0.py:57:36
|
55 | raise ValueError("This is also fine")
56 |
57 | with contextlib.nullcontext(), pytest.raises(Exception):
| ^^^^^^^^^^^^^^^^^^^^^^^^
58 | raise ValueError("Multiple context managers")
|
B017 Do not assert blind exception: `Exception`
--> B017_0.py:62:10
|
61 | def test_pytest_raises_keyword():
62 | with pytest.raises(expected_exception=Exception):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
63 | raise ValueError("Should be flagged")
|
B017 Do not assert blind exception: `Exception`
--> B017_0.py:68:18
|
66 | class TestKwargs(unittest.TestCase):
67 | def test_method(self):
68 | with self.assertRaises(exception=Exception):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
69 | raise ValueError("Should be flagged")
|
B017 Do not assert blind exception: `BaseException`
--> B017_0.py:71:18
|
69 | raise ValueError("Should be flagged")
70 |
71 | with self.assertRaises(exception=BaseException):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
72 | raise ValueError("Should be flagged")
|

View File

@@ -1,41 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
---
B017 Do not assert blind exception: `Exception`
--> B017_1.py:20:9
|
18 | class Foobar(unittest.TestCase):
19 | def call_form_raises(self) -> None:
20 | self.assertRaises(Exception, something_else)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
21 | self.assertRaises(BaseException, something_else)
|
B017 Do not assert blind exception: `BaseException`
--> B017_1.py:21:9
|
19 | def call_form_raises(self) -> None:
20 | self.assertRaises(Exception, something_else)
21 | self.assertRaises(BaseException, something_else)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
B017 Do not assert blind exception: `Exception`
--> B017_1.py:25:5
|
24 | def test_pytest_call_form() -> None:
25 | pytest.raises(Exception, something_else)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
26 | pytest.raises(BaseException, something_else)
|
B017 Do not assert blind exception: `BaseException`
--> B017_1.py:26:5
|
24 | def test_pytest_call_form() -> None:
25 | pytest.raises(Exception, something_else)
26 | pytest.raises(BaseException, something_else)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
27 |
28 | pytest.raises(Exception, something_else, match="hello")
|

View File

@@ -10,7 +10,7 @@ mod tests {
use crate::registry::Rule;
use crate::test::test_path;
use crate::{assert_diagnostics, assert_diagnostics_diff, settings};
use crate::{assert_diagnostics, settings};
#[test_case(Path::new("COM81.py"))]
#[test_case(Path::new("COM81_syntax_error.py"))]
@@ -27,28 +27,4 @@ mod tests {
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(Path::new("COM81.py"))]
#[test_case(Path::new("COM81_syntax_error.py"))]
fn preview_rules(path: &Path) -> Result<()> {
let snapshot = format!("preview_diff__{}", path.to_string_lossy());
let rules = vec![
Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma,
];
let settings_before = settings::LinterSettings::for_rules(rules.clone());
let settings_after = settings::LinterSettings {
preview: crate::settings::types::PreviewMode::Enabled,
..settings::LinterSettings::for_rules(rules)
};
assert_diagnostics_diff!(
snapshot,
Path::new("flake8_commas").join(path).as_path(),
&settings_before,
&settings_after
);
Ok(())
}
}

View File

@@ -5,8 +5,6 @@ use ruff_text_size::{Ranged, TextRange};
use crate::Locator;
use crate::checkers::ast::LintContext;
use crate::preview::is_trailing_comma_type_params_enabled;
use crate::settings::LinterSettings;
use crate::{AlwaysFixableViolation, Violation};
use crate::{Edit, Fix};
@@ -298,7 +296,7 @@ pub(crate) fn trailing_commas(
}
// Update the comma context stack.
let context = update_context(token, prev, prev_prev, &mut stack, lint_context.settings());
let context = update_context(token, prev, prev_prev, &mut stack);
check_token(token, prev, prev_prev, context, locator, lint_context);
@@ -417,7 +415,6 @@ fn update_context(
prev: SimpleToken,
prev_prev: SimpleToken,
stack: &mut Vec<Context>,
settings: &LinterSettings,
) -> Context {
let new_context = match token.ty {
TokenType::OpeningBracket => match (prev.ty, prev_prev.ty) {
@@ -427,19 +424,11 @@ fn update_context(
}
_ => Context::new(ContextType::Tuple),
},
TokenType::OpeningSquareBracket if is_trailing_comma_type_params_enabled(settings) => {
match (prev.ty, prev_prev.ty) {
(TokenType::Named, TokenType::Def | TokenType::Class | TokenType::Type) => {
Context::new(ContextType::TypeParameters)
}
(TokenType::ClosingBracket | TokenType::Named | TokenType::String, _) => {
Context::new(ContextType::Subscript)
}
_ => Context::new(ContextType::List),
TokenType::OpeningSquareBracket => match (prev.ty, prev_prev.ty) {
(TokenType::Named, TokenType::Def | TokenType::Class | TokenType::Type) => {
Context::new(ContextType::TypeParameters)
}
}
TokenType::OpeningSquareBracket => match prev.ty {
TokenType::ClosingBracket | TokenType::Named | TokenType::String => {
(TokenType::ClosingBracket | TokenType::Named | TokenType::String, _) => {
Context::new(ContextType::Subscript)
}
_ => Context::new(ContextType::List),

View File

@@ -939,3 +939,111 @@ help: Add trailing comma
644 | )
645 |
646 | assert False, f"<- This is not a trailing comma"
COM812 [*] Trailing comma missing
--> COM81.py:655:6
|
654 | type X[
655 | T
| ^
656 | ] = T
657 | def f[
|
help: Add trailing comma
652 | }"""
653 |
654 | type X[
- T
655 + T,
656 | ] = T
657 | def f[
658 | T
COM812 [*] Trailing comma missing
--> COM81.py:658:6
|
656 | ] = T
657 | def f[
658 | T
| ^
659 | ](): pass
660 | class C[
|
help: Add trailing comma
655 | T
656 | ] = T
657 | def f[
- T
658 + T,
659 | ](): pass
660 | class C[
661 | T
COM812 [*] Trailing comma missing
--> COM81.py:661:6
|
659 | ](): pass
660 | class C[
661 | T
| ^
662 | ]: pass
|
help: Add trailing comma
658 | T
659 | ](): pass
660 | class C[
- T
661 + T,
662 | ]: pass
663 |
664 | type X[T,] = T
COM819 [*] Trailing comma prohibited
--> COM81.py:664:9
|
662 | ]: pass
663 |
664 | type X[T,] = T
| ^
665 | def f[T,](): pass
666 | class C[T,]: pass
|
help: Remove trailing comma
661 | T
662 | ]: pass
663 |
- type X[T,] = T
664 + type X[T] = T
665 | def f[T,](): pass
666 | class C[T,]: pass
COM819 [*] Trailing comma prohibited
--> COM81.py:665:8
|
664 | type X[T,] = T
665 | def f[T,](): pass
| ^
666 | class C[T,]: pass
|
help: Remove trailing comma
662 | ]: pass
663 |
664 | type X[T,] = T
- def f[T,](): pass
665 + def f[T](): pass
666 | class C[T,]: pass
COM819 [*] Trailing comma prohibited
--> COM81.py:666:10
|
664 | type X[T,] = T
665 | def f[T,](): pass
666 | class C[T,]: pass
| ^
|
help: Remove trailing comma
663 |
664 | type X[T,] = T
665 | def f[T,](): pass
- class C[T,]: pass
666 + class C[T]: pass

View File

@@ -1,124 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
--- Summary ---
Removed: 0
Added: 6
--- Added ---
COM812 [*] Trailing comma missing
--> COM81.py:655:6
|
654 | type X[
655 | T
| ^
656 | ] = T
657 | def f[
|
help: Add trailing comma
652 | }"""
653 |
654 | type X[
- T
655 + T,
656 | ] = T
657 | def f[
658 | T
COM812 [*] Trailing comma missing
--> COM81.py:658:6
|
656 | ] = T
657 | def f[
658 | T
| ^
659 | ](): pass
660 | class C[
|
help: Add trailing comma
655 | T
656 | ] = T
657 | def f[
- T
658 + T,
659 | ](): pass
660 | class C[
661 | T
COM812 [*] Trailing comma missing
--> COM81.py:661:6
|
659 | ](): pass
660 | class C[
661 | T
| ^
662 | ]: pass
|
help: Add trailing comma
658 | T
659 | ](): pass
660 | class C[
- T
661 + T,
662 | ]: pass
663 |
664 | type X[T,] = T
COM819 [*] Trailing comma prohibited
--> COM81.py:664:9
|
662 | ]: pass
663 |
664 | type X[T,] = T
| ^
665 | def f[T,](): pass
666 | class C[T,]: pass
|
help: Remove trailing comma
661 | T
662 | ]: pass
663 |
- type X[T,] = T
664 + type X[T] = T
665 | def f[T,](): pass
666 | class C[T,]: pass
COM819 [*] Trailing comma prohibited
--> COM81.py:665:8
|
664 | type X[T,] = T
665 | def f[T,](): pass
| ^
666 | class C[T,]: pass
|
help: Remove trailing comma
662 | ]: pass
663 |
664 | type X[T,] = T
- def f[T,](): pass
665 + def f[T](): pass
666 | class C[T,]: pass
COM819 [*] Trailing comma prohibited
--> COM81.py:666:10
|
664 | type X[T,] = T
665 | def f[T,](): pass
666 | class C[T,]: pass
| ^
|
help: Remove trailing comma
663 |
664 | type X[T,] = T
665 | def f[T,](): pass
- class C[T,]: pass
666 + class C[T]: pass

View File

@@ -1,10 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
--- Summary ---
Removed: 0
Added: 0

View File

@@ -122,6 +122,13 @@ pub(crate) fn unnecessary_map(checker: &Checker, call: &ast::ExprCall) {
}
};
// If the lambda body contains a `yield` or `yield from`, rewriting `map(lambda ...)` to a
// generator expression or any comprehension is invalid Python syntax
// (e.g., `yield` is not allowed inside generator or comprehension expressions). In such cases, skip.
if lambda_contains_yield(&lambda.body) {
return;
}
for iterable in iterables {
// For example, (x+1 for x in (c:=a)) is invalid syntax
// so we can't suggest it.
@@ -183,6 +190,13 @@ fn map_lambda_and_iterables<'a>(
Some((lambda, iterables))
}
/// Returns true if the expression tree contains a `yield` or `yield from` expression.
fn lambda_contains_yield(expr: &Expr) -> bool {
any_over_expr(expr, &|expr| {
matches!(expr, Expr::Yield(_) | Expr::YieldFrom(_))
})
}
/// A lambda as the first argument to `map()` has the "expected" arity when:
///
/// * It has exactly one parameter

View File

@@ -342,6 +342,7 @@ help: Replace `map()` with a set comprehension
75 + _ = t"{ {x % 2 == 0 for x in nums} }"
76 | _ = t"{dict(map(lambda v: (v, v**2), nums))}"
77 |
78 |
note: This is an unsafe fix and may change runtime behavior
C417 [*] Unnecessary `map()` usage (rewrite using a dict comprehension)
@@ -359,4 +360,6 @@ help: Replace `map()` with a dict comprehension
- _ = t"{dict(map(lambda v: (v, v**2), nums))}"
76 + _ = t"{ {v: v**2 for v in nums} }"
77 |
78 |
79 | # See https://github.com/astral-sh/ruff/issues/20198
note: This is an unsafe fix and may change runtime behavior

View File

@@ -9,7 +9,6 @@ mod tests {
use anyhow::Result;
use crate::registry::Rule;
use crate::settings::types::PreviewMode;
use crate::test::test_path;
use crate::{assert_diagnostics, settings};
@@ -47,15 +46,14 @@ mod tests {
}
#[test]
fn preview_string_exception() -> Result<()> {
fn string_exception() -> Result<()> {
let diagnostics = test_path(
Path::new("flake8_errmsg/EM101_byte_string.py"),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
..settings::LinterSettings::for_rule(Rule::RawStringInException)
},
)?;
assert_diagnostics!("preview", diagnostics);
assert_diagnostics!(diagnostics);
Ok(())
}
}

View File

@@ -7,16 +7,12 @@ use ruff_text_size::Ranged;
use crate::Locator;
use crate::checkers::ast::Checker;
use crate::preview::is_raise_exception_byte_string_enabled;
use crate::registry::Rule;
use crate::{Edit, Fix, FixAvailability, Violation};
/// ## What it does
/// Checks for the use of string literals in exception constructors.
///
/// In [preview], this rule checks for byte string literals in
/// exception constructors.
///
/// ## Why is this bad?
/// Python includes the `raise` in the default traceback (and formatters
/// like Rich and IPython do too).
@@ -51,8 +47,6 @@ use crate::{Edit, Fix, FixAvailability, Violation};
/// raise RuntimeError(msg)
/// RuntimeError: 'Some value' is incorrect
/// ```
///
/// [preview]: https://docs.astral.sh/ruff/preview/
#[derive(ViolationMetadata)]
pub(crate) struct RawStringInException;
@@ -218,9 +212,7 @@ pub(crate) fn string_in_exception(checker: &Checker, stmt: &Stmt, exc: &Expr) {
// Check for byte string literals.
Expr::BytesLiteral(ast::ExprBytesLiteral { value: bytes, .. }) => {
if checker.settings().rules.enabled(Rule::RawStringInException) {
if bytes.len() >= checker.settings().flake8_errmsg.max_string_length
&& is_raise_exception_byte_string_enabled(checker.settings())
{
if bytes.len() >= checker.settings().flake8_errmsg.max_string_length {
let mut diagnostic =
checker.report_diagnostic(RawStringInException, first.range());
if let Some(indentation) = whitespace::indentation(checker.source(), stmt) {

View File

@@ -1,6 +1,6 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast as ast;
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::{self as ast, ParameterWithDefault};
use ruff_python_semantic::analyze::function_type;
use crate::Violation;
@@ -85,16 +85,9 @@ pub(crate) fn pep_484_positional_parameter(checker: &Checker, function_def: &ast
function_type::FunctionType::Method | function_type::FunctionType::ClassMethod
));
if let Some(arg) = function_def.parameters.args.get(skip) {
if is_old_style_positional_only(arg) {
checker.report_diagnostic(Pep484StylePositionalOnlyParameter, arg.identifier());
if let Some(param) = function_def.parameters.args.get(skip) {
if param.uses_pep_484_positional_only_convention() {
checker.report_diagnostic(Pep484StylePositionalOnlyParameter, param.identifier());
}
}
}
/// Returns `true` if the [`ParameterWithDefault`] is an old-style positional-only parameter (i.e.,
/// its name starts with `__` and does not end with `__`).
fn is_old_style_positional_only(param: &ParameterWithDefault) -> bool {
let arg_name = param.name();
arg_name.starts_with("__") && !arg_name.ends_with("__")
}

View File

@@ -59,7 +59,6 @@ mod tests {
Ok(())
}
#[test_case(Rule::MultipleWithStatements, Path::new("SIM117.py"))]
#[test_case(Rule::SplitStaticString, Path::new("SIM905.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(

View File

@@ -10,7 +10,6 @@ use super::fix_with;
use crate::Fix;
use crate::checkers::ast::Checker;
use crate::fix::edits::fits;
use crate::preview::is_multiple_with_statements_fix_safe_enabled;
use crate::{FixAvailability, Violation};
/// ## What it does
@@ -45,15 +44,8 @@ use crate::{FixAvailability, Violation};
/// pass
/// ```
///
/// ## Fix safety
///
/// This fix is marked as always unsafe unless [preview] mode is enabled, in which case it is always
/// marked as safe. Note that the fix is unavailable if it would remove comments (in either case).
///
/// ## References
/// - [Python documentation: The `with` statement](https://docs.python.org/3/reference/compound_stmts.html#the-with-statement)
///
/// [preview]: https://docs.astral.sh/ruff/preview/
#[derive(ViolationMetadata)]
pub(crate) struct MultipleWithStatements;
@@ -195,11 +187,7 @@ pub(crate) fn multiple_with_statements(
checker.settings().tab_size,
)
}) {
if is_multiple_with_statements_fix_safe_enabled(checker.settings()) {
Ok(Some(Fix::safe_edit(edit)))
} else {
Ok(Some(Fix::unsafe_edit(edit)))
}
Ok(Some(Fix::safe_edit(edit)))
} else {
Ok(None)
}

View File

@@ -20,7 +20,6 @@ help: Combine `with` statements
4 |
5 | # SIM117
6 | with A():
note: This is an unsafe fix and may change runtime behavior
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:7:1
@@ -46,7 +45,6 @@ help: Combine `with` statements
10 |
11 | # SIM117
12 | with A() as a:
note: This is an unsafe fix and may change runtime behavior
SIM117 Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:13:1
@@ -84,7 +82,6 @@ help: Combine `with` statements
22 |
23 | # OK
24 | with A() as a:
note: This is an unsafe fix and may change runtime behavior
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:47:1
@@ -107,7 +104,6 @@ help: Combine `with` statements
49 |
50 | while True:
51 | # SIM117
note: This is an unsafe fix and may change runtime behavior
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:53:5
@@ -144,7 +140,6 @@ help: Combine `with` statements
64 | "this for some reason")
65 |
66 | # SIM117
note: This is an unsafe fix and may change runtime behavior
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:68:1
@@ -171,7 +166,6 @@ help: Combine `with` statements
73 |
74 | # SIM117
75 | with A() as a:
note: This is an unsafe fix and may change runtime behavior
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:76:1
@@ -203,7 +197,6 @@ help: Combine `with` statements
81 |
82 | # SIM117
83 | with (
note: This is an unsafe fix and may change runtime behavior
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:84:1
@@ -237,7 +230,6 @@ help: Combine `with` statements
90 |
91 | # SIM117 (auto-fixable)
92 | with A("01ß9💣28901ß9💣28901ß9💣289") as a:
note: This is an unsafe fix and may change runtime behavior
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:95:1
@@ -260,7 +252,6 @@ help: Combine `with` statements
97 |
98 | # SIM117 (not auto-fixable too long)
99 | with A("01ß9💣28901ß9💣28901ß9💣2890") as a:
note: This is an unsafe fix and may change runtime behavior
SIM117 Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:100:1
@@ -319,7 +310,6 @@ help: Combine `with` statements
136 |
137 | # Allow cascading for some statements.
138 | import anyio
note: This is an unsafe fix and may change runtime behavior
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:163:1
@@ -339,4 +329,3 @@ help: Combine `with` statements
- pass
163 + async with asyncio.timeout(1), A(), B():
164 + pass
note: This is an unsafe fix and may change runtime behavior

View File

@@ -1,331 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
---
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:2:1
|
1 | # SIM117
2 | / with A() as a:
3 | | with B() as b:
| |__________________^
4 | print("hello")
|
help: Combine `with` statements
1 | # SIM117
- with A() as a:
- with B() as b:
- print("hello")
2 + with A() as a, B() as b:
3 + print("hello")
4 |
5 | # SIM117
6 | with A():
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:7:1
|
6 | # SIM117
7 | / with A():
8 | | with B():
| |_____________^
9 | with C():
10 | print("hello")
|
help: Combine `with` statements
4 | print("hello")
5 |
6 | # SIM117
- with A():
- with B():
- with C():
- print("hello")
7 + with A(), B():
8 + with C():
9 + print("hello")
10 |
11 | # SIM117
12 | with A() as a:
SIM117 Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:13:1
|
12 | # SIM117
13 | / with A() as a:
14 | | # Unfixable due to placement of this comment.
15 | | with B() as b:
| |__________________^
16 | print("hello")
|
help: Combine `with` statements
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:19:1
|
18 | # SIM117
19 | / with A() as a:
20 | | with B() as b:
| |__________________^
21 | # Fixable due to placement of this comment.
22 | print("hello")
|
help: Combine `with` statements
16 | print("hello")
17 |
18 | # SIM117
- with A() as a:
- with B() as b:
- # Fixable due to placement of this comment.
- print("hello")
19 + with A() as a, B() as b:
20 + # Fixable due to placement of this comment.
21 + print("hello")
22 |
23 | # OK
24 | with A() as a:
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:47:1
|
46 | # SIM117
47 | / async with A() as a:
48 | | async with B() as b:
| |________________________^
49 | print("hello")
|
help: Combine `with` statements
44 | print("hello")
45 |
46 | # SIM117
- async with A() as a:
- async with B() as b:
- print("hello")
47 + async with A() as a, B() as b:
48 + print("hello")
49 |
50 | while True:
51 | # SIM117
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:53:5
|
51 | while True:
52 | # SIM117
53 | / with A() as a:
54 | | with B() as b:
| |______________________^
55 | """this
56 | is valid"""
|
help: Combine `with` statements
50 |
51 | while True:
52 | # SIM117
- with A() as a:
- with B() as b:
- """this
53 + with A() as a, B() as b:
54 + """this
55 | is valid"""
56 |
- """the indentation on
57 + """the indentation on
58 | this line is significant"""
59 |
- "this is" \
60 + "this is" \
61 | "allowed too"
62 |
- ("so is"
63 + ("so is"
64 | "this for some reason")
65 |
66 | # SIM117
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:68:1
|
67 | # SIM117
68 | / with (
69 | | A() as a,
70 | | B() as b,
71 | | ):
72 | | with C() as c:
| |__________________^
73 | print("hello")
|
help: Combine `with` statements
67 | # SIM117
68 | with (
69 | A() as a,
- B() as b,
70 + B() as b,C() as c
71 | ):
- with C() as c:
- print("hello")
72 + print("hello")
73 |
74 | # SIM117
75 | with A() as a:
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:76:1
|
75 | # SIM117
76 | / with A() as a:
77 | | with (
78 | | B() as b,
79 | | C() as c,
80 | | ):
| |______^
81 | print("hello")
|
help: Combine `with` statements
73 | print("hello")
74 |
75 | # SIM117
- with A() as a:
- with (
- B() as b,
- C() as c,
- ):
- print("hello")
76 + with (
77 + A() as a, B() as b,
78 + C() as c,
79 + ):
80 + print("hello")
81 |
82 | # SIM117
83 | with (
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:84:1
|
83 | # SIM117
84 | / with (
85 | | A() as a,
86 | | B() as b,
87 | | ):
88 | | with (
89 | | C() as c,
90 | | D() as d,
91 | | ):
| |______^
92 | print("hello")
|
help: Combine `with` statements
83 | # SIM117
84 | with (
85 | A() as a,
- B() as b,
86 + B() as b,C() as c,
87 + D() as d,
88 | ):
- with (
- C() as c,
- D() as d,
- ):
- print("hello")
89 + print("hello")
90 |
91 | # SIM117 (auto-fixable)
92 | with A("01ß9💣28901ß9💣28901ß9💣289") as a:
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:95:1
|
94 | # SIM117 (auto-fixable)
95 | / with A("01ß9💣28901ß9💣28901ß9💣289") as a:
96 | | with B("01ß9💣28901ß9💣28901ß9💣289") as b:
| |__________________________________________________^
97 | print("hello")
|
help: Combine `with` statements
92 | print("hello")
93 |
94 | # SIM117 (auto-fixable)
- with A("01ß9💣28901ß9💣28901ß9💣289") as a:
- with B("01ß9💣28901ß9💣28901ß9💣289") as b:
- print("hello")
95 + with A("01ß9💣28901ß9💣28901ß9💣289") as a, B("01ß9💣28901ß9💣28901ß9💣289") as b:
96 + print("hello")
97 |
98 | # SIM117 (not auto-fixable too long)
99 | with A("01ß9💣28901ß9💣28901ß9💣2890") as a:
SIM117 Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:100:1
|
99 | # SIM117 (not auto-fixable too long)
100 | / with A("01ß9💣28901ß9💣28901ß9💣2890") as a:
101 | | with B("01ß9💣28901ß9💣28901ß9💣289") as b:
| |__________________________________________________^
102 | print("hello")
|
help: Combine `with` statements
SIM117 Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:106:5
|
104 | # From issue #3025.
105 | async def main():
106 | / async with A() as a: # SIM117.
107 | | async with B() as b:
| |____________________________^
108 | print("async-inside!")
|
help: Combine `with` statements
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:126:1
|
125 | # SIM117
126 | / with A() as a:
127 | | with B() as b:
| |__________________^
128 | type ListOrSet[T] = list[T] | set[T]
|
help: Combine `with` statements
123 | f(b2, c2, d2)
124 |
125 | # SIM117
- with A() as a:
- with B() as b:
- type ListOrSet[T] = list[T] | set[T]
126 + with A() as a, B() as b:
127 + type ListOrSet[T] = list[T] | set[T]
128 |
- class ClassA[T: str]:
- def method1(self) -> T:
- ...
129 + class ClassA[T: str]:
130 + def method1(self) -> T:
131 + ...
132 |
- f" something { my_dict["key"] } something else "
133 + f" something { my_dict["key"] } something else "
134 |
- f"foo {f"bar {x}"} baz"
135 + f"foo {f"bar {x}"} baz"
136 |
137 | # Allow cascading for some statements.
138 | import anyio
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
--> SIM117.py:163:1
|
162 | # Do not suppress combination, if a context manager is already combined with another.
163 | / async with asyncio.timeout(1), A():
164 | | async with B():
| |___________________^
165 | pass
|
help: Combine `with` statements
160 | pass
161 |
162 | # Do not suppress combination, if a context manager is already combined with another.
- async with asyncio.timeout(1), A():
- async with B():
- pass
163 + async with asyncio.timeout(1), A(), B():
164 + pass

View File

@@ -66,7 +66,7 @@ impl TypingReference {
}
// prefer `from __future__ import annotations` to quoting
if settings.future_annotations()
if settings.future_annotations
&& !reference.in_typing_only_annotation()
&& reference.in_runtime_evaluated_annotation()
{

View File

@@ -14,7 +14,6 @@ mod tests {
use test_case::test_case;
use crate::registry::{Linter, Rule};
use crate::settings::types::PreviewMode;
use crate::test::{test_path, test_snippet};
use crate::{assert_diagnostics, settings};
@@ -86,7 +85,6 @@ mod tests {
Path::new("flake8_type_checking").join(path).as_path(),
&settings::LinterSettings {
future_annotations: true,
preview: PreviewMode::Enabled,
// also enable quoting annotations to check the interaction. the future import
// should take precedence.
flake8_type_checking: super::settings::Settings {

View File

@@ -11,12 +11,10 @@ use crate::checkers::ast::{Checker, DiagnosticGuard};
use crate::codes::Rule;
use crate::fix;
use crate::importer::ImportedMembers;
use crate::preview::is_full_path_match_source_strategy_enabled;
use crate::rules::flake8_type_checking::helpers::{
TypingReference, filter_contained, quote_annotation,
};
use crate::rules::flake8_type_checking::imports::ImportBinding;
use crate::rules::isort::categorize::MatchSourceStrategy;
use crate::rules::isort::{ImportSection, ImportType, categorize};
use crate::{Fix, FixAvailability, Violation};
@@ -40,6 +38,13 @@ use crate::{Fix, FixAvailability, Violation};
/// [`lint.flake8-type-checking.runtime-evaluated-decorators`] settings to mark them
/// as such.
///
/// If [`lint.future-annotations`] is set to `true`, `from __future__ import
/// annotations` will be added if doing so would enable an import to be
/// moved into an `if TYPE_CHECKING:` block. This takes precedence over the
/// [`lint.flake8-type-checking.quote-annotations`] setting described above if
/// both settings are enabled.
///
///
/// ## Example
/// ```python
/// from __future__ import annotations
@@ -65,18 +70,6 @@ use crate::{Fix, FixAvailability, Violation};
/// return len(sized)
/// ```
///
///
/// ## Preview
/// When [preview](https://docs.astral.sh/ruff/preview/) is enabled,
/// the criterion for determining whether an import is first-party
/// is stricter, which could affect whether this lint is triggered vs [`TC001`](https://docs.astral.sh/ruff/rules/typing-only-third-party-import/). See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) for more details.
///
/// If [`lint.future-annotations`] is set to `true`, `from __future__ import
/// annotations` will be added if doing so would enable an import to be moved into an `if
/// TYPE_CHECKING:` block. This takes precedence over the
/// [`lint.flake8-type-checking.quote-annotations`] setting described above if both settings are
/// enabled.
///
/// ## Options
/// - `lint.flake8-type-checking.quote-annotations`
/// - `lint.flake8-type-checking.runtime-evaluated-base-classes`
@@ -128,6 +121,12 @@ impl Violation for TypingOnlyFirstPartyImport {
/// [`lint.flake8-type-checking.runtime-evaluated-decorators`] settings to mark them
/// as such.
///
/// If [`lint.future-annotations`] is set to `true`, `from __future__ import
/// annotations` will be added if doing so would enable an import to be
/// moved into an `if TYPE_CHECKING:` block. This takes precedence over the
/// [`lint.flake8-type-checking.quote-annotations`] setting described above if
/// both settings are enabled.
///
/// ## Example
/// ```python
/// from __future__ import annotations
@@ -153,17 +152,6 @@ impl Violation for TypingOnlyFirstPartyImport {
/// return len(df)
/// ```
///
/// ## Preview
/// When [preview](https://docs.astral.sh/ruff/preview/) is enabled,
/// the criterion for determining whether an import is first-party
/// is stricter, which could affect whether this lint is triggered vs [`TC001`](https://docs.astral.sh/ruff/rules/typing-only-first-party-import/). See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) for more details.
///
/// If [`lint.future-annotations`] is set to `true`, `from __future__ import
/// annotations` will be added if doing so would enable an import to be moved into an `if
/// TYPE_CHECKING:` block. This takes precedence over the
/// [`lint.flake8-type-checking.quote-annotations`] setting described above if both settings are
/// enabled.
///
/// ## Options
/// - `lint.flake8-type-checking.quote-annotations`
/// - `lint.flake8-type-checking.runtime-evaluated-base-classes`
@@ -215,6 +203,12 @@ impl Violation for TypingOnlyThirdPartyImport {
/// [`lint.flake8-type-checking.runtime-evaluated-decorators`] settings to mark them
/// as such.
///
/// If [`lint.future-annotations`] is set to `true`, `from __future__ import
/// annotations` will be added if doing so would enable an import to be
/// moved into an `if TYPE_CHECKING:` block. This takes precedence over the
/// [`lint.flake8-type-checking.quote-annotations`] setting described above if
/// both settings are enabled.
///
/// ## Example
/// ```python
/// from __future__ import annotations
@@ -240,15 +234,6 @@ impl Violation for TypingOnlyThirdPartyImport {
/// return str(path)
/// ```
///
/// ## Preview
///
/// When [preview](https://docs.astral.sh/ruff/preview/) is enabled, if
/// [`lint.future-annotations`] is set to `true`, `from __future__ import
/// annotations` will be added if doing so would enable an import to be moved into an `if
/// TYPE_CHECKING:` block. This takes precedence over the
/// [`lint.flake8-type-checking.quote-annotations`] setting described above if both settings are
/// enabled.
///
/// ## Options
/// - `lint.flake8-type-checking.quote-annotations`
/// - `lint.flake8-type-checking.runtime-evaluated-base-classes`
@@ -297,7 +282,7 @@ pub(crate) fn typing_only_runtime_import(
// If we can't add a `__future__` import and in un-strict mode, don't flag typing-only
// imports that are implicitly loaded by way of a valid runtime import.
if !checker.settings().future_annotations()
if !checker.settings().future_annotations
&& !checker.settings().flake8_type_checking.strict
&& runtime_imports
.iter()
@@ -347,13 +332,6 @@ pub(crate) fn typing_only_runtime_import(
let source_name = import.source_name().join(".");
// Categorize the import, using coarse-grained categorization.
let match_source_strategy =
if is_full_path_match_source_strategy_enabled(checker.settings()) {
MatchSourceStrategy::FullPath
} else {
MatchSourceStrategy::Root
};
let import_type = match categorize(
&source_name,
qualified_name.is_unresolved_import(),
@@ -365,7 +343,6 @@ pub(crate) fn typing_only_runtime_import(
checker.settings().isort.no_sections,
&checker.settings().isort.section_order,
&checker.settings().isort.default_section,
match_source_strategy,
) {
ImportSection::Known(ImportType::LocalFolder | ImportType::FirstParty) => {
ImportType::FirstParty

View File

@@ -60,6 +60,7 @@ pub(crate) fn check_os_pathlib_single_arg_calls(
fn_argument: &str,
fix_enabled: bool,
violation: impl Violation,
applicability: Option<Applicability>,
) {
if call.arguments.len() != 1 {
return;
@@ -74,33 +75,39 @@ pub(crate) fn check_os_pathlib_single_arg_calls(
let mut diagnostic = checker.report_diagnostic(violation, call.func.range());
if fix_enabled {
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("pathlib", "Path"),
call.start(),
checker.semantic(),
)?;
let applicability = if checker.comment_ranges().intersects(range) {
Applicability::Unsafe
} else {
Applicability::Safe
};
let replacement = if is_pathlib_path_call(checker, arg) {
format!("{arg_code}.{attr}")
} else {
format!("{binding}({arg_code}).{attr}")
};
Ok(Fix::applicable_edits(
Edit::range_replacement(replacement, range),
[import_edit],
applicability,
))
});
if !fix_enabled {
return;
}
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("pathlib", "Path"),
call.start(),
checker.semantic(),
)?;
let replacement = if is_pathlib_path_call(checker, arg) {
format!("{arg_code}.{attr}")
} else {
format!("{binding}({arg_code}).{attr}")
};
let edit = Edit::range_replacement(replacement, range);
let fix = match applicability {
Some(Applicability::Unsafe) => Fix::unsafe_edits(edit, [import_edit]),
_ => {
let applicability = if checker.comment_ranges().intersects(range) {
Applicability::Unsafe
} else {
Applicability::Safe
};
Fix::applicable_edits(edit, [import_edit], applicability)
}
};
Ok(fix)
});
}
pub(crate) fn get_name_expr(expr: &Expr) -> Option<&ast::ExprName> {

View File

@@ -129,7 +129,6 @@ mod tests {
#[test_case(Rule::OsPathGetatime, Path::new("PTH203.py"))]
#[test_case(Rule::OsPathGetmtime, Path::new("PTH204.py"))]
#[test_case(Rule::OsPathGetctime, Path::new("PTH205.py"))]
#[test_case(Rule::OsSymlink, Path::new("PTH211.py"))]
fn preview_flake8_use_pathlib(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",

View File

@@ -1,9 +1,11 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_path_basename_enabled;
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.path.basename`.
@@ -34,7 +36,16 @@ use ruff_python_ast::ExprCall;
/// especially on older versions of Python.
///
/// ## Fix Safety
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
/// This rule's fix is always marked as unsafe because the replacement is not always semantically
/// equivalent to the original code. In particular, `pathlib` performs path normalization,
/// which can alter the result compared to `os.path.basename`. For example, this normalization:
///
/// - Collapses consecutive slashes (e.g., `"a//b"` → `"a/b"`).
/// - Removes trailing slashes (e.g., `"a/b/"` → `"a/b"`).
/// - Eliminates `"."` (e.g., `"a/./b"` → `"a/b"`).
///
/// As a result, code relying on the exact string returned by `os.path.basename`
/// may behave differently after the fix.
///
/// ## References
/// - [Python documentation: `PurePath.name`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.name)
@@ -62,6 +73,7 @@ pub(crate) fn os_path_basename(checker: &Checker, call: &ExprCall, segments: &[&
if segments != ["os", "path", "basename"] {
return;
}
check_os_pathlib_single_arg_calls(
checker,
call,
@@ -69,5 +81,6 @@ pub(crate) fn os_path_basename(checker: &Checker, call: &ExprCall, segments: &[&
"p",
is_fix_os_path_basename_enabled(checker.settings()),
OsPathBasename,
Some(Applicability::Unsafe),
);
}

View File

@@ -1,9 +1,11 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_path_dirname_enabled;
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.path.dirname`.
@@ -29,7 +31,16 @@ use ruff_python_ast::ExprCall;
/// ```
///
/// ## Fix Safety
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
/// This rule's fix is always marked as unsafe because the replacement is not always semantically
/// equivalent to the original code. In particular, `pathlib` performs path normalization,
/// which can alter the result compared to `os.path.dirname`. For example, this normalization:
///
/// - Collapses consecutive slashes (e.g., `"a//b"` → `"a/b"`).
/// - Removes trailing slashes (e.g., `"a/b/"` → `"a/b"`).
/// - Eliminates `"."` (e.g., `"a/./b"` → `"a/b"`).
///
/// As a result, code relying on the exact string returned by `os.path.dirname`
/// may behave differently after the fix.
///
/// ## Known issues
/// While using `pathlib` can improve the readability and type safety of your code,
@@ -62,6 +73,7 @@ pub(crate) fn os_path_dirname(checker: &Checker, call: &ExprCall, segments: &[&s
if segments != ["os", "path", "dirname"] {
return;
}
check_os_pathlib_single_arg_calls(
checker,
call,
@@ -69,5 +81,6 @@ pub(crate) fn os_path_dirname(checker: &Checker, call: &ExprCall, segments: &[&s
"p",
is_fix_os_path_dirname_enabled(checker.settings()),
OsPathDirname,
Some(Applicability::Unsafe),
);
}

View File

@@ -1,9 +1,10 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_path_exists_enabled;
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.path.exists`.
@@ -62,6 +63,7 @@ pub(crate) fn os_path_exists(checker: &Checker, call: &ExprCall, segments: &[&st
if segments != ["os", "path", "exists"] {
return;
}
check_os_pathlib_single_arg_calls(
checker,
call,
@@ -69,5 +71,6 @@ pub(crate) fn os_path_exists(checker: &Checker, call: &ExprCall, segments: &[&st
"path",
is_fix_os_path_exists_enabled(checker.settings()),
OsPathExists,
None,
);
}

View File

@@ -1,9 +1,10 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_path_expanduser_enabled;
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.path.expanduser`.
@@ -62,6 +63,7 @@ pub(crate) fn os_path_expanduser(checker: &Checker, call: &ExprCall, segments: &
if segments != ["os", "path", "expanduser"] {
return;
}
check_os_pathlib_single_arg_calls(
checker,
call,
@@ -69,5 +71,6 @@ pub(crate) fn os_path_expanduser(checker: &Checker, call: &ExprCall, segments: &
"path",
is_fix_os_path_expanduser_enabled(checker.settings()),
OsPathExpanduser,
None,
);
}

View File

@@ -1,9 +1,10 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_path_getatime_enabled;
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.path.getatime`.
@@ -65,6 +66,7 @@ pub(crate) fn os_path_getatime(checker: &Checker, call: &ExprCall, segments: &[&
if segments != ["os", "path", "getatime"] {
return;
}
check_os_pathlib_single_arg_calls(
checker,
call,
@@ -72,5 +74,6 @@ pub(crate) fn os_path_getatime(checker: &Checker, call: &ExprCall, segments: &[&
"filename",
is_fix_os_path_getatime_enabled(checker.settings()),
OsPathGetatime,
None,
);
}

View File

@@ -1,9 +1,10 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_path_getctime_enabled;
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.path.getctime`.
@@ -66,6 +67,7 @@ pub(crate) fn os_path_getctime(checker: &Checker, call: &ExprCall, segments: &[&
if segments != ["os", "path", "getctime"] {
return;
}
check_os_pathlib_single_arg_calls(
checker,
call,
@@ -73,5 +75,6 @@ pub(crate) fn os_path_getctime(checker: &Checker, call: &ExprCall, segments: &[&
"filename",
is_fix_os_path_getctime_enabled(checker.settings()),
OsPathGetctime,
None,
);
}

View File

@@ -1,9 +1,10 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_path_getmtime_enabled;
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.path.getmtime`.
@@ -66,6 +67,7 @@ pub(crate) fn os_path_getmtime(checker: &Checker, call: &ExprCall, segments: &[&
if segments != ["os", "path", "getmtime"] {
return;
}
check_os_pathlib_single_arg_calls(
checker,
call,
@@ -73,5 +75,6 @@ pub(crate) fn os_path_getmtime(checker: &Checker, call: &ExprCall, segments: &[&
"filename",
is_fix_os_path_getmtime_enabled(checker.settings()),
OsPathGetmtime,
None,
);
}

View File

@@ -1,9 +1,10 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_path_getsize_enabled;
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.path.getsize`.
@@ -66,6 +67,7 @@ pub(crate) fn os_path_getsize(checker: &Checker, call: &ExprCall, segments: &[&s
if segments != ["os", "path", "getsize"] {
return;
}
check_os_pathlib_single_arg_calls(
checker,
call,
@@ -73,5 +75,6 @@ pub(crate) fn os_path_getsize(checker: &Checker, call: &ExprCall, segments: &[&s
"filename",
is_fix_os_path_getsize_enabled(checker.settings()),
OsPathGetsize,
None,
);
}

View File

@@ -1,9 +1,10 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_path_isabs_enabled;
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.path.isabs`.
@@ -61,6 +62,7 @@ pub(crate) fn os_path_isabs(checker: &Checker, call: &ExprCall, segments: &[&str
if segments != ["os", "path", "isabs"] {
return;
}
check_os_pathlib_single_arg_calls(
checker,
call,
@@ -68,5 +70,6 @@ pub(crate) fn os_path_isabs(checker: &Checker, call: &ExprCall, segments: &[&str
"s",
is_fix_os_path_isabs_enabled(checker.settings()),
OsPathIsabs,
None,
);
}

View File

@@ -1,9 +1,10 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_path_isdir_enabled;
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.path.isdir`.
@@ -71,5 +72,6 @@ pub(crate) fn os_path_isdir(checker: &Checker, call: &ExprCall, segments: &[&str
"s",
is_fix_os_path_isdir_enabled(checker.settings()),
OsPathIsdir,
None,
);
}

View File

@@ -1,9 +1,10 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_path_isfile_enabled;
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.path.isfile`.
@@ -71,5 +72,6 @@ pub(crate) fn os_path_isfile(checker: &Checker, call: &ExprCall, segments: &[&st
"path",
is_fix_os_path_isfile_enabled(checker.settings()),
OsPathIsfile,
None,
);
}

View File

@@ -1,9 +1,10 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_path_islink_enabled;
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.path.islink`.
@@ -71,5 +72,6 @@ pub(crate) fn os_path_islink(checker: &Checker, call: &ExprCall, segments: &[&st
"path",
is_fix_os_path_islink_enabled(checker.settings()),
OsPathIslink,
None,
);
}

View File

@@ -1,11 +1,12 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{ExprCall, PythonVersion};
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_readlink_enabled;
use crate::rules::flake8_use_pathlib::helpers::{
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
};
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{ExprCall, PythonVersion};
/// ## What it does
/// Checks for uses of `os.readlink`.
@@ -87,5 +88,6 @@ pub(crate) fn os_readlink(checker: &Checker, call: &ExprCall, segments: &[&str])
"path",
is_fix_os_readlink_enabled(checker.settings()),
OsReadlink,
None,
);
}

View File

@@ -1,11 +1,12 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_remove_enabled;
use crate::rules::flake8_use_pathlib::helpers::{
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
};
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.remove`.
@@ -82,5 +83,6 @@ pub(crate) fn os_remove(checker: &Checker, call: &ExprCall, segments: &[&str]) {
"path",
is_fix_os_remove_enabled(checker.settings()),
OsRemove,
None,
);
}

View File

@@ -1,11 +1,12 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_rmdir_enabled;
use crate::rules::flake8_use_pathlib::helpers::{
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
};
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.rmdir`.
@@ -82,5 +83,6 @@ pub(crate) fn os_rmdir(checker: &Checker, call: &ExprCall, segments: &[&str]) {
"path",
is_fix_os_rmdir_enabled(checker.settings()),
OsRmdir,
None,
);
}

View File

@@ -1,11 +1,12 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_os_unlink_enabled;
use crate::rules::flake8_use_pathlib::helpers::{
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
};
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
/// ## What it does
/// Checks for uses of `os.unlink`.
@@ -82,5 +83,6 @@ pub(crate) fn os_unlink(checker: &Checker, call: &ExprCall, segments: &[&str]) {
"path",
is_fix_os_unlink_enabled(checker.settings()),
OsUnlink,
None,
);
}

View File

@@ -1,152 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
---
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:5:1
|
5 | os.symlink("usr/bin/python", "tmp/python")
| ^^^^^^^^^^
6 | os.symlink(b"usr/bin/python", b"tmp/python")
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
help: Replace with `Path(...).symlink_to(...)`
2 | from pathlib import Path
3 |
4 |
- os.symlink("usr/bin/python", "tmp/python")
5 + Path("tmp/python").symlink_to("usr/bin/python")
6 | os.symlink(b"usr/bin/python", b"tmp/python")
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
8 |
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:6:1
|
5 | os.symlink("usr/bin/python", "tmp/python")
6 | os.symlink(b"usr/bin/python", b"tmp/python")
| ^^^^^^^^^^
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
help: Replace with `Path(...).symlink_to(...)`
3 |
4 |
5 | os.symlink("usr/bin/python", "tmp/python")
- os.symlink(b"usr/bin/python", b"tmp/python")
6 + Path(b"tmp/python").symlink_to(b"usr/bin/python")
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
8 |
9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:9:1
|
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
8 |
9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
| ^^^^^^^^^^
10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
help: Replace with `Path(...).symlink_to(...)`
6 | os.symlink(b"usr/bin/python", b"tmp/python")
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
8 |
- os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
9 + Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True)
10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
12 |
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:10:1
|
9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
| ^^^^^^^^^^
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
help: Replace with `Path(...).symlink_to(...)`
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
8 |
9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
- os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
10 + Path(b"tmp/python").symlink_to(b"usr/bin/python", target_is_directory=True)
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
12 |
13 | fd = os.open(".", os.O_RDONLY)
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:17:1
|
15 | os.close(fd)
16 |
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
| ^^^^^^^^^^
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:18:1
|
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
| ^^^^^^^^^^
19 |
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
help: Replace with `Path(...).symlink_to(...)`
15 | os.close(fd)
16 |
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
- os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
18 + Path("tmp/python").symlink_to("usr/bin/python")
19 |
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
21 |
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:20:1
|
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
19 |
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
| ^^^^^^^^^^
21 |
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
help: Replace with `Path(...).symlink_to(...)`
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
19 |
- os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
20 + Path("tmp/python").symlink_to("usr/bin/python")
21 |
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:22:1
|
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
21 |
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
| ^^^^^^^^^^
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
help: Replace with `Path(...).symlink_to(...)`
19 |
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
21 |
- os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
22 + Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True)
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:23:1
|
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
| ^^^^^^^^^^
|
help: Replace with `Path(...).symlink_to(...)`

View File

@@ -466,6 +466,7 @@ help: Replace with `Path(...).name`
30 | os.path.dirname(p)
31 | os.path.samefile(p)
32 | os.path.splitext(p)
note: This is an unsafe fix and may change runtime behavior
PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
--> full_name.py:29:1
@@ -493,6 +494,7 @@ help: Replace with `Path(...).parent`
31 | os.path.samefile(p)
32 | os.path.splitext(p)
33 | with open(p) as fp:
note: This is an unsafe fix and may change runtime behavior
PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
--> full_name.py:30:1

View File

@@ -466,6 +466,7 @@ help: Replace with `Path(...).name`
30 | foo_p.dirname(p)
31 | foo_p.samefile(p)
32 | foo_p.splitext(p)
note: This is an unsafe fix and may change runtime behavior
PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
--> import_as.py:29:1
@@ -492,6 +493,7 @@ help: Replace with `Path(...).parent`
30 + pathlib.Path(p).parent
31 | foo_p.samefile(p)
32 | foo_p.splitext(p)
note: This is an unsafe fix and may change runtime behavior
PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
--> import_as.py:30:1

View File

@@ -480,6 +480,7 @@ help: Replace with `Path(...).name`
32 | dirname(p)
33 | samefile(p)
34 | splitext(p)
note: This is an unsafe fix and may change runtime behavior
PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
--> import_from.py:31:1
@@ -508,6 +509,7 @@ help: Replace with `Path(...).parent`
33 | samefile(p)
34 | splitext(p)
35 | with open(p) as fp:
note: This is an unsafe fix and may change runtime behavior
PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
--> import_from.py:32:1

View File

@@ -480,6 +480,7 @@ help: Replace with `Path(...).name`
37 | xdirname(p)
38 | xsamefile(p)
39 | xsplitext(p)
note: This is an unsafe fix and may change runtime behavior
PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
--> import_from_as.py:36:1
@@ -507,6 +508,7 @@ help: Replace with `Path(...).parent`
37 + pathlib.Path(p).parent
38 | xsamefile(p)
39 | xsplitext(p)
note: This is an unsafe fix and may change runtime behavior
PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
--> import_from_as.py:37:1

View File

@@ -1,6 +1,5 @@
use std::collections::BTreeMap;
use std::fmt;
use std::fs;
use std::iter;
use std::path::{Path, PathBuf};
@@ -101,7 +100,6 @@ pub(crate) fn categorize<'a>(
no_sections: bool,
section_order: &'a [ImportSection],
default_section: &'a ImportSection,
match_source_strategy: MatchSourceStrategy,
) -> &'a ImportSection {
let module_base = module_name.split('.').next().unwrap();
let (mut import_type, mut reason) = {
@@ -129,7 +127,7 @@ pub(crate) fn categorize<'a>(
&ImportSection::Known(ImportType::FirstParty),
Reason::SamePackage,
)
} else if let Some(src) = match_sources(src, module_name, match_source_strategy) {
} else if let Some(src) = match_sources(src, module_name) {
(
&ImportSection::Known(ImportType::FirstParty),
Reason::SourceMatch(src),
@@ -161,61 +159,29 @@ fn same_package(package: Option<PackageRoot<'_>>, module_base: &str) -> bool {
/// Returns the source path with respect to which the module `name`
/// should be considered first party, or `None` if no path is found.
///
/// The [`MatchSourceStrategy`] is the criterion used to decide whether
/// the module path matches a given source directory.
///
/// # Examples
///
/// - The module named `foo` will match `[SRC]` if `[SRC]/foo` is a directory,
/// no matter the strategy.
/// - The module named `foo` will match `[SRC]` if `[SRC]/foo` is a directory
///
/// - With `match_source_strategy == MatchSourceStrategy::Root`, the module
/// named `foo.baz` will match `[SRC]` if `[SRC]/foo` is a
/// directory or `[SRC]/foo.py` exists.
///
/// - With `match_source_stratgy == MatchSourceStrategy::FullPath`, the module
/// named `foo.baz` will match `[SRC]` only if `[SRC]/foo/baz` is a directory,
/// or `[SRC]/foo/baz.py` exists or `[SRC]/foo/baz.pyi` exists.
fn match_sources<'a>(
paths: &'a [PathBuf],
name: &str,
match_source_strategy: MatchSourceStrategy,
) -> Option<&'a Path> {
match match_source_strategy {
MatchSourceStrategy::Root => {
let base = name.split('.').next()?;
for path in paths {
if let Ok(metadata) = fs::metadata(path.join(base)) {
if metadata.is_dir() {
return Some(path);
}
}
if let Ok(metadata) = fs::metadata(path.join(format!("{base}.py"))) {
if metadata.is_file() {
return Some(path);
}
}
}
None
/// - The module named `foo.baz` will match `[SRC]` only if `[SRC]/foo/baz`
/// is a directory, or `[SRC]/foo/baz.py` exists,
/// or `[SRC]/foo/baz.pyi` exists.
fn match_sources<'a>(paths: &'a [PathBuf], name: &str) -> Option<&'a Path> {
let relative_path: PathBuf = name.split('.').collect();
relative_path.components().next()?;
for root in paths {
let candidate = root.join(&relative_path);
if candidate.is_dir() {
return Some(root);
}
MatchSourceStrategy::FullPath => {
let relative_path: PathBuf = name.split('.').collect();
relative_path.components().next()?;
for root in paths {
let candidate = root.join(&relative_path);
if candidate.is_dir() {
return Some(root);
}
if ["py", "pyi"]
.into_iter()
.any(|extension| candidate.with_extension(extension).is_file())
{
return Some(root);
}
}
None
if ["py", "pyi"]
.into_iter()
.any(|extension| candidate.with_extension(extension).is_file())
{
return Some(root);
}
}
None
}
#[expect(clippy::too_many_arguments)]
@@ -229,7 +195,6 @@ pub(crate) fn categorize_imports<'a>(
no_sections: bool,
section_order: &'a [ImportSection],
default_section: &'a ImportSection,
match_source_strategy: MatchSourceStrategy,
) -> BTreeMap<&'a ImportSection, ImportBlock<'a>> {
let mut block_by_type: BTreeMap<&ImportSection, ImportBlock> = BTreeMap::default();
// Categorize `Stmt::Import`.
@@ -245,7 +210,6 @@ pub(crate) fn categorize_imports<'a>(
no_sections,
section_order,
default_section,
match_source_strategy,
);
block_by_type
.entry(import_type)
@@ -266,7 +230,6 @@ pub(crate) fn categorize_imports<'a>(
no_sections,
section_order,
default_section,
match_source_strategy,
);
block_by_type
.entry(classification)
@@ -287,7 +250,6 @@ pub(crate) fn categorize_imports<'a>(
no_sections,
section_order,
default_section,
match_source_strategy,
);
block_by_type
.entry(classification)
@@ -308,7 +270,6 @@ pub(crate) fn categorize_imports<'a>(
no_sections,
section_order,
default_section,
match_source_strategy,
);
block_by_type
.entry(classification)
@@ -463,25 +424,9 @@ impl fmt::Display for KnownModules {
}
}
/// Rule to determine whether a module path matches
/// a relative path from a source directory.
#[derive(Debug, Clone, Copy)]
pub(crate) enum MatchSourceStrategy {
/// Matches if first term in module path is found in file system
///
/// # Example
/// Module is `foo.bar.baz` and `[SRC]/foo` exists
Root,
/// Matches only if full module path is reflected in file system
///
/// # Example
/// Module is `foo.bar.baz` and `[SRC]/foo/bar/baz` exists
FullPath,
}
#[cfg(test)]
mod tests {
use crate::rules::isort::categorize::{MatchSourceStrategy, match_sources};
use crate::rules::isort::categorize::match_sources;
use std::fs;
use std::path::{Path, PathBuf};
@@ -522,49 +467,17 @@ mod tests {
let paths = vec![project_dir.clone()];
// Test with Root strategy
assert_eq!(
match_sources(&paths, "mypackage", MatchSourceStrategy::Root),
match_sources(&paths, "mypackage"),
Some(project_dir.as_path())
);
assert_eq!(
match_sources(&paths, "mypackage.module1", MatchSourceStrategy::Root),
match_sources(&paths, "mypackage.module1"),
Some(project_dir.as_path())
);
assert_eq!(
match_sources(&paths, "mypackage.nonexistent", MatchSourceStrategy::Root),
Some(project_dir.as_path())
);
assert_eq!(
match_sources(&paths, "nonexistent", MatchSourceStrategy::Root),
None
);
// Test with FullPath strategy
assert_eq!(
match_sources(&paths, "mypackage", MatchSourceStrategy::FullPath),
Some(project_dir.as_path())
);
assert_eq!(
match_sources(&paths, "mypackage.module1", MatchSourceStrategy::FullPath),
Some(project_dir.as_path())
);
// Differs in behavior from [`MatchSourceStrategy::Root`]
assert_eq!(
match_sources(
&paths,
"mypackage.nonexistent",
MatchSourceStrategy::FullPath
),
None
);
assert_eq!(match_sources(&paths, "mypackage.nonexistent",), None);
}
/// Tests a src-based Python package layout:
@@ -588,39 +501,12 @@ mod tests {
let paths = vec![src_dir.clone()];
// Test with Root strategy
assert_eq!(
match_sources(&paths, "mypackage", MatchSourceStrategy::Root),
match_sources(&paths, "mypackage.module1"),
Some(src_dir.as_path())
);
assert_eq!(
match_sources(&paths, "mypackage.module1", MatchSourceStrategy::Root),
Some(src_dir.as_path())
);
assert_eq!(
match_sources(&paths, "mypackage.nonexistent", MatchSourceStrategy::Root),
Some(src_dir.as_path())
);
// Test with FullPath strategy
assert_eq!(
match_sources(&paths, "mypackage.module1", MatchSourceStrategy::FullPath),
Some(src_dir.as_path())
);
// Differs in behavior from [`MatchSourceStrategy::Root`]
assert_eq!(
match_sources(
&paths,
"mypackage.nonexistent",
MatchSourceStrategy::FullPath
),
None
);
assert_eq!(match_sources(&paths, "mypackage.nonexistent"), None);
}
/// Tests a nested package layout:
@@ -647,35 +533,13 @@ mod tests {
let paths = vec![project_dir.clone()];
// Test with Root strategy
assert_eq!(
match_sources(&paths, "mypackage", MatchSourceStrategy::Root),
match_sources(&paths, "mypackage.subpackage.module2"),
Some(project_dir.as_path())
);
assert_eq!(
match_sources(&paths, "mypackage.subpackage", MatchSourceStrategy::Root),
Some(project_dir.as_path())
);
// Test with FullPath strategy
assert_eq!(
match_sources(
&paths,
"mypackage.subpackage.module2",
MatchSourceStrategy::FullPath
),
Some(project_dir.as_path())
);
// Differs in behavior from [`MatchSourceStrategy::Root`]
assert_eq!(
match_sources(
&paths,
"mypackage.subpackage.nonexistent",
MatchSourceStrategy::FullPath
),
match_sources(&paths, "mypackage.subpackage.nonexistent"),
None
);
}
@@ -699,52 +563,17 @@ mod tests {
create_file(project_dir.join("namespace/package1/module1.py"));
let paths = vec![project_dir.clone()];
// Test with Root strategy
assert_eq!(
match_sources(&paths, "namespace", MatchSourceStrategy::Root),
match_sources(&paths, "namespace.package1"),
Some(project_dir.as_path())
);
assert_eq!(
match_sources(&paths, "namespace.package1", MatchSourceStrategy::Root),
match_sources(&paths, "namespace.package1.module1"),
Some(project_dir.as_path())
);
assert_eq!(
match_sources(
&paths,
"namespace.package2.module1",
MatchSourceStrategy::Root
),
Some(project_dir.as_path())
);
// Test with FullPath strategy
assert_eq!(
match_sources(&paths, "namespace.package1", MatchSourceStrategy::FullPath),
Some(project_dir.as_path())
);
assert_eq!(
match_sources(
&paths,
"namespace.package1.module1",
MatchSourceStrategy::FullPath
),
Some(project_dir.as_path())
);
// Differs in behavior from [`MatchSourceStrategy::Root`]
assert_eq!(
match_sources(
&paths,
"namespace.package2.module1",
MatchSourceStrategy::FullPath
),
None
);
assert_eq!(match_sources(&paths, "namespace.package2.module1"), None);
}
/// Tests a package with type stubs (.pyi files):
@@ -764,12 +593,11 @@ mod tests {
create_file(project_dir.join("mypackage/__init__.py"));
create_file(project_dir.join("mypackage/module1.pyi")); // Only create .pyi file, not .py
// Test with FullPath strategy
let paths = vec![project_dir.clone()];
// Module "mypackage.module1" should match project_dir using .pyi file
assert_eq!(
match_sources(&paths, "mypackage.module1", MatchSourceStrategy::FullPath),
match_sources(&paths, "mypackage.module1"),
Some(project_dir.as_path())
);
}
@@ -796,30 +624,17 @@ mod tests {
create_file(project_dir.join("mypackage/feature/__init__.py"));
create_file(project_dir.join("mypackage/feature/submodule.py"));
// Test with Root strategy
let paths = vec![project_dir.clone()];
// Module "mypackage.feature" should match project_dir (matches the file first)
assert_eq!(
match_sources(&paths, "mypackage.feature", MatchSourceStrategy::Root),
Some(project_dir.as_path())
);
// Test with FullPath strategy
// Module "mypackage.feature" should match project_dir
assert_eq!(
match_sources(&paths, "mypackage.feature", MatchSourceStrategy::FullPath),
match_sources(&paths, "mypackage.feature"),
Some(project_dir.as_path())
);
// Module "mypackage.feature.submodule" should match project_dir
assert_eq!(
match_sources(
&paths,
"mypackage.feature.submodule",
MatchSourceStrategy::FullPath
),
match_sources(&paths, "mypackage.feature.submodule"),
Some(project_dir.as_path())
);
}
@@ -857,13 +672,13 @@ mod tests {
// Module "package1" should match project1_dir
assert_eq!(
match_sources(&paths, "package1", MatchSourceStrategy::Root),
match_sources(&paths, "package1"),
Some(project1_dir.as_path())
);
// Module "package2" should match project2_dir
assert_eq!(
match_sources(&paths, "package2", MatchSourceStrategy::Root),
match_sources(&paths, "package2"),
Some(project2_dir.as_path())
);
@@ -872,7 +687,7 @@ mod tests {
// Module "package1" should still match project1_dir
assert_eq!(
match_sources(&paths_reversed, "package1", MatchSourceStrategy::Root),
match_sources(&paths_reversed, "package1"),
Some(project1_dir.as_path())
);
}
@@ -885,8 +700,7 @@ mod tests {
///
/// In theory this should never happen since we expect
/// module names to have been normalized by the time we
/// call `match_sources`. But it is worth noting that the
/// behavior is different depending on the [`MatchSourceStrategy`]
/// call `match_sources`.
#[test]
fn test_empty_module_name() {
let temp_dir = tempdir().unwrap();
@@ -894,16 +708,9 @@ mod tests {
create_dir(project_dir.join("mypackage"));
let paths = vec![project_dir.clone()];
let paths = vec![project_dir];
assert_eq!(
match_sources(&paths, "", MatchSourceStrategy::Root),
Some(project_dir.as_path())
);
assert_eq!(
match_sources(&paths, "", MatchSourceStrategy::FullPath),
None
);
assert_eq!(match_sources(&paths, ""), None);
}
/// Tests behavior with an empty list of source paths
@@ -911,14 +718,6 @@ mod tests {
fn test_empty_paths() {
let paths: Vec<PathBuf> = vec![];
// Empty paths should return None
assert_eq!(
match_sources(&paths, "mypackage", MatchSourceStrategy::Root),
None
);
assert_eq!(
match_sources(&paths, "mypackage", MatchSourceStrategy::FullPath),
None
);
assert_eq!(match_sources(&paths, "mypackage"), None);
}
}

View File

@@ -5,8 +5,8 @@ use std::path::PathBuf;
use annotate::annotate_imports;
use block::{Block, Trailer};
pub(crate) use categorize::categorize;
use categorize::categorize_imports;
pub use categorize::{ImportSection, ImportType};
use categorize::{MatchSourceStrategy, categorize_imports};
use comments::Comment;
use normalize::normalize_imports;
use order::order_imports;
@@ -76,7 +76,6 @@ pub(crate) fn format_imports(
source_type: PySourceType,
target_version: PythonVersion,
settings: &Settings,
match_source_strategy: MatchSourceStrategy,
tokens: &Tokens,
) -> String {
let trailer = &block.trailer;
@@ -104,7 +103,6 @@ pub(crate) fn format_imports(
package,
target_version,
settings,
match_source_strategy,
);
if !block_output.is_empty() && !output.is_empty() {
@@ -161,7 +159,6 @@ fn format_import_block(
package: Option<PackageRoot<'_>>,
target_version: PythonVersion,
settings: &Settings,
match_source_strategy: MatchSourceStrategy,
) -> String {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum LineInsertion {
@@ -182,7 +179,6 @@ fn format_import_block(
settings.no_sections,
&settings.section_order,
&settings.default_section,
match_source_strategy,
);
let mut output = String::new();

View File

@@ -14,9 +14,7 @@ use crate::Locator;
use crate::checkers::ast::LintContext;
use crate::line_width::LineWidthBuilder;
use crate::package::PackageRoot;
use crate::preview::is_full_path_match_source_strategy_enabled;
use crate::rules::isort::block::Block;
use crate::rules::isort::categorize::MatchSourceStrategy;
use crate::rules::isort::{comments, format_imports};
use crate::settings::LinterSettings;
use crate::{Edit, Fix, FixAvailability, Violation};
@@ -40,12 +38,6 @@ use crate::{Edit, Fix, FixAvailability, Violation};
/// import pandas
/// ```
///
/// ## Preview
/// When [`preview`](https://docs.astral.sh/ruff/preview/) mode is enabled, Ruff applies a stricter criterion
/// for determining whether an import should be classified as first-party.
/// Specifically, for an import of the form `import foo.bar.baz`, Ruff will
/// check that `foo/bar`, relative to a [user-specified `src`](https://docs.astral.sh/ruff/settings/#src) directory, contains either
/// the directory `baz` or else a file with the name `baz.py` or `baz.pyi`.
#[derive(ViolationMetadata)]
pub(crate) struct UnsortedImports;
@@ -129,12 +121,6 @@ pub(crate) fn organize_imports(
trailing_lines_end(block.imports.last().unwrap(), locator.contents())
};
let match_source_strategy = if is_full_path_match_source_strategy_enabled(settings) {
MatchSourceStrategy::FullPath
} else {
MatchSourceStrategy::Root
};
// Generate the sorted import block.
let expected = format_imports(
block,
@@ -148,7 +134,6 @@ pub(crate) fn organize_imports(
source_type,
target_version,
&settings.isort,
match_source_strategy,
tokens,
);

View File

@@ -4,9 +4,9 @@ use ruff_text_size::Ranged;
use crate::{Violation, checkers::ast::Checker};
/// ## Deprecated
/// ## Removed
///
/// This rule has been deprecated as it's highly opinionated and overly strict in most cases.
/// This rule has been removed as it's highly opinionated and overly strict in most cases.
///
/// ## What it does
/// Checks for assignments to the variable `df`.

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