Compare commits

...

82 Commits

Author SHA1 Message Date
konstin
2141e34158 post-commit 2026-01-09 13:46:25 +01:00
konstin
393ca28693 Use explicit manylinux/musllinux targets and better pre-upload checks
This ensures that changes to the targets are intentional and explicit.

See also https://github.com/astral-sh/ty/pull/2393
and https://github.com/astral-sh/uv/pull/17358
2026-01-09 13:36:23 +01:00
Micha Reiser
e61657ff3c [ty] Enable unused-type-ignore-comment by default (#22474) 2026-01-09 10:58:43 +00:00
Micha Reiser
ba5dd5837c [ty] Pass slice to specialize (#22421) 2026-01-09 09:45:39 +01:00
Micha Reiser
c5f6a74da5 [ty] Fix goto definition for relative imports in third-party files (#22457) 2026-01-09 09:30:31 +01:00
Micha Reiser
b3cde98cd1 [ty] Update salsa (#22473) 2026-01-09 09:21:45 +01:00
Carl Meyer
f9f7a6901b Complete minor TODO in ty_python_semantic crate (#22468)
This TODO is very old -- we have long since recorded this definition.
Updating the test to actually assert the declaration requires a new
helper method for declarations, to complement the existing
`first_public_binding` helper.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-08 16:04:34 -08:00
Dylan
c920cf8cdb Bump 0.14.11 (#22462) 2026-01-08 12:51:47 -06:00
Micha Reiser
bb757b5a79 [ty] Don't show diagnostics for excluded files (#22455) 2026-01-08 18:27:28 +01:00
Charlie Marsh
1f49e8ef51 Include configured src directories when resolving graphs (#22451)
## Summary

This PR augments the detected source paths with the user-configured
`src` when computing roots for `ruff analyze graph`.
2026-01-08 15:19:15 +00:00
Douglas Creager
701f5134ab [ty] Only consider fully static pivots when deriving transitive constraints (#22444)
When working with constraint sets, we track transitive relationships
between the constraints in the set. For instance, in `S ≤ int ∧ int ≤
T`, we can infer that `S ≤ T`. However, we should only consider fully
static types when looking for a "pivot" for this kind of transitive
relationship. The same pattern does not hold for `S ≤ Any ∧ Any ≤ T`;
because the two `Any`s can materialize to different types, we cannot
infer that `S ≤ T`.

Fixes https://github.com/astral-sh/ty/issues/2371
2026-01-08 09:31:55 -05:00
Micha Reiser
eea9ad8352 Pin maturin version (#22454) 2026-01-08 12:39:51 +01:00
Alex Waygood
eeac2bd3ee [ty] Optimize union building for unions with many enum-literal members (#22363) 2026-01-08 10:50:04 +00:00
Jason K Hall
7319c37f4e docs: fix jupyter notebook discovery info for editors (#22447)
Resolves #21892

## Summary

This PR updates `docs/editors/features.md` to clarify that Jupyter
Notebooks are now included by default as of version 0.6.0.
2026-01-08 11:52:01 +05:30
Amethyst Reese
805503c19a [ruff] Improve fix title for RUF102 invalid rule code (#22100)
## Summary

Updates the fix title for RUF102 to either specify which rule code to
remove, or clarify
that the entire suppression comment should be removed.

## Test Plan

Updated test snapshots.
2026-01-07 17:23:18 -08:00
Charlie Marsh
68a2f6c57d [ty] Fix super() with TypeVar-annotated self and cls parameter (#22208)
## Summary

This PR fixes `super()` handling when the first parameter (`self` or
`cls`) is annotated with a TypeVar, like `Self`.

Previously, `super()` would incorrectly resolve TypeVars to their bounds
before creating the `BoundSuperType`. So if you had `self: Self` where
`Self` is bounded by `Parent`, we'd process `Parent` as a
`NominalInstance` and end up with `SuperOwnerKind::Instance(Parent)`.

As a result:

```python
class Parent:
    @classmethod
    def create(cls) -> Self:
        return cls()

class Child(Parent):
    @classmethod
    def create(cls) -> Self:
        return super().create()  # Error: Argument type `Self@create` does not satisfy upper bound `Parent`
```

We now track two additional variants on `SuperOwnerKind` for TypeVar
owners:

- `InstanceTypeVar`: for instance methods where self is a TypeVar (e.g.,
`self: Self`).
- `ClassTypeVar`: for classmethods where `cls` is a `TypeVar` wrapped in
`type[...]` (e.g., `cls: type[Self]`).

Closes https://github.com/astral-sh/ty/issues/2122.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2026-01-07 19:56:09 -05:00
Alex Waygood
abaa735e1d [ty] Improve UnionBuilder performance by changing Type::is_subtype_of calls to Type::is_redundant_with (#22337) 2026-01-07 22:17:44 +00:00
Jelle Zijlstra
c02d164357 Check required-version before parsing rules (#22410)
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-01-07 17:29:27 +00:00
Andrew Gallant
88aa3f82f0 [ty] Fix generally poor ranking in playground completions
We enabled [`CompletionListisIncomplete`] in our LSP server a while back
in order to have more of a say in how we rank and filter completions.
When it isn't set, the client tends to ask for completions less
frequently and will instead do its own filtering.

But... we did not enable it for the playground. Which I guess didn't
result in anything noticeably bad until we started limiting completions
to 1,000 suggestions. This meant that if the _initial_ completion
response didn't include the ultimate desired answer, then it would never
show up in the results until the client requested completions again.
This in turn led to some very poor completions in some cases.

This all gets fixed by simply enabling `isIncomplete` for Monaco.

Fixes astral-sh/ty#2340

[`CompletionList::isIncomplete`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionList)
2026-01-07 12:25:26 -05:00
Carl Meyer
30902497db [ty] Make signature return and parameter types non-optional (#22425)
## Summary

Fixes https://github.com/astral-sh/ty/issues/2363
Fixes https://github.com/astral-sh/ty/issues/2013

And several other bugs with the same root cause. And makes any similar
bugs impossible by construction.

Previously we distinguished "no annotation" (Rust `None`) from
"explicitly annotated with something of type `Unknown`" (which is not an
error, and results in the annotation being of Rust type
`Some(Type::DynamicType(Unknown))`), even though semantically these
should be treated the same.

This was a bit of a bug magnet, because it was easy to forget to make
this `None` -> `Unknown` translation everywhere we needed to. And in
fact we did fail to do it in the case of materializing a callable,
leading to a top-materialized callable still having (rust) `None` return
type, which should have instead materialized to `object`.

This also fixes several other bugs related to not handling un-annotated
return types correctly:
1. We previously considered the return type of an unannotated `async
def` to be `Unknown`, where it should be `CoroutineType[Any, Any,
Unknown]`.
2. We previously failed to infer a ParamSpec if the return type of the
callable we are inferring against was not annotated.
3. We previously wrongly returned `Unknown` from `some_dict.get("key",
None)` if the value type of `some_dict` included a callable type with
un-annotated return type.

We now make signature return types and annotated parameter types
required, and we eagerly insert `Unknown` if there's no annotation. Most
of the diff is just a bunch of mechanical code changes where we
construct these types, and simplifications where we use them.

One exception is type display: when a callable type has un-annotated
parameters, we want to display them as un-annotated, but if it has a
parameter explicitly annotated with something of `Unknown` type, we want
to display that parameter as `x: Unknown` (it would be confusing if it
looked like your annotation just disappeared entirely).

Fortunately, we already have a mechanism in place for handling this: the
`inferred_annotation` flag, which suppresses display of an annotation.
Previously we used it only for `self` and `cls` parameters with an
inferred annotated type -- but we now also set it for any un-annotated
parameter, for which we infer `Unknown` type.

We also need to normalize `inferred_annotation`, since it's display-only
and shouldn't impact type equivalence. (This is technically a
previously-existing bug, it just never came up when it only affected
self types -- now it comes up because we have tests asserting that `def
f(x)` and `def g(x: Unknown)` are equivalent.)

## Test Plan

Added mdtests.
2026-01-07 09:18:39 -08:00
Alex Waygood
3ad99fb1f4 [ty] Fix an mdtest title (#22439) 2026-01-07 16:34:56 +00:00
Micha Reiser
d0ff59cfe5 [ty] Use Pool from regex_automata to reuse the matches allocations (#22438) 2026-01-07 17:22:35 +01:00
Andrew Gallant
952193e0c6 [ty] Offer completions for T when a value has type Unknown | T
Fixes astral-sh/ty#2197
2026-01-07 10:15:36 -05:00
Alex Waygood
4cba2e8f91 [ty] Generalize len() narrowing somewhat (#22330) 2026-01-07 13:57:50 +00:00
Alex Waygood
1a7f53022a [ty] Link to Callable __name__ FAQ directly from unresolved-attribute diagnostic (#22437) 2026-01-07 13:22:53 +00:00
Micha Reiser
266a7bc4c5 [ty] Fix stack overflow due to too small stack size (#22433) 2026-01-07 13:55:23 +01:00
Micha Reiser
3b7a5e4de8 [ty] Allow including files with no extension (#22243) 2026-01-07 11:38:02 +01:00
Micha Reiser
93039d055d [ty] Add --add-ignore CLI option (#21696) 2026-01-07 11:17:05 +01:00
Jason K Hall
3b61da0da3 Allow Python 3.15 as valid target-version value in preview (#22419) 2026-01-07 09:38:36 +01:00
Alex Waygood
5933cc0101 [ty] Optimize and simplify some object-related code (#22366)
## Summary

I wondered if this might improve performance a little. It doesn't seem
to, but it's a net reduction in LOC and I think the changes make sense.
I think it's worth it anyway just in terms of simplifying the code.

## Test Plan

Our existing tests all pass and the primer report is clean (aside from
our usual flakes).
2026-01-07 08:35:26 +00:00
Dhruv Manilawala
2190fcebe0 [ty] Substitute ParamSpec in overloaded functions (#22416)
## Summary

fixes: https://github.com/astral-sh/ty/issues/2027

This PR fixes a bug where the type mapping for a `ParamSpec` was not
being applied in an overloaded function.

This PR also fixes https://github.com/astral-sh/ty/issues/2081 and
reveals new diagnostics which doesn't look related to the bug:

```py
from prefect import flow, task

@task
def task_get() -> int:
    """Task get integer."""
    return 42

@task
def task_add(x: int, y: int) -> int:
    """Task add two integers."""
    print(f"Adding {x} and {y}")
    return x + y

@flow
def my_flow():
    """My flow."""
    x = 23
    future_y = task_get.submit()

	# error: [no-matching-overload]
    task_add(future_y, future_y)
	# error: [no-matching-overload]
    task_add(x, future_y)
```

The reason is that the type of `future_y` is `PrefectFuture[int]` while
the type of `task_add` is `Task[(x: int, y: int), int]` which means that
the assignment between `int` and `PrefectFuture[int]` fails which
results in no overload matching. Pyright also raises the invalid
argument type error on all three usages of `future_y` in those two
calls.

## Test Plan

Add regression mdtest from the linked issue.
2026-01-07 13:30:34 +05:30
Douglas Creager
df9d6886d4 [ty] Remove redundant apply_specialization type mappings (#22422)
@dhruvmanila encountered this in #22416 — there are two different
`TypeMapping` variants for apply a specialization to a type. One
operates on a full `Specialization` instance, the other on a partially
constructed one. If we move this enum-ness "down a level" it reduces
some copy/paste in places where we are operating on a `TypeMapping`.
2026-01-07 13:10:26 +05:30
Aria Desires
5133fa4516 [ty] fix typo in CODEOWNERS (#22430) 2026-01-07 07:44:46 +01:00
Amethyst Reese
21c5cfe236 Consolidate diagnostics for matched disable/enable suppression comments (#22099)
## Summary

Combines diagnostics for matched suppression comments, so that ranges
and autofixes for both
the `#ruff:disable` and `#ruff:enable` comments will be reported as a
single diagnostic.

## Test Plan

Snapshot changes, added new snapshot for full output from preview mode
rather than just a diff.

Issue #3711
2026-01-06 18:42:51 -08:00
Carl Meyer
f97da18267 [ty] improve typevar solving from constraint sets (#22411)
## Summary

Fixes https://github.com/astral-sh/ty/issues/2292

When solving a bounded typevar, we preferred the upper bound over the
actual type seen in the call. This change fixes that.

## Test Plan

Added mdtest, existing tests pass.
2026-01-06 13:10:51 -08:00
Alex Waygood
bc191f59b9 Convert more ty snapshots to the new format (#22424) 2026-01-06 20:01:41 +00:00
Alex Waygood
00f86c39e0 Add Alex Waygood back as a ty_ide codeowner (#22423) 2026-01-06 19:24:13 +00:00
Alex Waygood
2ec29b7418 [ty] Optimize Type::negate() (#22402) 2026-01-06 19:17:59 +00:00
Jack O'Connor
ab1ac254d9 [ty] fix comparisons and arithmetic with NewTypes of float (#22105)
Fixes https://github.com/astral-sh/ty/issues/2077.
2026-01-06 09:32:22 -08:00
Charlie Marsh
01de8bef3e [ty] Add named fields for Place enum (#22172)
## Summary

Mechanical refactor to migrate this enum to named fields. No functional
changes.

See:
https://github.com/astral-sh/ruff/pull/22093#discussion_r2636050127.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 17:24:51 +00:00
Charlie Marsh
b59f6eb5e9 [ty] Support comparisons between variable-length tuples (#21824)
## Summary

Closes https://github.com/astral-sh/ty/issues/1741.
2026-01-06 12:09:40 -05:00
Aria Desires
9ca78bdf76 [ty] Add Gankra as a CODEOWNER for lsp and imports work (#22420)
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-01-06 16:50:16 +00:00
Charlie Marsh
d65542c05e [ty] Make tuple intersection a fallible operation (#22094)
## Summary

This PR attempts to address a TODO in
https://github.com/astral-sh/ruff/pull/21965#discussion_r2635378498.
2026-01-06 10:47:04 -05:00
Aria Desires
98728b2c98 [ty] improve indented codefence rendering in docstrings (#22408)
By stripping leading indents from codefence lines to ensure they're
properly understood by markdown (but otherwise preserving the indent in
the codeblock so all the code renders roughly at the right indent).

As described in [this
comment](https://github.com/astral-sh/ty/issues/2352#issuecomment-3711686053)
this solution is very "do what I mean" for when a user has an explicit
markdown codeblock in e.g. a `Returns:` section which "has" to be
indented but that indent makes the verbatim codefence invalid markdown.

* Fixes https://github.com/astral-sh/ty/issues/2352
2026-01-06 10:44:31 -05:00
Dylan
924b2972f2 Update Black tests (#22405)
I am updating these because we didn't have test coverage for the
different handling of `fmt: skip` comments applied to multiple
statements on the same line. This is in preparation for #22119 (to show
before/after deviations).

Follows the same procedure as in #20794

Edit: As it happens, the new fixtures do not even cover the case
relevant to #22119 - they just deal with the already handled case of a
one-line compound statement. Nevertheless, it seems worthwhile to make
this update, especially since it uncovered a (possible?) bug.
2026-01-06 09:09:05 -06:00
Andrew Gallant
d035744959 [ty] Include = in completion suggestions in playground
This was an accidental omission in #21988 and identified in
astral-sh/ty#2203.
2026-01-06 09:26:29 -05:00
RasmusNygren
ce059c4857 [ty] Sort keyword argument completions higher (#22297) 2026-01-06 10:57:10 +00:00
Micha Reiser
acbc83d6d2 [ty] Fix stale semantic tokens after opening the same document with new content (#22414) 2026-01-06 11:52:51 +01:00
RasmusNygren
a9e5246786 [ty] Ensure the ty playground module is only ever loaded once (#22409) 2026-01-06 10:52:02 +01:00
Charlie Marsh
8b8b174e4f [ty] Add a diagnostic for @functools.total_ordering without a defined comparison method (#22183)
## Summary

This raises a `ValueError` at runtime:

```python
from functools import total_ordering

@total_ordering
class NoOrdering:
    def __eq__(self, other: object) -> bool:
        return True
```

Specifically:

```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/functools.py", line 193, in total_ordering
    raise ValueError('must define at least one ordering operation: < > <= >=')
ValueError: must define at least one ordering operation: < > <= >=
```

See: https://github.com/astral-sh/ty/issues/1202.
2026-01-06 04:14:06 +00:00
Charlie Marsh
28fa02129b [ty] Add support for @total_ordering (#22181)
## Summary

We have some suppressions in the pyx codebase related to this, so wanted
to resolve.

Closes https://github.com/astral-sh/ty/issues/1202.
2026-01-05 22:47:03 -05:00
Brent Westbrook
a10e42294b [pylint] Demote PLW1510 fix to display-only (#22318)
Summary
--

Closes #17091. `PLW1510` checks for `subprocess.run` calls without a
`check`
keyword argument and previously had a safe fix to add `check=False`.
That's the
default value, so technically it preserved the code's behavior, but as
discussed
in #17091 and #17087, Ruff can't actually know what the author intended.

I don't think it hurts to keep this as a display-only fix instead of
removing it
entirely, but it definitely shouldn't be safe at the very least.

Test Plan
--

Existing tests
2026-01-05 19:36:16 -05:00
Amethyst Reese
12a4ca003f [flake8_print] better suggestion for basicConfig in T201 docs (#22101)
`logging.basicConfig` should not be called at a global module scope,
as that produces a race condition to configure logging based on which
module gets imported first.  Logging should instead be initialized
in an entrypoint to the program, either in a `main()` or in the
typical `if __name__ == "__main__"` block.
2026-01-05 11:42:47 -08:00
Charlie Marsh
60f7ec90ef Add a fast-test profile (#22382)
## Summary

We use this profile in uv to create success, as an optimization for the
iterative test loop. We include `opt-level=1` because it ends up being
"worth it" for testing (empirically), even though it means the build is
actually a big slower than `dev` (if you remove `opt-level=1`, clean
compile is about 22% faster than `dev`).

Here are some benchmarks I generated with Claude -- the main motivator
here is the incremental testing for `ty_python_semantic` which is 2.4x
faster:

### `ty_python_semantic`

Full test suite (471 tests):
| Scenario    | dev   | fast-test | Improvement |
|-------------|-------|------------|-------------|
| Clean       | 53s   | 49s        | 8% faster   |
| Incremental | 17.8s | 6.8s       | 2.4x faster |

Single test:
| Scenario    | dev   | fast-test | Improvement |
|-------------|-------|------------|-------------|
| Clean       | 42.5s | 55.3s      | 30% slower  |
| Incremental | 6.5s  | 6.1s       | ~same       |

### `ruff_linter`

Full test suite (2622 tests):
| Scenario    | dev   | fast-test | Improvement |
|-------------|-------|------------|-------------|
| Clean       | 31s   | 41s        | 32% slower  |
| Incremental | 11.9s | 10.5s      | 12% faster  |

Single test:
| Scenario    | dev  | fast-test | Improvement |
|-------------|------|------------|-------------|
| Clean       | 26s  | 36.5s      | 40% slower  |
| Incremental | 4.5s | 5.5s       | 22% slower  |
2026-01-05 19:35:43 +00:00
Jack O'Connor
922d964bcb [ty] emit diagnostics for method definitions and other invalid statements in TypedDict class bodies (#22351)
Fixes https://github.com/astral-sh/ty/issues/2277.
2026-01-05 11:28:04 -08:00
Jack O'Connor
4712503c6d [ty] cargo insta test --force-update-snapshots (#22313)
Snapshot tests recently started reporting this warning:

> Snapshot test passes but the existing value is in a legacy format.
> Please run cargo insta test --force-update-snapshots to update to a
> newer format.

This PR is the result of that forced update.

One file (crates/ruff_db/src/diagnostic/render/full.rs) seems to get
corrupted, because it contains strings with unprintable characters that
trigger some bug in cargo-insta. I've manually reverted that file, and
also manually reverted the `input_file:` lines, which we like.
2026-01-05 07:55:47 -08:00
Alex Waygood
6b3de1517a [ty] Improve tracebacks when installing dependencies fails in ty_benchmark (#22399) 2026-01-05 14:55:08 +00:00
Alex Waygood
f3dea6e5c9 [ty] Optimize IntersectionType for the common case of a single negated element (#22344)
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-01-05 13:41:50 +00:00
Micha Reiser
24dd149e03 [ty] Extract relation module from types.rs (#22232) 2026-01-05 13:16:49 +00:00
Alex Waygood
b8d527ff46 [ty] Optimize and simplify UnionElement::try_reduce (#22339) 2026-01-05 12:54:44 +00:00
Aria Desires
e63cf978ae [ty] Implement support for explicit markdown code fences in docstring rendering (#22373)
* Fixes https://github.com/astral-sh/ty/issues/2291
2026-01-05 07:13:24 -05:00
Rob Hand
3dab4ff8ad [ty] (docs) - Note insta is required for working with ty tests in ty CONTRIBUTING.md (#22332) 2026-01-05 11:05:13 +01:00
Jason K Hall
24580e2ee8 flake8-simplify: avoid unnecessary builtins import for SIM105 (#22358) 2026-01-05 10:58:46 +01:00
renovate[bot]
3d3af6f7c8 Update pre-commit dependencies (#22393)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-01-05 10:24:40 +01:00
renovate[bot]
7cc34c081a Update CodSpeedHQ/action action to v4.5.1 (#22389)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 09:10:06 +01:00
renovate[bot]
7a95013f56 Update Rust crate serde_json to v1.0.148 (#22387)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 09:09:23 +01:00
renovate[bot]
2395954d9a Update Rust crate schemars to v1.2.0 (#22391)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-01-05 09:08:58 +01:00
renovate[bot]
670bd01fb5 Update Rust crate arc-swap to v1.8.0 (#22390)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 09:08:32 +01:00
renovate[bot]
eae5c685f8 Update Rust crate proc-macro2 to v1.0.104 (#22386)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 09:05:21 +01:00
renovate[bot]
994f05f3ca Update Rust crate tempfile to v3.24.0 (#22392)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 09:04:59 +01:00
renovate[bot]
8dcecf323b Update taiki-e/install-action action to v2.65.6 (#22388)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 08:03:31 +00:00
renovate[bot]
b12c94e411 Update Rust crate jiff to v0.2.17 (#22384)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 07:57:34 +00:00
renovate[bot]
a9c3ea9674 Update Rust crate matchit to v0.9.1 (#22385)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 07:56:54 +00:00
renovate[bot]
704c57f491 Update Rust crate insta to v1.45.1 (#22383)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 07:56:36 +00:00
renovate[bot]
7a27662eca Update cargo-bins/cargo-binstall action to v1.16.6 (#22380)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 08:30:34 +01:00
renovate[bot]
ce2490ee93 Update dependency @cloudflare/workers-types to v4.20251229.0 (#22381)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 08:30:16 +01:00
Charlie Marsh
92a2f2c992 [ty] Apply class decorators via try_call() (#22375)
## Summary

Decorators are now called with the class as an argument, and the return
type becomes the class's type. This mirrors how function decorators
already work.

Closes https://github.com/astral-sh/ty/issues/2313.
2026-01-04 17:11:00 -05:00
Charlie Marsh
11b551c2be Add a CLAUDE.md (#22370)
## Summary

This is a starting point based on my own experiments. Feedback and
changes welcome -- I think we should iterate on this a lot as we go.
2026-01-04 15:00:31 -05:00
Micha Reiser
b85c0190c5 [ty] Use upstream GetSize implementation for OrderMap and OrderSet (#22374) 2026-01-04 19:54:03 +00:00
Micha Reiser
46a4bfc478 [ty] Use default HashSet for TypeCollector (#22368) 2026-01-04 18:58:30 +00:00
Alex Waygood
0c53395917 [ty] Add a second benchmark for enums with many members (#22364) 2026-01-04 17:58:20 +00:00
Alex Waygood
8464aca795 Bump docstring-adder pin (#22361) 2026-01-03 20:21:45 +00:00
1093 changed files with 14933 additions and 7187 deletions

10
.github/CODEOWNERS vendored
View File

@@ -20,9 +20,11 @@
# ty
/crates/ty* @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
/crates/ruff_db/ @carljm @MichaReiser @sharkdp @dcreager
/crates/ty_project/ @carljm @MichaReiser @sharkdp @dcreager
/crates/ty_server/ @carljm @MichaReiser @sharkdp @dcreager
/crates/ty_project/ @carljm @MichaReiser @sharkdp @dcreager @Gankra
/crates/ty_ide/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager @Gankra
/crates/ty_server/ @carljm @MichaReiser @sharkdp @dcreager @Gankra
/crates/ty/ @carljm @MichaReiser @sharkdp @dcreager
/crates/ty_wasm/ @carljm @MichaReiser @sharkdp @dcreager
/crates/ty_wasm/ @carljm @MichaReiser @sharkdp @dcreager @Gankra
/scripts/ty_benchmark/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
/crates/ty_python_semantic @carljm @AlexWaygood @sharkdp @dcreager
/crates/ty_python_semantic/ @carljm @AlexWaygood @sharkdp @dcreager
/crates/ty_module_resolver/ @carljm @MichaReiser @AlexWaygood @Gankra

View File

@@ -5,5 +5,4 @@
[rules]
possibly-unresolved-reference = "warn"
possibly-missing-import = "warn"
unused-ignore-comment = "warn"
division-by-zero = "warn"

View File

@@ -51,6 +51,7 @@ jobs:
- name: "Build sdist"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.11.5
command: sdist
args: --out dist
- name: "Test sdist"
@@ -81,8 +82,9 @@ jobs:
- name: "Build wheels - x86_64"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.11.5
target: x86_64
args: --release --locked --out dist
args: --release --locked --out dist --compatibility pypi
- name: "Upload wheels"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
@@ -123,8 +125,9 @@ jobs:
- name: "Build wheels - aarch64"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.11.5
target: aarch64
args: --release --locked --out dist
args: --release --locked --out dist --compatibility pypi
- name: "Test wheel - aarch64"
run: |
pip install dist/"${PACKAGE_NAME}"-*.whl --force-reinstall
@@ -179,8 +182,9 @@ jobs:
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.11.5
target: ${{ matrix.platform.target }}
args: --release --locked --out dist
args: --release --locked --out dist --compatibility pypi
env:
# aarch64 build fails, see https://github.com/PyO3/maturin/issues/2110
XWIN_VERSION: 16
@@ -232,9 +236,10 @@ jobs:
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.11.5
target: ${{ matrix.target }}
manylinux: auto
args: --release --locked --out dist
manylinux: 2_17
args: --release --locked --out dist --compatibility pypi
- name: "Test wheel"
if: ${{ startsWith(matrix.target, 'x86_64') }}
run: |
@@ -275,25 +280,34 @@ jobs:
platform:
- target: aarch64-unknown-linux-gnu
arch: aarch64
manylinux: 2_17
# see https://github.com/astral-sh/ruff/issues/3791
# and https://github.com/gnzlbg/jemallocator/issues/170#issuecomment-1503228963
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
- target: armv7-unknown-linux-gnueabihf
arch: armv7
manylinux: 2_17
- target: s390x-unknown-linux-gnu
arch: s390x
manylinux: 2_17
- target: powerpc64le-unknown-linux-gnu
arch: ppc64le
manylinux: 2_17
# see https://github.com/astral-sh/ruff/issues/10073
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
- target: powerpc64-unknown-linux-gnu
arch: ppc64
manylinux: 2_17
# see https://github.com/astral-sh/ruff/issues/10073
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
- target: arm-unknown-linux-musleabihf
# Use the cross container, but tag as `linux_armv6l`
manylinux: auto
arch: arm
- target: riscv64gc-unknown-linux-gnu
arch: riscv64
# Minimum manylinux target for riscv64
manylinux: 2_31
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
@@ -308,10 +322,11 @@ jobs:
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.11.5
target: ${{ matrix.platform.target }}
manylinux: auto
manylinux: ${{ matrix.platform.manylinux }}
docker-options: ${{ matrix.platform.maturin_docker_options }}
args: --release --locked --out dist
args: --release --locked --out dist --compatibility pypi
- uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
if: ${{ matrix.platform.arch != 'ppc64' && matrix.platform.arch != 'ppc64le'}}
name: Test wheel
@@ -374,9 +389,10 @@ jobs:
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.11.5
target: ${{ matrix.target }}
manylinux: musllinux_1_2
args: --release --locked --out dist
args: --release --locked --out dist --compatibility pypi
- name: "Test wheel"
if: matrix.target == 'x86_64-unknown-linux-musl'
uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3
@@ -439,9 +455,10 @@ jobs:
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.11.5
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2
args: --release --locked --out dist
args: --release --locked --out dist --compatibility pypi
docker-options: ${{ matrix.platform.maturin_docker_options }}
- uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
name: Test wheel

View File

@@ -281,11 +281,11 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-insta
- name: "Install uv"
@@ -343,7 +343,7 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-nextest
- name: "Install uv"
@@ -376,7 +376,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo nextest"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-nextest
- name: "Install uv"
@@ -468,7 +468,7 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@4a9028576ed64318f7b24193a62695e96dcbe015 # v1.16.5
uses: cargo-bins/cargo-binstall@80aaafe04903087c333980fa2686259ddd34b2d9 # v1.16.6
- 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
@@ -713,7 +713,7 @@ jobs:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@4a9028576ed64318f7b24193a62695e96dcbe015 # v1.16.5
- uses: cargo-bins/cargo-binstall@80aaafe04903087c333980fa2686259ddd34b2d9 # v1.16.6
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -972,7 +972,7 @@ jobs:
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-codspeed
@@ -980,7 +980,7 @@ jobs:
run: cargo codspeed build --features "codspeed,ruff_instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
- name: "Run benchmarks"
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
uses: CodSpeedHQ/action@972e3437949c89e1357ebd1a2dbc852fcbc57245 # v4.5.1
with:
mode: simulation
run: cargo codspeed run
@@ -1011,7 +1011,7 @@ jobs:
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-codspeed
@@ -1047,7 +1047,7 @@ jobs:
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: "Install codspeed"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-codspeed
@@ -1061,7 +1061,7 @@ jobs:
run: chmod +x target/codspeed/simulation/ruff_benchmark/ty
- name: "Run benchmarks"
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
uses: CodSpeedHQ/action@972e3437949c89e1357ebd1a2dbc852fcbc57245 # v4.5.1
with:
mode: simulation
run: cargo codspeed run --bench ty "${{ matrix.benchmark }}"
@@ -1098,7 +1098,7 @@ jobs:
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-codspeed
@@ -1136,7 +1136,7 @@ jobs:
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: "Install codspeed"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-codspeed
@@ -1150,7 +1150,7 @@ jobs:
run: chmod +x target/codspeed/walltime/ruff_benchmark/ty_walltime
- name: "Run benchmarks"
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
uses: CodSpeedHQ/action@972e3437949c89e1357ebd1a2dbc852fcbc57245 # v4.5.1
env:
# enabling walltime flamegraphs adds ~6 minutes to the CI time, and they don't
# appear to provide much useful insight for our walltime benchmarks right now

View File

@@ -32,13 +32,13 @@ repos:
- id: validate-pyproject
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.22
rev: 1.0.0
hooks:
- id: mdformat
language: python # means renovate will also update `additional_dependencies`
additional_dependencies:
- mdformat-mkdocs==4.0.0
- mdformat-footnote==0.1.1
- mdformat-mkdocs==5.0.0
- mdformat-footnote==0.1.2
exclude: |
(?x)^(
docs/formatter/black\.md

View File

@@ -1,5 +1,63 @@
# Changelog
## 0.14.11
Released on 2026-01-08.
### Preview features
- Consolidate diagnostics for matched disable/enable suppression comments ([#22099](https://github.com/astral-sh/ruff/pull/22099))
- Report diagnostics for invalid/unmatched range suppression comments ([#21908](https://github.com/astral-sh/ruff/pull/21908))
- \[`airflow`\] Passing positional argument into `airflow.lineage.hook.HookLineageCollector.create_asset` is not allowed (`AIR303`) ([#22046](https://github.com/astral-sh/ruff/pull/22046))
- \[`refurb`\] Mark `FURB192` fix as always unsafe ([#22210](https://github.com/astral-sh/ruff/pull/22210))
- \[`ruff`\] Add `non-empty-init-module` (`RUF067`) ([#22143](https://github.com/astral-sh/ruff/pull/22143))
### Bug fixes
- Fix GitHub format for multi-line diagnostics ([#22108](https://github.com/astral-sh/ruff/pull/22108))
- \[`flake8-unused-arguments`\] Mark `**kwargs` in `TypeVar` as used (`ARG001`) ([#22214](https://github.com/astral-sh/ruff/pull/22214))
### Rule changes
- Add `help:` subdiagnostics for several Ruff rules that can sometimes appear to disagree with `ty` ([#22331](https://github.com/astral-sh/ruff/pull/22331))
- \[`pylint`\] Demote `PLW1510` fix to display-only ([#22318](https://github.com/astral-sh/ruff/pull/22318))
- \[`pylint`\] Ignore identical members (`PLR1714`) ([#22220](https://github.com/astral-sh/ruff/pull/22220))
- \[`pylint`\] Improve diagnostic range for `PLC0206` ([#22312](https://github.com/astral-sh/ruff/pull/22312))
- \[`ruff`\] Improve fix title for `RUF102` invalid rule code ([#22100](https://github.com/astral-sh/ruff/pull/22100))
- \[`flake8-simplify`\]: Avoid unnecessary builtins import for `SIM105` ([#22358](https://github.com/astral-sh/ruff/pull/22358))
### Configuration
- Allow Python 3.15 as valid `target-version` value in preview ([#22419](https://github.com/astral-sh/ruff/pull/22419))
- Check `required-version` before parsing rules ([#22410](https://github.com/astral-sh/ruff/pull/22410))
- Include configured `src` directories when resolving graphs ([#22451](https://github.com/astral-sh/ruff/pull/22451))
### Documentation
- Update `T201` suggestion to not use root logger to satisfy `LOG015` ([#22059](https://github.com/astral-sh/ruff/pull/22059))
- Fix `iter` example in unsafe fixes doc ([#22118](https://github.com/astral-sh/ruff/pull/22118))
- \[`flake8_print`\] better suggestion for `basicConfig` in `T201` docs ([#22101](https://github.com/astral-sh/ruff/pull/22101))
- \[`pylint`\] Restore the fix safety docs for `PLW0133` ([#22211](https://github.com/astral-sh/ruff/pull/22211))
- Fix Jupyter notebook discovery info for editors ([#22447](https://github.com/astral-sh/ruff/pull/22447))
### Contributors
- [@charliermarsh](https://github.com/charliermarsh)
- [@ntBre](https://github.com/ntBre)
- [@cenviity](https://github.com/cenviity)
- [@njhearp](https://github.com/njhearp)
- [@cbachhuber](https://github.com/cbachhuber)
- [@jelle-openai](https://github.com/jelle-openai)
- [@AlexWaygood](https://github.com/AlexWaygood)
- [@ValdonVitija](https://github.com/ValdonVitija)
- [@BurntSushi](https://github.com/BurntSushi)
- [@Jkhall81](https://github.com/Jkhall81)
- [@PeterJCLaw](https://github.com/PeterJCLaw)
- [@harupy](https://github.com/harupy)
- [@amyreese](https://github.com/amyreese)
- [@sjyangkevin](https://github.com/sjyangkevin)
- [@woodruffw](https://github.com/woodruffw)
## 0.14.10
Released on 2025-12-18.

70
CLAUDE.md Normal file
View File

@@ -0,0 +1,70 @@
# Ruff Repository
This repository contains both Ruff (a Python linter and formatter) and ty (a Python type checker). The crates follow a naming convention: `ruff_*` for Ruff-specific code and `ty_*` for ty-specific code. ty reuses several Ruff crates, including the Python parser (`ruff_python_parser`) and AST definitions (`ruff_python_ast`).
## Running Tests
Run all tests (using `nextest` for faster execution):
```sh
cargo nextest run
```
For faster test execution, use the `fast-test` profile which enables optimizations while retaining debug info:
```sh
cargo nextest run --cargo-profile fast-test
```
Run tests for a specific crate:
```sh
cargo nextest run -p ty_python_semantic
```
Run a specific mdtest (use a substring of the test name):
```sh
MDTEST_TEST_FILTER="<filter>" cargo nextest run -p ty_python_semantic mdtest
```
Update snapshots after running tests:
```sh
cargo insta accept
```
## Running Clippy
```sh
cargo clippy --workspace --all-targets --all-features -- -D warnings
```
## Running Debug Builds
Use debug builds (not `--release`) when developing, as release builds lack debug assertions and have slower compile times.
Run Ruff:
```sh
cargo run --bin ruff -- check path/to/file.py
```
Run ty:
```sh
cargo run --bin ty -- check path/to/file.py
```
## Pull Requests
When working on ty, PR titles should start with `[ty]` and be tagged with the `ty` GitHub label.
## Development Guidelines
- All changes must be tested. If you're not testing your changes, you're not done.
- Get your tests to pass. If you didn't run the tests, your code does not work.
- Follow existing code style. Check neighboring files for patterns.
- Always run `uvx pre-commit run -a` at the end of a task.
- Avoid writing significant amounts of new code. This is often a sign that we're missing an existing method or mechanism that could help solve the problem. Look for existing utilities first.
- Avoid falling back to patterns that require `panic!`, `unreachable!`, or `.unwrap()`. Instead, try to encode those constraints in the type system.

83
Cargo.lock generated
View File

@@ -146,9 +146,12 @@ dependencies = [
[[package]]
name = "arc-swap"
version = "1.7.1"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e"
dependencies = [
"rustversion",
]
[[package]]
name = "argfile"
@@ -1030,7 +1033,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -1122,7 +1125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -1645,9 +1648,9 @@ dependencies = [
[[package]]
name = "insta"
version = "1.45.0"
version = "1.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b76866be74d68b1595eb8060cb9191dca9c021db2316558e52ddc5d55d41b66c"
checksum = "983e3b24350c84ab8a65151f537d67afbbf7153bb9f1110e03e9fa9b07f67a5c"
dependencies = [
"console 0.15.11",
"once_cell",
@@ -1778,9 +1781,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35"
checksum = "a87d9b8105c23642f50cbbae03d1f75d8422c5cb98ce7ee9271f7ff7505be6b8"
dependencies = [
"jiff-static",
"jiff-tzdb-platform",
@@ -1788,14 +1791,14 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde_core",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
name = "jiff-static"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69"
checksum = "b787bebb543f8969132630c51fd0afab173a86c6abae56ff3b9e5e3e3f9f6e58"
dependencies = [
"proc-macro2",
"quote",
@@ -2057,9 +2060,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "matchit"
version = "0.9.0"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea5f97102eb9e54ab99fb70bb175589073f554bdadfb74d9bd656482ea73e2a"
checksum = "b3eede3bdf92f3b4f9dc04072a9ce5ab557d5ec9038773bf9ffcd5588b3cc05b"
[[package]]
name = "memchr"
@@ -2631,9 +2634,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.103"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0"
dependencies = [
"unicode-ident",
]
@@ -2909,7 +2912,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.14.10"
version = "0.14.11"
dependencies = [
"anyhow",
"argfile",
@@ -2925,6 +2928,7 @@ dependencies = [
"filetime",
"globwalk",
"ignore",
"indexmap",
"indoc",
"insta",
"insta-cmd",
@@ -3168,7 +3172,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.14.10"
version = "0.14.11"
dependencies = [
"aho-corasick",
"anyhow",
@@ -3246,7 +3250,6 @@ name = "ruff_memory_usage"
version = "0.0.0"
dependencies = [
"get-size2",
"ordermap",
]
[[package]]
@@ -3527,7 +3530,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.14.10"
version = "0.14.11"
dependencies = [
"console_error_panic_hook",
"console_log",
@@ -3617,15 +3620,15 @@ checksum = "781442f29170c5c93b7185ad559492601acdc71d5bb0706f5868094f45cfcd08"
[[package]]
name = "rustix"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
"bitflags 2.10.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -3643,7 +3646,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "salsa"
version = "0.25.2"
source = "git+https://github.com/salsa-rs/salsa.git?rev=309c249088fdeef0129606fa34ec2eefc74736ff#309c249088fdeef0129606fa34ec2eefc74736ff"
source = "git+https://github.com/salsa-rs/salsa.git?rev=9860ff6ca0f1f8f3a8d6b832020002790b501254#9860ff6ca0f1f8f3a8d6b832020002790b501254"
dependencies = [
"boxcar",
"compact_str",
@@ -3668,12 +3671,12 @@ dependencies = [
[[package]]
name = "salsa-macro-rules"
version = "0.25.2"
source = "git+https://github.com/salsa-rs/salsa.git?rev=309c249088fdeef0129606fa34ec2eefc74736ff#309c249088fdeef0129606fa34ec2eefc74736ff"
source = "git+https://github.com/salsa-rs/salsa.git?rev=9860ff6ca0f1f8f3a8d6b832020002790b501254#9860ff6ca0f1f8f3a8d6b832020002790b501254"
[[package]]
name = "salsa-macros"
version = "0.25.2"
source = "git+https://github.com/salsa-rs/salsa.git?rev=309c249088fdeef0129606fa34ec2eefc74736ff#309c249088fdeef0129606fa34ec2eefc74736ff"
source = "git+https://github.com/salsa-rs/salsa.git?rev=9860ff6ca0f1f8f3a8d6b832020002790b501254#9860ff6ca0f1f8f3a8d6b832020002790b501254"
dependencies = [
"proc-macro2",
"quote",
@@ -3692,9 +3695,9 @@ dependencies = [
[[package]]
name = "schemars"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289"
checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2"
dependencies = [
"dyn-clone",
"ref-cast",
@@ -3705,9 +3708,9 @@ dependencies = [
[[package]]
name = "schemars_derive"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301858a4023d78debd2353c7426dc486001bddc91ae31a76fb1f55132f7e2633"
checksum = "4908ad288c5035a8eb12cfdf0d49270def0a268ee162b75eeee0f85d155a7c45"
dependencies = [
"proc-macro2",
"quote",
@@ -3781,15 +3784,15 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.146"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8"
checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
"zmij",
]
[[package]]
@@ -4019,15 +4022,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.23.0"
version = "3.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"once_cell",
"rustix",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -4509,11 +4512,13 @@ dependencies = [
"regex-automata",
"ruff_cache",
"ruff_db",
"ruff_diagnostics",
"ruff_macros",
"ruff_memory_usage",
"ruff_options_metadata",
"ruff_python_ast",
"ruff_python_formatter",
"ruff_python_trivia",
"ruff_text_size",
"rustc-hash",
"salsa",
@@ -5131,7 +5136,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -5523,6 +5528,12 @@ dependencies = [
"zstd",
]
[[package]]
name = "zmij"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30e0d8dffbae3d840f64bda38e28391faef673a7b5a6017840f2a106c8145868"
[[package]]
name = "zstd"
version = "0.11.2+zstd.1.5.2"

View File

@@ -93,6 +93,7 @@ get-size2 = { version = "0.7.3", features = [
"smallvec",
"hashbrown",
"compact-str",
"ordermap"
] }
getrandom = { version = "0.3.1" }
glob = { version = "0.3.1" }
@@ -149,7 +150,7 @@ regex-automata = { version = "0.4.9" }
rustc-hash = { version = "2.0.0" }
rustc-stable-hash = { version = "0.1.2" }
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "309c249088fdeef0129606fa34ec2eefc74736ff", default-features = false, features = [
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "9860ff6ca0f1f8f3a8d6b832020002790b501254", default-features = false, features = [
"compact_str",
"macros",
"salsa_unstable",
@@ -334,6 +335,11 @@ strip = false
debug = "full"
lto = false
# Profile for faster iteration: applies minimal optimizations for faster tests.
[profile.fast-test]
inherits = "dev"
opt-level = 1
# The profile that 'cargo dist' will build with.
[profile.dist]
inherits = "release"

View File

@@ -150,8 +150,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.14.10/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.14.10/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.14.11/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.14.11/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -184,7 +184,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.14.10
rev: v0.14.11
hooks:
# Run the linter.
- id: ruff-check

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.14.10"
version = "0.14.11"
publish = true
authors = { workspace = true }
edition = { workspace = true }
@@ -48,6 +48,7 @@ colored = { workspace = true }
filetime = { workspace = true }
globwalk = { workspace = true }
ignore = { workspace = true }
indexmap = { workspace = true }
is-macro = { workspace = true }
itertools = { workspace = true }
jiff = { workspace = true }

View File

@@ -2,6 +2,7 @@ use crate::args::{AnalyzeGraphArgs, ConfigArguments};
use crate::resolve::resolve;
use crate::{ExitStatus, resolve_default_files};
use anyhow::Result;
use indexmap::IndexSet;
use log::{debug, warn};
use path_absolutize::CWD;
use ruff_db::system::{SystemPath, SystemPathBuf};
@@ -11,7 +12,7 @@ use ruff_linter::source_kind::SourceKind;
use ruff_linter::{warn_user, warn_user_once};
use ruff_python_ast::{PySourceType, SourceType};
use ruff_workspace::resolver::{ResolvedFile, match_exclusion, python_files_in_path};
use rustc_hash::FxHashMap;
use rustc_hash::{FxBuildHasher, FxHashMap};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
@@ -59,17 +60,34 @@ pub(crate) fn analyze_graph(
})
.collect::<FxHashMap<_, _>>();
// Create a database from the source roots.
let src_roots = package_roots
.values()
.filter_map(|package| package.as_deref())
.filter_map(|package| package.parent())
.map(Path::to_path_buf)
.filter_map(|path| SystemPathBuf::from_path_buf(path).ok())
.collect();
// Create a database from the source roots, combining configured `src` paths with detected
// package roots. Configured paths are added first so they take precedence, and duplicates
// are removed.
let mut src_roots: IndexSet<SystemPathBuf, FxBuildHasher> = IndexSet::default();
// Add configured `src` paths first (for precedence), filtering to only include existing
// directories.
src_roots.extend(
pyproject_config
.settings
.linter
.src
.iter()
.filter(|path| path.is_dir())
.filter_map(|path| SystemPathBuf::from_path_buf(path.clone()).ok()),
);
// Add detected package roots.
src_roots.extend(
package_roots
.values()
.filter_map(|package| package.as_deref())
.filter_map(|path| path.parent())
.filter_map(|path| SystemPathBuf::from_path_buf(path.to_path_buf()).ok()),
);
let db = ModuleDb::from_src_roots(
src_roots,
src_roots.into_iter().collect(),
pyproject_config
.settings
.analyze

View File

@@ -1305,7 +1305,7 @@ mod tests {
settings.add_filter(r"(Panicked at) [^:]+:\d+:\d+", "$1 <location>");
let _s = settings.bind_to_scope();
assert_snapshot!(str::from_utf8(&buf)?, @r"
assert_snapshot!(str::from_utf8(&buf)?, @"
io: test.py: Permission denied
--> test.py:1:1

View File

@@ -4,4 +4,3 @@ source: crates/ruff/src/commands/check.rs
/home/ferris/project/code.py:1:1: E902 Permission denied (os error 13)
/home/ferris/project/notebook.ipynb:1:1: E902 Permission denied (os error 13)
/home/ferris/project/pyproject.toml:1:1: E902 Permission denied (os error 13)

View File

@@ -1,6 +1,5 @@
---
source: crates/ruff/src/version.rs
expression: version
snapshot_kind: text
---
0.0.0

View File

@@ -1,6 +1,5 @@
---
source: crates/ruff/src/version.rs
expression: version
snapshot_kind: text
---
0.0.0 (53b0f5d92 2023-10-19)

View File

@@ -1,6 +1,5 @@
---
source: crates/ruff/src/version.rs
expression: version
snapshot_kind: text
---
0.0.0+24 (53b0f5d92 2023-10-19)

View File

@@ -1,7 +1,6 @@
---
source: crates/ruff/src/version.rs
expression: version
snapshot_kind: text
---
{
"version": "0.0.0",

View File

@@ -132,29 +132,29 @@ fn dependents() -> Result<()> {
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().arg("--direction").arg("dependents").current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [],
"ruff/b.py": [
"ruff/a.py"
],
"ruff/c.py": [
"ruff/b.py"
],
"ruff/d.py": [
"ruff/c.py"
],
"ruff/e.py": [
"ruff/d.py"
]
}
assert_cmd_snapshot!(command().arg("--direction").arg("dependents").current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [],
"ruff/b.py": [
"ruff/a.py"
],
"ruff/c.py": [
"ruff/b.py"
],
"ruff/d.py": [
"ruff/c.py"
],
"ruff/e.py": [
"ruff/d.py"
]
}
----- stderr -----
"###);
----- stderr -----
"#);
});
Ok(())
@@ -184,21 +184,21 @@ fn string_detection() -> Result<()> {
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [
"ruff/b.py"
],
"ruff/b.py": [],
"ruff/c.py": []
}
assert_cmd_snapshot!(command().current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [
"ruff/b.py"
],
"ruff/b.py": [],
"ruff/c.py": []
}
----- stderr -----
"###);
----- stderr -----
"#);
});
insta::with_settings!({
@@ -319,7 +319,7 @@ fn globs() -> Result<()> {
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r###"
assert_cmd_snapshot!(command().current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
@@ -340,7 +340,7 @@ fn globs() -> Result<()> {
}
----- stderr -----
"###);
"#);
});
Ok(())
@@ -368,7 +368,7 @@ fn exclude() -> Result<()> {
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r###"
assert_cmd_snapshot!(command().current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
@@ -381,7 +381,7 @@ fn exclude() -> Result<()> {
}
----- stderr -----
"###);
"#);
});
Ok(())
@@ -421,7 +421,7 @@ fn wildcard() -> Result<()> {
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r###"
assert_cmd_snapshot!(command().current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
@@ -443,7 +443,7 @@ fn wildcard() -> Result<()> {
}
----- stderr -----
"###);
"#);
});
Ok(())
@@ -639,7 +639,7 @@ fn venv() -> Result<()> {
}, {
assert_cmd_snapshot!(
command().args(["--python", "none"]).arg("packages/albatross").current_dir(&root),
@r"
@"
success: false
exit_code: 2
----- stdout -----
@@ -695,7 +695,7 @@ fn notebook_basic() -> Result<()> {
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r###"
assert_cmd_snapshot!(command().current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
@@ -708,7 +708,122 @@ fn notebook_basic() -> Result<()> {
}
----- stderr -----
"###);
"#);
});
Ok(())
}
/// Test that the `src` configuration option is respected.
///
/// This is useful for monorepos where there are multiple source directories that need to be
/// included in the module resolution search path.
#[test]
fn src_option() -> Result<()> {
let tempdir = TempDir::new()?;
let root = ChildPath::new(tempdir.path());
// Create a lib directory with a package.
root.child("lib")
.child("mylib")
.child("__init__.py")
.write_str("def helper(): pass")?;
// Create an app directory with a file that imports from mylib.
root.child("app").child("__init__.py").write_str("")?;
root.child("app")
.child("main.py")
.write_str("from mylib import helper")?;
// Without src configured, the import from mylib won't resolve.
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().arg("app").current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
{
"app/__init__.py": [],
"app/main.py": []
}
----- stderr -----
"#);
});
// With src = ["lib"], the import should resolve.
root.child("ruff.toml").write_str(indoc::indoc! {r#"
src = ["lib"]
"#})?;
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().arg("app").current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
{
"app/__init__.py": [],
"app/main.py": [
"lib/mylib/__init__.py"
]
}
----- stderr -----
"#);
});
Ok(())
}
/// Test that glob patterns in `src` are expanded.
#[test]
fn src_glob_expansion() -> Result<()> {
let tempdir = TempDir::new()?;
let root = ChildPath::new(tempdir.path());
// Create multiple lib directories with packages.
root.child("libs")
.child("lib_a")
.child("pkg_a")
.child("__init__.py")
.write_str("def func_a(): pass")?;
root.child("libs")
.child("lib_b")
.child("pkg_b")
.child("__init__.py")
.write_str("def func_b(): pass")?;
// Create an app that imports from both packages.
root.child("app").child("__init__.py").write_str("")?;
root.child("app")
.child("main.py")
.write_str("from pkg_a import func_a\nfrom pkg_b import func_b")?;
// Use a glob pattern to include all lib directories.
root.child("ruff.toml").write_str(indoc::indoc! {r#"
src = ["libs/*"]
"#})?;
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().arg("app").current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
{
"app/__init__.py": [],
"app/main.py": [
"libs/lib_a/pkg_a/__init__.py",
"libs/lib_b/pkg_b/__init__.py"
]
}
----- stderr -----
"#);
});
Ok(())
@@ -765,7 +880,7 @@ fn notebook_with_magic() -> Result<()> {
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r###"
assert_cmd_snapshot!(command().current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
@@ -778,7 +893,7 @@ fn notebook_with_magic() -> Result<()> {
}
----- stderr -----
"###);
"#);
});
Ok(())

View File

@@ -29,7 +29,7 @@ fn type_checking_imports() -> anyhow::Result<()> {
("ruff/c.py", ""),
])?;
assert_cmd_snapshot!(test.command(), @r###"
assert_cmd_snapshot!(test.command(), @r#"
success: true
exit_code: 0
----- stdout -----
@@ -46,12 +46,12 @@ fn type_checking_imports() -> anyhow::Result<()> {
}
----- stderr -----
"###);
"#);
assert_cmd_snapshot!(
test.command()
.arg("--no-type-checking-imports"),
@r###"
@r#"
success: true
exit_code: 0
----- stdout -----
@@ -65,7 +65,7 @@ fn type_checking_imports() -> anyhow::Result<()> {
}
----- stderr -----
"###
"#
);
Ok(())
@@ -103,7 +103,7 @@ fn type_checking_imports_from_config() -> anyhow::Result<()> {
),
])?;
assert_cmd_snapshot!(test.command(), @r###"
assert_cmd_snapshot!(test.command(), @r#"
success: true
exit_code: 0
----- stdout -----
@@ -117,7 +117,7 @@ fn type_checking_imports_from_config() -> anyhow::Result<()> {
}
----- stderr -----
"###);
"#);
test.write_file(
"ruff.toml",
@@ -127,7 +127,7 @@ fn type_checking_imports_from_config() -> anyhow::Result<()> {
"#,
)?;
assert_cmd_snapshot!(test.command(), @r###"
assert_cmd_snapshot!(test.command(), @r#"
success: true
exit_code: 0
----- stdout -----
@@ -144,7 +144,7 @@ fn type_checking_imports_from_config() -> anyhow::Result<()> {
}
----- stderr -----
"###
"#
);
Ok(())

View File

@@ -51,7 +51,7 @@ fn default_files() -> Result<()> {
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("--check"), @r"
.arg("--check"), @"
success: false
exit_code: 1
----- stdout -----
@@ -71,7 +71,7 @@ fn format_warn_stdin_filename_with_files() -> Result<()> {
assert_cmd_snapshot!(test.format_command()
.args(["--isolated", "--stdin-filename", "foo.py"])
.arg("foo.py")
.pass_stdin("foo = 1"), @r"
.pass_stdin("foo = 1"), @"
success: true
exit_code: 0
----- stdout -----
@@ -87,7 +87,7 @@ fn format_warn_stdin_filename_with_files() -> Result<()> {
fn nonexistent_config_file() -> Result<()> {
let test = CliTest::new()?;
assert_cmd_snapshot!(test.format_command()
.args(["--config", "foo.toml", "."]), @r"
.args(["--config", "foo.toml", "."]), @"
success: false
exit_code: 2
----- stdout -----
@@ -111,7 +111,7 @@ fn nonexistent_config_file() -> Result<()> {
fn config_override_rejected_if_invalid_toml() -> Result<()> {
let test = CliTest::new()?;
assert_cmd_snapshot!(test.format_command()
.args(["--config", "foo = bar", "."]), @r"
.args(["--config", "foo = bar", "."]), @"
success: false
exit_code: 2
----- stdout -----
@@ -145,7 +145,7 @@ fn too_many_config_files() -> Result<()> {
.arg("ruff.toml")
.arg("--config")
.arg("ruff2.toml")
.arg("."), @r"
.arg("."), @"
success: false
exit_code: 2
----- stdout -----
@@ -168,7 +168,7 @@ fn config_file_and_isolated() -> Result<()> {
.arg("--isolated")
.arg("--config")
.arg("ruff.toml")
.arg("."), @r"
.arg("."), @"
success: false
exit_code: 2
----- stdout -----
@@ -390,7 +390,7 @@ fn mixed_line_endings() -> Result<()> {
assert_cmd_snapshot!(test.format_command()
.arg("--diff")
.arg("--isolated")
.arg("."), @r"
.arg("."), @"
success: true
exit_code: 0
----- stdout -----
@@ -446,7 +446,7 @@ OTHER = "OTHER"
// Explicitly pass test.py, should be formatted regardless of it being excluded by format.exclude
.arg("test.py")
// Format all other files in the directory, should respect the `exclude` and `format.exclude` options
.arg("."), @r"
.arg("."), @"
success: false
exit_code: 1
----- stdout -----
@@ -469,7 +469,7 @@ fn deduplicate_directory_and_explicit_file() -> Result<()> {
.arg("--check")
.arg(".")
.arg("main.py"),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -495,7 +495,7 @@ from module import =
assert_cmd_snapshot!(test.format_command()
.arg("--check")
.arg("--isolated")
.arg("main.py"), @r"
.arg("main.py"), @"
success: false
exit_code: 2
----- stdout -----
@@ -522,7 +522,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("--check")
.arg("main.py"), @r"
.arg("main.py"), @"
success: false
exit_code: 1
----- stdout -----
@@ -534,7 +534,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("main.py"), @r"
.arg("main.py"), @"
success: true
exit_code: 0
----- stdout -----
@@ -545,7 +545,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("main.py"), @r"
.arg("main.py"), @"
success: true
exit_code: 0
----- stdout -----
@@ -614,7 +614,7 @@ fn output_format_notebook() -> Result<()> {
assert_cmd_snapshot!(
test.format_command().args(["--isolated", "--preview", "--check"]).arg(path),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -672,7 +672,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("--exit-non-zero-on-format")
.arg("main.py"), @r"
.arg("main.py"), @"
success: false
exit_code: 1
----- stdout -----
@@ -685,7 +685,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("--exit-non-zero-on-format")
.arg("main.py"), @r"
.arg("main.py"), @"
success: true
exit_code: 0
----- stdout -----
@@ -701,7 +701,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("--exit-non-zero-on-fix")
.arg("main.py"), @r"
.arg("main.py"), @"
success: false
exit_code: 1
----- stdout -----
@@ -714,7 +714,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("--exit-non-zero-on-fix")
.arg("main.py"), @r"
.arg("main.py"), @"
success: true
exit_code: 0
----- stdout -----
@@ -771,7 +771,7 @@ OTHER = "OTHER"
// Explicitly pass test.py, should not be formatted because of --force-exclude
.arg("test.py")
// Format all other files in the directory, should respect the `exclude` and `format.exclude` options
.arg("."), @r"
.arg("."), @"
success: false
exit_code: 1
----- stdout -----
@@ -931,7 +931,7 @@ tab-size = 2
.pass_stdin(r"
if True:
pass
"), @r"
"), @"
success: false
exit_code: 2
----- stdout -----
@@ -1144,7 +1144,7 @@ def say_hy(name: str):
assert_cmd_snapshot!(test.format_command()
.arg("--config")
.arg("ruff.toml")
.arg("test.py"), @r"
.arg("test.py"), @"
success: true
exit_code: 0
----- stdout -----
@@ -1184,7 +1184,7 @@ def say_hy(name: str):
assert_cmd_snapshot!(test.format_command()
.arg("--config")
.arg("ruff.toml")
.arg("test.py"), @r"
.arg("test.py"), @"
success: true
exit_code: 0
----- stdout -----
@@ -1216,7 +1216,7 @@ def say_hy(name: str):
assert_cmd_snapshot!(test.format_command()
.arg("--config")
.arg("ruff.toml")
.arg("test.py"), @r"
.arg("test.py"), @"
success: true
exit_code: 0
----- stdout -----
@@ -1246,7 +1246,7 @@ fn test_diff() -> Result<()> {
assert_cmd_snapshot!(
test.format_command().args(["--isolated", "--diff"]).args(paths),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1311,7 +1311,7 @@ fn test_diff_no_change() -> Result<()> {
let paths = [fixtures.join("unformatted.py")];
assert_cmd_snapshot!(
test.format_command().args(["--isolated", "--diff"]).args(paths),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1341,7 +1341,7 @@ fn test_diff_stdin_unformatted() -> Result<()> {
test.format_command()
.args(["--isolated", "--diff", "-", "--stdin-filename", "unformatted.py"])
.pass_stdin(unformatted),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1366,7 +1366,7 @@ fn test_diff_stdin_formatted() -> Result<()> {
let unformatted = fs::read(fixtures.join("formatted.py")).unwrap();
assert_cmd_snapshot!(
test.format_command().args(["--isolated", "--diff", "-"]).pass_stdin(unformatted),
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -1873,7 +1873,7 @@ include = ["*.ipy"]
assert_cmd_snapshot!(test.format_command()
.args(["--config", "ruff.toml"])
.args(["--extension", "ipy:ipynb"])
.arg("."), @r"
.arg("."), @"
success: false
exit_code: 2
----- stdout -----
@@ -1938,7 +1938,7 @@ include = ["*.ipy"]
assert_cmd_snapshot!(test.format_command()
.args(["--config", "ruff.toml"])
.args(["--extension", "ipy:ipynb"])
.arg("."), @r"
.arg("."), @"
success: true
exit_code: 0
----- stdout -----
@@ -2021,7 +2021,7 @@ def file2(arg1, arg2,):
assert_cmd_snapshot!(test.format_command()
.args(["--isolated", "--range=1:8-1:15"])
.arg("file1.py")
.arg("file2.py"), @r"
.arg("file2.py"), @"
success: false
exit_code: 2
----- stdout -----
@@ -2068,7 +2068,7 @@ fn range_start_larger_than_end() -> Result<()> {
def foo(arg1, arg2,):
print("Shouldn't format this" )
"#), @r"
"#), @"
success: false
exit_code: 2
----- stdout -----
@@ -2168,7 +2168,7 @@ fn range_missing_line() -> Result<()> {
def foo(arg1, arg2,):
print("Should format this" )
"#), @r"
"#), @"
success: false
exit_code: 2
----- stdout -----
@@ -2192,7 +2192,7 @@ fn zero_line_number() -> Result<()> {
def foo(arg1, arg2,):
print("Should format this" )
"#), @r"
"#), @"
success: false
exit_code: 2
----- stdout -----
@@ -2217,7 +2217,7 @@ fn column_and_line_zero() -> Result<()> {
def foo(arg1, arg2,):
print("Should format this" )
"#), @r"
"#), @"
success: false
exit_code: 2
----- stdout -----
@@ -2274,7 +2274,7 @@ fn range_formatting_notebook() -> Result<()> {
"nbformat": 4,
"nbformat_minor": 5
}
"#), @r"
"#), @"
success: false
exit_code: 2
----- stdout -----
@@ -2355,7 +2355,7 @@ fn cookiecutter_globbing() -> Result<()> {
])?;
assert_cmd_snapshot!(test.format_command()
.args(["--isolated", "--diff", "."]), @r"
.args(["--isolated", "--diff", "."]), @"
success: true
exit_code: 0
----- stdout -----
@@ -2374,7 +2374,7 @@ fn stable_output_format_warning() -> Result<()> {
test.format_command()
.args(["--output-format=full", "-"])
.pass_stdin(""),
@r"
@"
success: true
exit_code: 0
----- stdout -----

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:
@@ -17,7 +17,6 @@ info:
- "--fix"
- "-"
stdin: "1"
snapshot_kind: text
---
success: false
exit_code: 2

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -12,7 +12,6 @@ info:
- "--target-version"
- py39
- input.py
snapshot_kind: text
---
success: false
exit_code: 1

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/lint.rs
source: crates/ruff/tests/cli/lint.rs
info:
program: ruff
args:

View File

@@ -18,13 +18,13 @@ fn check_in_deleted_directory_errors() {
set_current_dir(&temp_path).unwrap();
drop(temp_dir);
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)).arg("check"), @r###"
success: false
exit_code: 2
----- stdout -----
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)).arg("check"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: Working directory does not exist
"###);
----- stderr -----
ruff failed
Cause: Working directory does not exist
");
}

View File

@@ -97,7 +97,7 @@ impl<'a> RuffCheck<'a> {
fn stdin_success() {
let mut cmd = RuffCheck::default().args([]).build();
assert_cmd_snapshot!(cmd
.pass_stdin(""), @r"
.pass_stdin(""), @"
success: true
exit_code: 0
----- stdout -----
@@ -111,7 +111,7 @@ fn stdin_success() {
fn stdin_error() {
let mut cmd = RuffCheck::default().args([]).build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\n"), @r"
.pass_stdin("import os\n"), @"
success: false
exit_code: 1
----- stdout -----
@@ -136,7 +136,7 @@ fn stdin_filename() {
.args(["--stdin-filename", "F401.py"])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\n"), @r"
.pass_stdin("import os\n"), @"
success: false
exit_code: 1
----- stdout -----
@@ -172,7 +172,7 @@ import bar # unused import
)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--isolated", "--no-cache", "--select", "F401"]).current_dir(tempdir.path()), @r"
.args(["check", "--isolated", "--no-cache", "--select", "F401"]).current_dir(tempdir.path()), @"
success: false
exit_code: 1
----- stdout -----
@@ -208,7 +208,7 @@ fn check_warn_stdin_filename_with_files() {
.filename("foo.py")
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\n"), @r"
.pass_stdin("import os\n"), @"
success: false
exit_code: 1
----- stdout -----
@@ -235,7 +235,7 @@ fn stdin_source_type_py() {
.args(["--stdin-filename", "TCH.py"])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\n"), @r"
.pass_stdin("import os\n"), @"
success: false
exit_code: 1
----- stdout -----
@@ -261,7 +261,7 @@ fn stdin_source_type_pyi() {
.args(["--stdin-filename", "TCH.pyi", "--select", "TCH"])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\n"), @r"
.pass_stdin("import os\n"), @"
success: true
exit_code: 0
----- stdout -----
@@ -294,7 +294,7 @@ fn stdin_json() {
fn stdin_fix_py() {
let mut cmd = RuffCheck::default().args(["--fix"]).build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\nimport sys\n\nprint(sys.version)\n"), @r"
.pass_stdin("import os\nimport sys\n\nprint(sys.version)\n"), @"
success: true
exit_code: 0
----- stdout -----
@@ -572,7 +572,7 @@ fn stdin_override_parser_ipynb() {
},
"nbformat": 4,
"nbformat_minor": 5
}"#), @r"
}"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -610,7 +610,7 @@ fn stdin_override_parser_py() {
])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\n"), @r"
.pass_stdin("import os\n"), @"
success: false
exit_code: 1
----- stdout -----
@@ -633,7 +633,7 @@ fn stdin_override_parser_py() {
fn stdin_fix_when_not_fixable_should_still_print_contents() {
let mut cmd = RuffCheck::default().args(["--fix"]).build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\nimport sys\n\nif (1, 2):\n print(sys.version)\n"), @r###"
.pass_stdin("import os\nimport sys\n\nif (1, 2):\n print(sys.version)\n"), @"
success: false
exit_code: 1
----- stdout -----
@@ -654,14 +654,14 @@ fn stdin_fix_when_not_fixable_should_still_print_contents() {
|
Found 2 errors (1 fixed, 1 remaining).
"###);
");
}
#[test]
fn stdin_fix_when_no_issues_should_still_print_contents() {
let mut cmd = RuffCheck::default().args(["--fix"]).build();
assert_cmd_snapshot!(cmd
.pass_stdin("import sys\n\nprint(sys.version)\n"), @r"
.pass_stdin("import sys\n\nprint(sys.version)\n"), @"
success: true
exit_code: 0
----- stdout -----
@@ -805,7 +805,7 @@ fn stdin_format_jupyter() {
fn stdin_parse_error() {
let mut cmd = RuffCheck::default().build();
assert_cmd_snapshot!(cmd
.pass_stdin("from foo import\n"), @r"
.pass_stdin("from foo import\n"), @"
success: false
exit_code: 1
----- stdout -----
@@ -826,7 +826,7 @@ fn stdin_parse_error() {
fn stdin_multiple_parse_error() {
let mut cmd = RuffCheck::default().build();
assert_cmd_snapshot!(cmd
.pass_stdin("from foo import\nbar =\n"), @r"
.pass_stdin("from foo import\nbar =\n"), @"
success: false
exit_code: 1
----- stdout -----
@@ -857,7 +857,7 @@ fn parse_error_not_included() {
// Parse errors are always shown
let mut cmd = RuffCheck::default().args(["--select=I"]).build();
assert_cmd_snapshot!(cmd
.pass_stdin("foo =\n"), @r"
.pass_stdin("foo =\n"), @"
success: false
exit_code: 1
----- stdout -----
@@ -878,7 +878,7 @@ fn parse_error_not_included() {
fn full_output_preview() {
let mut cmd = RuffCheck::default().args(["--preview"]).build();
assert_cmd_snapshot!(cmd
.pass_stdin("l = 1"), @r"
.pass_stdin("l = 1"), @"
success: false
exit_code: 1
----- stdout -----
@@ -907,7 +907,7 @@ preview = true
",
)?;
let mut cmd = RuffCheck::default().config(&pyproject_toml).build();
assert_cmd_snapshot!(cmd.pass_stdin("l = 1"), @r"
assert_cmd_snapshot!(cmd.pass_stdin("l = 1"), @"
success: false
exit_code: 1
----- stdout -----
@@ -929,7 +929,7 @@ preview = true
fn full_output_format() {
let mut cmd = RuffCheck::default().output_format("full").build();
assert_cmd_snapshot!(cmd
.pass_stdin("l = 1"), @r"
.pass_stdin("l = 1"), @"
success: false
exit_code: 1
----- stdout -----
@@ -967,7 +967,7 @@ fn rule_f401_output_text() {
#[test]
fn rule_invalid_rule_name() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404"]), @r"
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404"]), @"
success: false
exit_code: 2
----- stdout -----
@@ -981,7 +981,7 @@ fn rule_invalid_rule_name() {
#[test]
fn rule_invalid_rule_name_output_json() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404", "--output-format", "json"]), @r"
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404", "--output-format", "json"]), @"
success: false
exit_code: 2
----- stdout -----
@@ -995,7 +995,7 @@ fn rule_invalid_rule_name_output_json() {
#[test]
fn rule_invalid_rule_name_output_text() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404", "--output-format", "text"]), @r"
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404", "--output-format", "text"]), @"
success: false
exit_code: 2
----- stdout -----
@@ -1016,7 +1016,7 @@ fn show_statistics() {
.pass_stdin(r#"
def mvce(keys, values):
return {key: value for key, value in zip(keys, values)}
"#), @r"
"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -1037,7 +1037,7 @@ fn show_statistics_unsafe_fixes() {
.pass_stdin(r#"
def mvce(keys, values):
return {key: value for key, value in zip(keys, values)}
"#), @r"
"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -1152,7 +1152,7 @@ fn show_statistics_partial_fix() {
.args(["--select", "UP035", "--statistics"])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("from typing import List, AsyncGenerator"), @r"
.pass_stdin("from typing import List, AsyncGenerator"), @"
success: false
exit_code: 1
----- stdout -----
@@ -1173,7 +1173,7 @@ fn show_statistics_syntax_errors() {
// ParseError
assert_cmd_snapshot!(
cmd.pass_stdin("x ="),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1186,7 +1186,7 @@ fn show_statistics_syntax_errors() {
// match before 3.10, UnsupportedSyntaxError
assert_cmd_snapshot!(
cmd.pass_stdin("match 2:\n case 1: ..."),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1199,7 +1199,7 @@ fn show_statistics_syntax_errors() {
// rebound comprehension variable, SemanticSyntaxError
assert_cmd_snapshot!(
cmd.pass_stdin("[x := 1 for x in range(0)]"),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1216,7 +1216,7 @@ fn preview_enabled_prefix() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF9", "--output-format=concise", "--preview"])
.build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: false
exit_code: 1
----- stdout -----
@@ -1238,7 +1238,7 @@ fn preview_enabled_all() {
let mut cmd = RuffCheck::default()
.args(["--select", "ALL", "--output-format=concise", "--preview"])
.build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: false
exit_code: 1
----- stdout -----
@@ -1265,7 +1265,7 @@ fn preview_enabled_direct() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF911", "--output-format=concise", "--preview"])
.build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: false
exit_code: 1
----- stdout -----
@@ -1282,7 +1282,7 @@ fn preview_disabled_direct() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF911", "--output-format=concise"])
.build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: true
exit_code: 0
----- stdout -----
@@ -1299,7 +1299,7 @@ fn preview_disabled_prefix_empty() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF91", "--output-format=concise"])
.build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: true
exit_code: 0
----- stdout -----
@@ -1316,7 +1316,7 @@ fn preview_disabled_does_not_warn_for_empty_ignore_selections() {
let mut cmd = RuffCheck::default()
.args(["--ignore", "RUF9", "--output-format=concise"])
.build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: true
exit_code: 0
----- stdout -----
@@ -1332,7 +1332,7 @@ fn preview_disabled_does_not_warn_for_empty_fixable_selections() {
let mut cmd = RuffCheck::default()
.args(["--fixable", "RUF9", "--output-format=concise"])
.build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: true
exit_code: 0
----- stdout -----
@@ -1354,7 +1354,7 @@ fn preview_group_selector() {
])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("I=42\n"), @r"
.pass_stdin("I=42\n"), @"
success: false
exit_code: 2
----- stdout -----
@@ -1379,7 +1379,7 @@ fn preview_enabled_group_ignore() {
"--output-format=concise",
])
.build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: false
exit_code: 1
----- stdout -----
@@ -1400,7 +1400,7 @@ fn preview_enabled_group_ignore() {
fn removed_direct() {
// Selection of a removed rule should fail
let mut cmd = RuffCheck::default().args(["--select", "RUF931"]).build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: false
exit_code: 2
----- stdout -----
@@ -1418,7 +1418,7 @@ fn removed_direct_multiple() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF930", "--select", "RUF931"])
.build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: false
exit_code: 2
----- stdout -----
@@ -1436,7 +1436,7 @@ fn removed_indirect() {
// Selection _including_ a removed rule without matching should not fail
// nor should the rule be used
let mut cmd = RuffCheck::default().args(["--select", "RUF93"]).build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: true
exit_code: 0
----- stdout -----
@@ -1449,7 +1449,7 @@ fn removed_indirect() {
#[test]
fn removed_ignore_direct() {
let mut cmd = RuffCheck::default().args(["--ignore", "UP027"]).build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: true
exit_code: 0
----- stdout -----
@@ -1466,7 +1466,7 @@ fn removed_ignore_multiple_direct() {
let mut cmd = RuffCheck::default()
.args(["--ignore", "UP027", "--ignore", "PLR1706"])
.build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: true
exit_code: 0
----- stdout -----
@@ -1482,7 +1482,7 @@ fn removed_ignore_multiple_direct() {
#[test]
fn removed_ignore_remapped_direct() {
let mut cmd = RuffCheck::default().args(["--ignore", "PGH001"]).build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: true
exit_code: 0
----- stdout -----
@@ -1498,7 +1498,7 @@ fn removed_ignore_indirect() {
// `PLR170` includes removed rules but should not select or warn
// since it is not a "direct" selection
let mut cmd = RuffCheck::default().args(["--ignore", "PLR170"]).build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: true
exit_code: 0
----- stdout -----
@@ -1512,7 +1512,7 @@ fn removed_ignore_indirect() {
fn redirect_direct() {
// Selection of a redirected rule directly should use the new rule and warn
let mut cmd = RuffCheck::default().args(["--select", "RUF940"]).build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: false
exit_code: 1
----- stdout -----
@@ -1531,7 +1531,7 @@ fn redirect_indirect() {
// Selection _including_ a redirected rule without matching should not fail
// nor should the rule be used
let mut cmd = RuffCheck::default().args(["--select", "RUF94"]).build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: true
exit_code: 0
----- stdout -----
@@ -1546,7 +1546,7 @@ fn redirect_prefix() {
// Selection using a redirected prefix should switch to all rules in the
// new prefix
let mut cmd = RuffCheck::default().args(["--select", "RUF96"]).build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: false
exit_code: 1
----- stdout -----
@@ -1565,7 +1565,7 @@ fn deprecated_direct() {
// Selection of a deprecated rule without preview enabled should still work
// but a warning should be displayed
let mut cmd = RuffCheck::default().args(["--select", "RUF920"]).build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: false
exit_code: 1
----- stdout -----
@@ -1586,7 +1586,7 @@ fn deprecated_multiple_direct() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF920", "--select", "RUF921"])
.build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: false
exit_code: 1
----- stdout -----
@@ -1609,7 +1609,7 @@ fn deprecated_indirect() {
// `RUF92` includes deprecated rules but should not warn
// since it is not a "direct" selection
let mut cmd = RuffCheck::default().args(["--select", "RUF92"]).build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: true
exit_code: 0
----- stdout -----
@@ -1625,7 +1625,7 @@ fn deprecated_direct_preview_enabled() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF920", "--preview"])
.build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: false
exit_code: 2
----- stdout -----
@@ -1642,7 +1642,7 @@ fn deprecated_indirect_preview_enabled() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF92", "--preview"])
.build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: true
exit_code: 0
----- stdout -----
@@ -1659,7 +1659,7 @@ fn deprecated_multiple_direct_preview_enabled() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF920", "--select", "RUF921", "--preview"])
.build();
assert_cmd_snapshot!(cmd, @r"
assert_cmd_snapshot!(cmd, @"
success: false
exit_code: 2
----- stdout -----
@@ -1720,7 +1720,7 @@ fn unreadable_dir() -> Result<()> {
.filename(unreadable_dir.to_str().unwrap())
.args([])
.build();
assert_cmd_snapshot!(cmd, @r###"
assert_cmd_snapshot!(cmd, @"
success: true
exit_code: 0
----- stdout -----
@@ -1728,7 +1728,7 @@ fn unreadable_dir() -> Result<()> {
----- stderr -----
warning: Encountered error: Permission denied (os error 13)
"###);
");
Ok(())
}
@@ -1758,7 +1758,7 @@ fn check_input_from_argfile() -> Result<()> {
(file_a_path.display().to_string().as_str(), "/path/to/a.py"),
]}, {
assert_cmd_snapshot!(cmd
.pass_stdin(""), @r"
.pass_stdin(""), @"
success: false
exit_code: 1
----- stdout -----
@@ -1787,17 +1787,17 @@ fn missing_argfile_reports_error() {
insta::with_settings!({filters => vec![
("The system cannot find the file specified.", "No such file or directory")
]}, {
assert_cmd_snapshot!(cmd, @r"
success: false
exit_code: 2
----- stdout -----
assert_cmd_snapshot!(cmd, @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: Failed to read CLI arguments from files
Cause: failed to open file `!.txt`
Cause: No such file or directory (os error 2)
");
----- stderr -----
ruff failed
Cause: Failed to read CLI arguments from files
Cause: failed to open file `!.txt`
Cause: No such file or directory (os error 2)
");
});
}
@@ -1807,7 +1807,7 @@ fn check_hints_hidden_unsafe_fixes() {
.args(["--select", "RUF901,RUF902"])
.build();
assert_cmd_snapshot!(cmd,
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1829,7 +1829,7 @@ fn check_hints_hidden_unsafe_fixes_with_no_safe_fixes() {
let mut cmd = RuffCheck::default().args(["--select", "RUF902"]).build();
assert_cmd_snapshot!(cmd
.pass_stdin("x = {'a': 1, 'a': 1}\n"),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1849,7 +1849,7 @@ fn check_no_hint_for_hidden_unsafe_fixes_when_disabled() {
.args(["--select", "RUF901,RUF902", "--no-unsafe-fixes"])
.build();
assert_cmd_snapshot!(cmd,
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1873,7 +1873,7 @@ fn check_no_hint_for_hidden_unsafe_fixes_with_no_safe_fixes_when_disabled() {
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("x = {'a': 1, 'a': 1}\n"),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1892,7 +1892,7 @@ fn check_shows_unsafe_fixes_with_opt_in() {
.args(["--select", "RUF901,RUF902", "--unsafe-fixes"])
.build();
assert_cmd_snapshot!(cmd,
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1915,7 +1915,7 @@ fn fix_applies_safe_fixes_by_default() {
.args(["--select", "RUF901,RUF902", "--fix"])
.build();
assert_cmd_snapshot!(cmd,
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1936,7 +1936,7 @@ fn fix_applies_unsafe_fixes_with_opt_in() {
.args(["--select", "RUF901,RUF902", "--fix", "--unsafe-fixes"])
.build();
assert_cmd_snapshot!(cmd,
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -1955,7 +1955,7 @@ fn fix_does_not_apply_display_only_fixes() {
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1975,7 +1975,7 @@ fn fix_does_not_apply_display_only_fixes_with_unsafe_fixes_enabled() {
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1994,7 +1994,7 @@ fn fix_only_unsafe_fixes_available() {
.args(["--select", "RUF902", "--fix"])
.build();
assert_cmd_snapshot!(cmd,
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -2014,7 +2014,7 @@ fn fix_only_flag_applies_safe_fixes_by_default() {
.args(["--select", "RUF901,RUF902", "--fix-only"])
.build();
assert_cmd_snapshot!(cmd,
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -2031,7 +2031,7 @@ fn fix_only_flag_applies_unsafe_fixes_with_opt_in() {
.args(["--select", "RUF901,RUF902", "--fix-only", "--unsafe-fixes"])
.build();
assert_cmd_snapshot!(cmd,
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -2049,7 +2049,7 @@ fn diff_shows_safe_fixes_by_default() {
.args(["--select", "RUF901,RUF902", "--diff"])
.build();
assert_cmd_snapshot!(cmd,
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -2069,7 +2069,7 @@ fn diff_shows_unsafe_fixes_with_opt_in() {
.args(["--select", "RUF901,RUF902", "--diff", "--unsafe-fixes"])
.build();
assert_cmd_snapshot!(cmd,
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -2091,7 +2091,7 @@ fn diff_does_not_show_display_only_fixes_with_unsafe_fixes_enabled() {
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -2106,7 +2106,7 @@ fn diff_only_unsafe_fixes_available() {
.args(["--select", "RUF902", "--diff"])
.build();
assert_cmd_snapshot!(cmd,
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -2134,7 +2134,7 @@ extend-unsafe-fixes = ["RUF901"]
.args(["--select", "RUF901,RUF902"])
.build();
assert_cmd_snapshot!(cmd,
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -2170,7 +2170,7 @@ extend-safe-fixes = ["RUF902"]
.args(["--select", "RUF901,RUF902"])
.build();
assert_cmd_snapshot!(cmd,
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -2208,7 +2208,7 @@ extend-safe-fixes = ["RUF902"]
.args(["--select", "RUF901,RUF902"])
.build();
assert_cmd_snapshot!(cmd,
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -2248,7 +2248,7 @@ extend-safe-fixes = ["RUF9"]
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\nprint(str('foo'))\nisinstance(x, (int, str))\n"),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -2307,7 +2307,7 @@ def log(x, base) -> float:
.args(["--select", "D41"])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin(stdin), @r"
.pass_stdin(stdin), @"
success: true
exit_code: 0
----- stdout -----
@@ -2360,7 +2360,7 @@ select = ["RUF017"]
let mut cmd = RuffCheck::default().config(&ruff_toml).build();
assert_cmd_snapshot!(cmd
.pass_stdin("x = [1, 2, 3]\ny = [4, 5, 6]\nsum([x, y], [])"),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -2401,7 +2401,7 @@ unfixable = ["RUF"]
let mut cmd = RuffCheck::default().config(&ruff_toml).build();
assert_cmd_snapshot!(cmd
.pass_stdin("x = [1, 2, 3]\ny = [4, 5, 6]\nsum([x, y], [])"),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -2431,7 +2431,7 @@ fn pyproject_toml_stdin_syntax_error() {
assert_cmd_snapshot!(
cmd.pass_stdin("[project"),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -2457,7 +2457,7 @@ fn pyproject_toml_stdin_schema_error() {
assert_cmd_snapshot!(
cmd.pass_stdin("[project]\nname = 1"),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -2484,7 +2484,7 @@ fn pyproject_toml_stdin_no_applicable_rules_selected() {
assert_cmd_snapshot!(
cmd.pass_stdin("[project"),
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -2503,7 +2503,7 @@ fn pyproject_toml_stdin_no_applicable_rules_selected_2() {
assert_cmd_snapshot!(
cmd.pass_stdin("[project"),
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -2522,7 +2522,7 @@ fn pyproject_toml_stdin_no_errors() {
assert_cmd_snapshot!(
cmd.pass_stdin(r#"[project]\nname = "ruff"\nversion = "0.0.0""#),
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -2547,7 +2547,7 @@ fn pyproject_toml_stdin_schema_error_fix() {
assert_cmd_snapshot!(
cmd.pass_stdin("[project]\nname = 1"),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -2581,7 +2581,7 @@ fn pyproject_toml_stdin_schema_error_fix_only() {
assert_cmd_snapshot!(
cmd.pass_stdin("[project]\nname = 1"),
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -2607,7 +2607,7 @@ fn pyproject_toml_stdin_schema_error_fix_diff() {
assert_cmd_snapshot!(
cmd.pass_stdin("[project]\nname = 1"),
@r"
@"
success: true
exit_code: 0
----- stdout -----

View File

@@ -29,7 +29,7 @@ fn check_project_include_defaults() {
filters => TEST_FILTERS.to_vec()
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--show-files"]).current_dir(Path::new("./resources/test/fixtures/include-test")), @r"
.args(["check", "--show-files"]).current_dir(Path::new("./resources/test/fixtures/include-test")), @"
success: true
exit_code: 0
----- stdout -----
@@ -53,7 +53,7 @@ fn check_project_respects_direct_paths() {
filters => TEST_FILTERS.to_vec()
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--show-files", "b.py"]).current_dir(Path::new("./resources/test/fixtures/include-test")), @r"
.args(["check", "--show-files", "b.py"]).current_dir(Path::new("./resources/test/fixtures/include-test")), @"
success: true
exit_code: 0
----- stdout -----
@@ -72,7 +72,7 @@ fn check_project_respects_subdirectory_includes() {
filters => TEST_FILTERS.to_vec()
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--show-files", "subdirectory"]).current_dir(Path::new("./resources/test/fixtures/include-test")), @r"
.args(["check", "--show-files", "subdirectory"]).current_dir(Path::new("./resources/test/fixtures/include-test")), @"
success: true
exit_code: 0
----- stdout -----
@@ -91,7 +91,7 @@ fn check_project_from_project_subdirectory_respects_includes() {
filters => TEST_FILTERS.to_vec()
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--show-files"]).current_dir(Path::new("./resources/test/fixtures/include-test/subdirectory")), @r"
.args(["check", "--show-files"]).current_dir(Path::new("./resources/test/fixtures/include-test/subdirectory")), @"
success: true
exit_code: 0
----- stdout -----

View File

@@ -3,11 +3,12 @@ source: crates/ruff/tests/integration_test.rs
info:
program: ruff
args:
- "-"
- "--isolated"
- "--no-cache"
- check
- "--output-format"
- json
- "--no-cache"
- "--isolated"
- "-"
- "--stdin-filename"
- F401.py
stdin: "import os\n"
@@ -51,4 +52,3 @@ exit_code: 1
}
]
----- stderr -----

View File

@@ -16,7 +16,7 @@ const VERSION_FILTER: [(&str, &str); 1] = [(
fn version_basics() {
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME)).arg("version"), @r"
Command::new(get_cargo_bin(BIN_NAME)).arg("version"), @"
success: true
exit_code: 0
----- stdout -----
@@ -42,7 +42,7 @@ fn config_option_allowed_but_ignored() -> Result<()> {
.arg("version")
.arg("--config")
.arg(&ruff_dot_toml)
.args(["--config", "lint.isort.extra-standard-library = ['foo', 'bar']"]), @r"
.args(["--config", "lint.isort.extra-standard-library = ['foo', 'bar']"]), @"
success: true
exit_code: 0
----- stdout -----
@@ -60,7 +60,7 @@ fn config_option_ignored_but_validated() {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.arg("version")
.args(["--config", "foo = bar"]), @r"
.args(["--config", "foo = bar"]), @"
success: false
exit_code: 2
----- stdout -----
@@ -91,7 +91,7 @@ fn config_option_ignored_but_validated() {
fn isolated_option_allowed() {
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME)).arg("version").arg("--isolated"), @r"
Command::new(get_cargo_bin(BIN_NAME)).arg("version").arg("--isolated"), @"
success: true
exit_code: 0
----- stdout -----

View File

@@ -15,7 +15,7 @@ use ruff_db::files::{File, system_path_to_file};
use ruff_db::source::source_text;
use ruff_db::system::{InMemorySystem, MemoryFileSystem, SystemPath, SystemPathBuf, TestSystem};
use ruff_python_ast::PythonVersion;
use ty_project::metadata::options::{EnvironmentOptions, Options};
use ty_project::metadata::options::{AnalysisOptions, EnvironmentOptions, Options};
use ty_project::metadata::value::{RangedValue, RelativePathBuf};
use ty_project::watch::{ChangeEvent, ChangedKind};
use ty_project::{CheckMode, Db, ProjectDatabase, ProjectMetadata};
@@ -67,6 +67,7 @@ fn tomllib_path(file: &TestFile) -> SystemPathBuf {
SystemPathBuf::from("src").join(file.name())
}
#[expect(clippy::needless_update)]
fn setup_tomllib_case() -> Case {
let system = TestSystem::default();
let fs = system.memory_file_system().clone();
@@ -85,6 +86,10 @@ fn setup_tomllib_case() -> Case {
python_version: Some(RangedValue::cli(PythonVersion::PY312)),
..EnvironmentOptions::default()
}),
analysis: Some(AnalysisOptions {
respect_type_ignore_comments: Some(false),
..AnalysisOptions::default()
}),
..Options::default()
});
@@ -221,7 +226,7 @@ fn setup_micro_case(code: &str) -> Case {
let file_path = "src/test.py";
fs.write_file_all(
SystemPathBuf::from(file_path),
ruff_python_trivia::textwrap::dedent(code),
&*ruff_python_trivia::textwrap::dedent(code),
)
.unwrap();
@@ -557,6 +562,60 @@ fn benchmark_many_enum_members(criterion: &mut Criterion) {
});
}
fn benchmark_many_enum_members_2(criterion: &mut Criterion) {
const NUM_ENUM_MEMBERS: usize = 48;
setup_rayon();
let mut code = "\
from enum import Enum
from typing_extensions import assert_never
class E(Enum):
"
.to_string();
for i in 0..NUM_ENUM_MEMBERS {
writeln!(&mut code, " m{i} = {i}").ok();
}
code.push_str(
"
def method(self):
match self:",
);
for i in 0..NUM_ENUM_MEMBERS {
write!(
&mut code,
"
case E.m{i}:
pass"
)
.ok();
}
write!(
&mut code,
"
case _:
assert_never(self)"
)
.ok();
criterion.bench_function("ty_micro[many_enum_members_2]", |b| {
b.iter_batched_ref(
|| setup_micro_case(&code),
|case| {
let Case { db, .. } = case;
let result = db.check();
assert_eq!(result.len(), 0);
},
BatchSize::SmallInput,
);
});
}
struct ProjectBenchmark<'a> {
project: InstalledProject<'a>,
fs: MemoryFileSystem,
@@ -701,7 +760,7 @@ fn datetype(criterion: &mut Criterion) {
max_dep_date: "2025-07-04",
python_version: PythonVersion::PY313,
},
2,
4,
);
bench_project(&benchmark, criterion);
@@ -717,6 +776,7 @@ criterion_group!(
benchmark_complex_constrained_attributes_2,
benchmark_complex_constrained_attributes_3,
benchmark_many_enum_members,
benchmark_many_enum_members_2,
);
criterion_group!(project, anyio, attrs, hydra, datetype);
criterion_main!(check_file, micro, project);

View File

@@ -71,6 +71,8 @@ impl Display for Benchmark<'_> {
}
}
#[track_caller]
#[expect(clippy::cast_precision_loss)]
fn check_project(db: &ProjectDatabase, project_name: &str, max_diagnostics: usize) {
let result = db.check();
let diagnostics = result.len();
@@ -79,6 +81,12 @@ fn check_project(db: &ProjectDatabase, project_name: &str, max_diagnostics: usiz
diagnostics > 1 && diagnostics <= max_diagnostics,
"Expected between 1 and {max_diagnostics} diagnostics on project '{project_name}' but got {diagnostics}",
);
if (max_diagnostics - diagnostics) as f64 / max_diagnostics as f64 > 0.10 {
tracing::warn!(
"The expected diagnostics for project `{project_name}` can be reduced: expected {max_diagnostics} but got {diagnostics}"
);
}
}
static ALTAIR: Benchmark = Benchmark::new(
@@ -101,7 +109,7 @@ static ALTAIR: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
1000,
850,
);
static COLOUR_SCIENCE: Benchmark = Benchmark::new(
@@ -120,7 +128,7 @@ static COLOUR_SCIENCE: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY310,
},
1070,
350,
);
static FREQTRADE: Benchmark = Benchmark::new(
@@ -163,7 +171,7 @@ static PANDAS: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
4000,
3800,
);
static PYDANTIC: Benchmark = Benchmark::new(
@@ -181,7 +189,7 @@ static PYDANTIC: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY39,
},
7000,
3200,
);
static SYMPY: Benchmark = Benchmark::new(
@@ -194,7 +202,7 @@ static SYMPY: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
13116,
13400,
);
static TANJUN: Benchmark = Benchmark::new(
@@ -207,7 +215,7 @@ static TANJUN: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
320,
110,
);
static STATIC_FRAME: Benchmark = Benchmark::new(
@@ -223,7 +231,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
max_dep_date: "2025-08-09",
python_version: PythonVersion::PY311,
},
1100,
1657,
);
#[track_caller]

View File

@@ -1,3 +1,4 @@
use std::fmt::Formatter;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
@@ -49,3 +50,15 @@ impl CancellationToken {
self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
}
}
/// The operation was canceled by the provided [`CancellationToken`].
#[derive(Debug)]
pub struct Canceled;
impl std::error::Error for Canceled {}
impl std::fmt::Display for Canceled {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("operation was canceled")
}
}

View File

@@ -98,6 +98,44 @@ impl Diagnostic {
diag
}
/// Adds sub diagnostics that tell the user that this is a bug in ty
/// and asks them to open an issue on GitHub.
pub fn add_bug_sub_diagnostics(&mut self, url_encoded_title: &str) {
self.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
"This indicates a bug in ty.",
));
self.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
format_args!(
"If you could open an issue at https://github.com/astral-sh/ty/issues/new?title={url_encoded_title}, we'd be very appreciative!"
),
));
self.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
format!(
"Platform: {os} {arch}",
os = std::env::consts::OS,
arch = std::env::consts::ARCH
),
));
if let Some(version) = crate::program_version() {
self.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
format!("Version: {version}"),
));
}
self.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
format!(
"Args: {args:?}",
args = std::env::args().collect::<Vec<_>>()
),
));
}
/// Add an annotation to this diagnostic.
///
/// Annotations for a diagnostic are optional, but if any are added,
@@ -1019,6 +1057,13 @@ impl DiagnosticId {
matches!(self, DiagnosticId::Lint(_))
}
pub const fn as_lint(&self) -> Option<LintName> {
match self {
DiagnosticId::Lint(name) => Some(*name),
_ => None,
}
}
/// Returns `true` if this `DiagnosticId` represents a lint with the given name.
pub fn is_lint_named(&self, name: &str) -> bool {
matches!(self, DiagnosticId::Lint(self_name) if self_name == name)

View File

@@ -1284,7 +1284,7 @@ watermelon
let diag = env.err().primary("animals", "5", "5", "").build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -1308,7 +1308,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
warning[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -1328,7 +1328,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
info[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -1355,7 +1355,7 @@ watermelon
let diag = builder.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1374,7 +1374,7 @@ watermelon
let diag = builder.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1395,7 +1395,7 @@ watermelon
let diag = env.err().primary("non-ascii", "5", "5", "").build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> non-ascii:5:1
|
@@ -1414,7 +1414,7 @@ watermelon
let diag = env.err().primary("non-ascii", "2:4", "2:8", "").build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> non-ascii:2:2
|
@@ -1438,7 +1438,7 @@ watermelon
env.context(1);
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -1455,7 +1455,7 @@ watermelon
env.context(0);
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -1470,7 +1470,7 @@ watermelon
env.context(2);
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1487,7 +1487,7 @@ watermelon
env.context(2);
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:11:1
|
@@ -1504,7 +1504,7 @@ watermelon
env.context(200);
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -1537,7 +1537,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1581,7 +1581,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1606,7 +1606,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1634,7 +1634,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1662,7 +1662,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1687,7 +1687,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1718,7 +1718,7 @@ watermelon
// window.
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1756,7 +1756,7 @@ watermelon
let diag = env.err().primary("spacey-animals", "8", "8", "").build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> spacey-animals:8:1
|
@@ -1773,7 +1773,7 @@ watermelon
let diag = env.err().primary("spacey-animals", "12", "12", "").build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> spacey-animals:12:1
|
@@ -1791,7 +1791,7 @@ watermelon
let diag = env.err().primary("spacey-animals", "13", "13", "").build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> spacey-animals:13:1
|
@@ -1831,7 +1831,7 @@ watermelon
// instead of special casing the snippet assembly.
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> spacey-animals:3:1
|
@@ -1860,7 +1860,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:3:1
|
@@ -1897,7 +1897,7 @@ watermelon
);
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:3:1
|
@@ -1934,7 +1934,7 @@ watermelon
);
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:3:1
|
@@ -1962,7 +1962,7 @@ watermelon
diag.sub(env.sub_warn().primary("fruits", "3", "3", "").build());
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:3:1
|
@@ -1998,7 +1998,7 @@ watermelon
diag.sub(env.sub_warn().primary("animals", "11", "11", "").build());
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:3:1
|
@@ -2037,7 +2037,7 @@ watermelon
diag.sub(env.sub_warn().primary("fruits", "3", "3", "").build());
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:3:1
|
@@ -2085,7 +2085,7 @@ watermelon
diag.sub(env.sub_warn().secondary("animals", "3", "3", "").build());
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:3:1
|
@@ -2121,7 +2121,7 @@ watermelon
let diag = env.err().primary("animals", "5", "6", "").build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -2144,7 +2144,7 @@ watermelon
let diag = env.err().primary("animals", "5", "7:0", "").build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -2164,7 +2164,7 @@ watermelon
let diag = env.err().primary("animals", "5", "7:1", "").build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -2184,7 +2184,7 @@ watermelon
let diag = env.err().primary("animals", "5:3", "8:8", "").build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:5:4
|
@@ -2206,7 +2206,7 @@ watermelon
let diag = env.err().secondary("animals", "5:3", "8:8", "").build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:5:4
|
@@ -2238,7 +2238,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:4:1
|
@@ -2267,7 +2267,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:4:1
|
@@ -2298,7 +2298,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -2333,7 +2333,7 @@ watermelon
// better using only ASCII art.
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -2361,7 +2361,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -2393,7 +2393,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:5:3
|
@@ -2415,7 +2415,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:5:3
|
@@ -2448,7 +2448,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:8:1
|
@@ -2488,7 +2488,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -2532,7 +2532,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> fruits:1:1
|
@@ -2567,7 +2567,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@r"
@"
error[test-diagnostic]: main diagnostic message
--> animals:11:1
|

View File

@@ -137,7 +137,7 @@ mod tests {
#[test]
fn output() {
let (env, diagnostics) = create_diagnostics(DiagnosticFormat::Concise);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @"
fib.py:1:8: error[unused-import] `os` imported but unused
fib.py:6:5: error[unused-variable] Local variable `x` is assigned to but never used
undef.py:1:4: error[undefined-name] Undefined name `a`
@@ -150,7 +150,7 @@ mod tests {
env.hide_severity(true);
env.show_fix_status(true);
env.fix_applicability(Applicability::DisplayOnly);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @"
fib.py:1:8: F401 [*] `os` imported but unused
fib.py:6:5: F841 [*] Local variable `x` is assigned to but never used
undef.py:1:4: F821 Undefined name `a`
@@ -164,7 +164,7 @@ mod tests {
env.show_fix_status(true);
env.fix_applicability(Applicability::DisplayOnly);
env.preview(true);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @"
fib.py:1:8: F401 [*] `os` imported but unused
fib.py:6:5: F841 [*] Local variable `x` is assigned to but never used
undef.py:1:4: F821 Undefined name `a`
@@ -177,7 +177,7 @@ mod tests {
env.hide_severity(true);
env.show_fix_status(true);
env.fix_applicability(Applicability::DisplayOnly);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @"
syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
");
@@ -186,7 +186,7 @@ mod tests {
#[test]
fn syntax_errors() {
let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Concise);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @"
syntax_errors.py:1:15: error[invalid-syntax] Expected one or more symbol names after import
syntax_errors.py:3:12: error[invalid-syntax] Expected ')', found newline
");
@@ -195,7 +195,7 @@ mod tests {
#[test]
fn notebook_output() {
let (env, diagnostics) = create_notebook_diagnostics(DiagnosticFormat::Concise);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @"
notebook.ipynb:cell 1:2:8: error[unused-import] `os` imported but unused
notebook.ipynb:cell 2:2:8: error[unused-import] `math` imported but unused
notebook.ipynb:cell 3:4:5: error[unused-variable] Local variable `x` is assigned to but never used

View File

@@ -14,6 +14,7 @@ use crate::diagnostic::{Span, UnifiedFile};
use crate::file_revision::FileRevision;
use crate::files::file_root::FileRoots;
use crate::files::private::FileStatus;
use crate::source::SourceText;
use crate::system::{SystemPath, SystemPathBuf, SystemVirtualPath, SystemVirtualPathBuf};
use crate::vendored::{VendoredPath, VendoredPathBuf};
use crate::{Db, FxDashMap, vendored};
@@ -323,6 +324,17 @@ pub struct File {
/// the file has been deleted is to change the status to `Deleted`.
#[default]
status: FileStatus,
/// Overrides the result of [`source_text`](crate::source::source_text).
///
/// This is useful when running queries after modifying a file's content but
/// before the content is written to disk. For example, to verify that the applied fixes
/// didn't introduce any new errors.
///
/// The override gets automatically removed the next time the file changes.
#[default]
#[returns(ref)]
pub source_text_override: Option<SourceText>,
}
// The Salsa heap is tracked separately.
@@ -444,20 +456,28 @@ impl File {
_ => (FileStatus::NotFound, FileRevision::zero(), None),
};
let mut clear_override = false;
if file.status(db) != status {
tracing::debug!("Updating the status of `{}`", file.path(db));
file.set_status(db).to(status);
clear_override = true;
}
if file.revision(db) != revision {
tracing::debug!("Updating the revision of `{}`", file.path(db));
file.set_revision(db).to(revision);
clear_override = true;
}
if file.permissions(db) != permission {
tracing::debug!("Updating the permissions of `{}`", file.path(db));
file.set_permissions(db).to(permission);
}
if clear_override && file.source_text_override(db).is_some() {
file.set_source_text_override(db).to(None);
}
}
/// Returns `true` if the file exists.
@@ -526,7 +546,7 @@ impl VirtualFile {
}
/// Increments the revision of the underlying [`File`].
fn sync(&self, db: &mut dyn Db) {
pub fn sync(&self, db: &mut dyn Db) {
let file = self.0;
tracing::debug!("Updating the revision of `{}`", file.path(db));
let current_revision = file.revision(db);

View File

@@ -85,6 +85,13 @@ pub fn max_parallelism() -> NonZeroUsize {
})
}
// Use a reasonably large stack size to avoid running into stack overflows too easily. The
// size was chosen in such a way as to still be able to handle large expressions involving
// binary operators (x + x + … + x) both during the AST walk in semantic index building as
// well as during type checking. Using this stack size, we can handle handle expressions
// that are several times larger than the corresponding limits in existing type checkers.
pub const STACK_SIZE: usize = 16 * 1024 * 1024;
/// Trait for types that can provide Rust documentation.
///
/// Use `derive(RustDoc)` to automatically implement this trait for types that have a static string documentation.

View File

@@ -1,6 +1,8 @@
use std::borrow::Cow;
use std::ops::Deref;
use std::sync::Arc;
use ruff_diagnostics::SourceMap;
use ruff_notebook::Notebook;
use ruff_python_ast::PySourceType;
use ruff_source_file::LineIndex;
@@ -16,6 +18,10 @@ pub fn source_text(db: &dyn Db, file: File) -> SourceText {
let _span = tracing::trace_span!("source_text", file = %path).entered();
let mut read_error = None;
if let Some(source) = file.source_text_override(db) {
return source.clone();
}
let kind = if is_notebook(db.system(), path) {
file.read_to_notebook(db)
.unwrap_or_else(|error| {
@@ -90,6 +96,45 @@ impl SourceText {
pub fn read_error(&self) -> Option<&SourceTextError> {
self.inner.read_error.as_ref()
}
/// Returns a new instance for this file with the updated source text (Python code).
///
/// Uses the `source_map` to preserve the cell-boundaries.
#[must_use]
pub fn with_text(&self, new_text: String, source_map: &SourceMap) -> Self {
let new_kind = match &self.inner.kind {
SourceTextKind::Text(_) => SourceTextKind::Text(new_text),
SourceTextKind::Notebook { notebook } => {
let mut new_notebook = notebook.as_ref().clone();
new_notebook.update(source_map, new_text);
SourceTextKind::Notebook {
notebook: new_notebook.into(),
}
}
};
Self {
inner: Arc::new(SourceTextInner {
kind: new_kind,
read_error: self.inner.read_error.clone(),
}),
}
}
pub fn to_bytes(&self) -> Cow<'_, [u8]> {
match &self.inner.kind {
SourceTextKind::Text(source) => Cow::Borrowed(source.as_bytes()),
SourceTextKind::Notebook { notebook } => {
let mut output: Vec<u8> = Vec::new();
notebook
.write(&mut output)
.expect("writing to a Vec should never fail");
Cow::Owned(output)
}
}
}
}
impl Deref for SourceText {
@@ -117,13 +162,13 @@ impl std::fmt::Debug for SourceText {
}
}
#[derive(Eq, PartialEq, get_size2::GetSize)]
#[derive(Eq, PartialEq, get_size2::GetSize, Clone)]
struct SourceTextInner {
kind: SourceTextKind,
read_error: Option<SourceTextError>,
}
#[derive(Eq, PartialEq, get_size2::GetSize)]
#[derive(Eq, PartialEq, get_size2::GetSize, Clone)]
enum SourceTextKind {
Text(String),
Notebook {

View File

@@ -271,7 +271,12 @@ pub trait WritableSystem: System {
fn create_new_file(&self, path: &SystemPath) -> Result<()>;
/// Writes the given content to the file at the given path.
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()>;
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
self.write_file_bytes(path, content.as_bytes())
}
/// Writes the given content to the file at the given path.
fn write_file_bytes(&self, path: &SystemPath, content: &[u8]) -> Result<()>;
/// Creates a directory at `path` as well as any intermediate directories.
fn create_directory_all(&self, path: &SystemPath) -> Result<()>;
@@ -311,6 +316,8 @@ pub trait WritableSystem: System {
Ok(Some(cache_path))
}
fn dyn_clone(&self) -> Box<dyn WritableSystem>;
}
#[derive(Clone, Debug, Eq, PartialEq)]

View File

@@ -122,7 +122,9 @@ impl MemoryFileSystem {
let entry = by_path.get(&normalized).ok_or_else(not_found)?;
match entry {
Entry::File(file) => Ok(file.content.clone()),
Entry::File(file) => {
String::from_utf8(file.content.to_vec()).map_err(|_| invalid_utf8())
}
Entry::Directory(_) => Err(is_a_directory()),
}
}
@@ -139,7 +141,7 @@ impl MemoryFileSystem {
.get(&path.as_ref().to_path_buf())
.ok_or_else(not_found)?;
Ok(file.content.clone())
String::from_utf8(file.content.to_vec()).map_err(|_| invalid_utf8())
}
pub fn exists(&self, path: &SystemPath) -> bool {
@@ -161,7 +163,7 @@ impl MemoryFileSystem {
match by_path.entry(normalized) {
btree_map::Entry::Vacant(entry) => {
entry.insert(Entry::File(File {
content: String::new(),
content: Box::default(),
last_modified: file_time_now(),
}));
@@ -177,13 +179,17 @@ impl MemoryFileSystem {
/// Stores a new file in the file system.
///
/// The operation overrides the content for an existing file with the same normalized `path`.
pub fn write_file(&self, path: impl AsRef<SystemPath>, content: impl ToString) -> Result<()> {
pub fn write_file(
&self,
path: impl AsRef<SystemPath>,
content: impl AsRef<[u8]>,
) -> Result<()> {
let mut by_path = self.inner.by_path.write().unwrap();
let normalized = self.normalize_path(path.as_ref());
let file = get_or_create_file(&mut by_path, &normalized)?;
file.content = content.to_string();
file.content = content.as_ref().to_vec().into_boxed_slice();
file.last_modified = file_time_now();
Ok(())
@@ -214,7 +220,7 @@ impl MemoryFileSystem {
pub fn write_file_all(
&self,
path: impl AsRef<SystemPath>,
content: impl ToString,
content: impl AsRef<[u8]>,
) -> Result<()> {
let path = path.as_ref();
@@ -228,19 +234,24 @@ impl MemoryFileSystem {
/// Stores a new virtual file in the file system.
///
/// The operation overrides the content for an existing virtual file with the same `path`.
pub fn write_virtual_file(&self, path: impl AsRef<SystemVirtualPath>, content: impl ToString) {
pub fn write_virtual_file(
&self,
path: impl AsRef<SystemVirtualPath>,
content: impl AsRef<[u8]>,
) {
let path = path.as_ref();
let mut virtual_files = self.inner.virtual_files.write().unwrap();
let content = content.as_ref().to_vec().into_boxed_slice();
match virtual_files.entry(path.to_path_buf()) {
std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(File {
content: content.to_string(),
content,
last_modified: file_time_now(),
});
}
std::collections::hash_map::Entry::Occupied(mut entry) => {
entry.get_mut().content = content.to_string();
entry.get_mut().content = content;
}
}
}
@@ -468,7 +479,7 @@ impl Entry {
#[derive(Debug)]
struct File {
content: String,
content: Box<[u8]>,
last_modified: FileTime,
}
@@ -497,6 +508,13 @@ fn directory_not_empty() -> std::io::Error {
std::io::Error::other("directory not empty")
}
fn invalid_utf8() -> std::io::Error {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"stream did not contain valid UTF-8",
)
}
fn create_dir_all(
paths: &mut RwLockWriteGuard<BTreeMap<Utf8PathBuf, Entry>>,
normalized: &Utf8Path,
@@ -533,7 +551,7 @@ fn get_or_create_file<'a>(
let entry = paths.entry(normalized.to_path_buf()).or_insert_with(|| {
Entry::File(File {
content: String::new(),
content: Box::default(),
last_modified: file_time_now(),
})
});
@@ -844,7 +862,7 @@ mod tests {
let fs = with_files(["c.py"]);
let error = fs
.write_file(SystemPath::new("a/b.py"), "content".to_string())
.write_file(SystemPath::new("a/b.py"), "content")
.unwrap_err();
assert_eq!(error.kind(), ErrorKind::NotFound);
@@ -855,7 +873,7 @@ mod tests {
let fs = with_files(["a/b.py"]);
let error = fs
.write_file_all(SystemPath::new("a/b.py/c"), "content".to_string())
.write_file_all(SystemPath::new("a/b.py/c"), "content")
.unwrap_err();
assert_eq!(error.kind(), ErrorKind::Other);
@@ -878,7 +896,7 @@ mod tests {
let fs = MemoryFileSystem::new();
let path = SystemPath::new("a.py");
fs.write_file_all(path, "Test content".to_string())?;
fs.write_file_all(path, "Test content")?;
assert_eq!(fs.read_to_string(path)?, "Test content");
@@ -915,9 +933,7 @@ mod tests {
fs.create_directory_all("a")?;
let error = fs
.write_file(SystemPath::new("a"), "content".to_string())
.unwrap_err();
let error = fs.write_file(SystemPath::new("a"), "content").unwrap_err();
assert_eq!(error.kind(), ErrorKind::Other);

View File

@@ -361,13 +361,17 @@ impl WritableSystem for OsSystem {
std::fs::File::create_new(path).map(drop)
}
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
fn write_file_bytes(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
std::fs::write(path.as_std_path(), content)
}
fn create_directory_all(&self, path: &SystemPath) -> Result<()> {
std::fs::create_dir_all(path.as_std_path())
}
fn dyn_clone(&self) -> Box<dyn WritableSystem> {
Box::new(self.clone())
}
}
impl Default for OsSystem {

View File

@@ -205,13 +205,17 @@ impl WritableSystem for TestSystem {
self.system().create_new_file(path)
}
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
self.system().write_file(path, content)
fn write_file_bytes(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
self.system().write_file_bytes(path, content)
}
fn create_directory_all(&self, path: &SystemPath) -> Result<()> {
self.system().create_directory_all(path)
}
fn dyn_clone(&self) -> Box<dyn WritableSystem> {
Box::new(self.clone())
}
}
/// Extension trait for databases that use a [`WritableSystem`].
@@ -283,7 +287,11 @@ pub trait DbWithTestSystem: Db + Sized {
///
/// ## Panics
/// If the db isn't using the [`InMemorySystem`].
fn write_virtual_file(&mut self, path: impl AsRef<SystemVirtualPath>, content: impl ToString) {
fn write_virtual_file(
&mut self,
path: impl AsRef<SystemVirtualPath>,
content: impl AsRef<[u8]>,
) {
let path = path.as_ref();
self.test_system()
.memory_file_system()
@@ -322,23 +330,23 @@ where
}
}
#[derive(Default, Debug)]
#[derive(Clone, Default, Debug)]
pub struct InMemorySystem {
user_config_directory: Mutex<Option<SystemPathBuf>>,
user_config_directory: Arc<Mutex<Option<SystemPathBuf>>>,
memory_fs: MemoryFileSystem,
}
impl InMemorySystem {
pub fn new(cwd: SystemPathBuf) -> Self {
Self {
user_config_directory: Mutex::new(None),
user_config_directory: Mutex::new(None).into(),
memory_fs: MemoryFileSystem::with_current_directory(cwd),
}
}
pub fn from_memory_fs(memory_fs: MemoryFileSystem) -> Self {
Self {
user_config_directory: Mutex::new(None),
user_config_directory: Mutex::new(None).into(),
memory_fs,
}
}
@@ -440,10 +448,7 @@ impl System for InMemorySystem {
}
fn dyn_clone(&self) -> Box<dyn System> {
Box::new(Self {
user_config_directory: Mutex::new(self.user_config_directory.lock().unwrap().clone()),
memory_fs: self.memory_fs.clone(),
})
Box::new(self.clone())
}
}
@@ -452,11 +457,15 @@ impl WritableSystem for InMemorySystem {
self.memory_fs.create_new_file(path)
}
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
fn write_file_bytes(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
self.memory_fs.write_file(path, content)
}
fn create_directory_all(&self, path: &SystemPath) -> Result<()> {
self.memory_fs.create_directory_all(path)
}
fn dyn_clone(&self) -> Box<dyn WritableSystem> {
Box::new(self.clone())
}
}

View File

@@ -619,19 +619,19 @@ pub(crate) mod tests {
fn read_directory_stdlib() {
let mock_typeshed = mock_typeshed();
assert_snapshot!(readdir_snapshot(&mock_typeshed, "stdlib"), @r"
assert_snapshot!(readdir_snapshot(&mock_typeshed, "stdlib"), @"
vendored://stdlib/asyncio/
vendored://stdlib/functools.pyi
");
assert_snapshot!(readdir_snapshot(&mock_typeshed, "stdlib/"), @r"
assert_snapshot!(readdir_snapshot(&mock_typeshed, "stdlib/"), @"
vendored://stdlib/asyncio/
vendored://stdlib/functools.pyi
");
assert_snapshot!(readdir_snapshot(&mock_typeshed, "./stdlib"), @r"
assert_snapshot!(readdir_snapshot(&mock_typeshed, "./stdlib"), @"
vendored://stdlib/asyncio/
vendored://stdlib/functools.pyi
");
assert_snapshot!(readdir_snapshot(&mock_typeshed, "./stdlib/"), @r"
assert_snapshot!(readdir_snapshot(&mock_typeshed, "./stdlib/"), @"
vendored://stdlib/asyncio/
vendored://stdlib/functools.pyi
");

View File

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

View File

@@ -1,6 +1,5 @@
---
source: crates/ruff_linter/src/comments/shebang.rs
expression: "ShebangDirective::try_extract(source)"
snapshot_kind: text
---
None

View File

@@ -1,6 +1,5 @@
---
source: crates/ruff_linter/src/comments/shebang.rs
expression: "ShebangDirective::try_extract(source)"
snapshot_kind: text
---
None

View File

@@ -1,7 +1,6 @@
---
source: crates/ruff_linter/src/comments/shebang.rs
expression: "ShebangDirective::try_extract(source)"
snapshot_kind: text
---
Some(
ShebangDirective(

View File

@@ -1,7 +1,6 @@
---
source: crates/ruff_linter/src/comments/shebang.rs
expression: "ShebangDirective::try_extract(source)"
snapshot_kind: text
---
Some(
ShebangDirective(

View File

@@ -1,6 +1,5 @@
---
source: crates/ruff_linter/src/comments/shebang.rs
expression: "ShebangDirective::try_extract(source)"
snapshot_kind: text
---
None

View File

@@ -26,6 +26,7 @@ use crate::doc_lines::{doc_lines_from_ast, doc_lines_from_tokens};
use crate::fix::{FixResult, fix_file};
use crate::noqa::add_noqa;
use crate::package::PackageRoot;
use crate::preview::is_py315_support_enabled;
use crate::registry::Rule;
#[cfg(any(feature = "test-rules", test))]
use crate::rules::ruff::rules::test_rules::{self, TEST_RULES, TestRule};
@@ -33,7 +34,7 @@ use crate::settings::types::UnsafeFixes;
use crate::settings::{LinterSettings, TargetVersion, flags};
use crate::source_kind::SourceKind;
use crate::suppression::Suppressions;
use crate::{Locator, directives, fs};
use crate::{Locator, directives, fs, warn_user_once};
pub(crate) mod float;
@@ -450,6 +451,14 @@ pub fn lint_only(
) -> LinterResult {
let target_version = settings.resolve_target_version(path);
if matches!(target_version.linter_version(), PythonVersion::PY315)
&& !is_py315_support_enabled(settings)
{
warn_user_once!(
"Support for Python 3.15 is under development and may be unstable. Enable `preview` to remove this warning."
);
}
let parsed = source.into_parsed(source_kind, source_type, target_version.parser_version());
// Map row and column locations to byte slices (lazily).
@@ -555,6 +564,14 @@ pub fn lint_fix<'a>(
let target_version = settings.resolve_target_version(path);
if matches!(target_version.linter_version(), PythonVersion::PY315)
&& !is_py315_support_enabled(settings)
{
warn_user_once!(
"Support for Python 3.15 is under development and may be unstable. Enable `preview` to remove this warning."
);
}
// Continuously fix until the source code stabilizes.
loop {
// Parse once.

View File

@@ -1,7 +1,6 @@
---
source: crates/ruff_linter/src/message/grouped.rs
expression: content
snapshot_kind: text
---
fib.py:
1:8 F401 `os` imported but unused

View File

@@ -1326,7 +1326,7 @@ mod tests {
fn noqa_all() {
let source = "# noqa";
let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Ok(
Some(
NoqaLexerOutput {
@@ -1347,7 +1347,7 @@ mod tests {
fn noqa_no_code() {
let source = "# noqa:";
let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Err(
MissingCodes,
)
@@ -1359,7 +1359,7 @@ mod tests {
fn noqa_no_code_invalid_suffix() {
let source = "# noqa: foo";
let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Err(
MissingCodes,
)
@@ -1371,7 +1371,7 @@ mod tests {
fn noqa_no_code_trailing_content() {
let source = "# noqa: # Foo";
let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Err(
MissingCodes,
)
@@ -1383,7 +1383,7 @@ mod tests {
fn malformed_code_1() {
let source = "# noqa: F";
let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Err(
MissingCodes,
)
@@ -1422,7 +1422,7 @@ mod tests {
fn malformed_code_3() {
let source = "# noqa: RUF001F";
let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Err(
InvalidCodeSuffix,
)
@@ -1492,7 +1492,7 @@ mod tests {
fn noqa_all_case_insensitive() {
let source = "# NOQA";
let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Ok(
Some(
NoqaLexerOutput {
@@ -1625,7 +1625,7 @@ mod tests {
fn noqa_all_no_space() {
let source = "#noqa";
let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Ok(
Some(
NoqaLexerOutput {
@@ -1704,7 +1704,7 @@ mod tests {
fn noqa_all_multi_space() {
let source = "# noqa";
let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Ok(
Some(
NoqaLexerOutput {
@@ -1837,7 +1837,7 @@ mod tests {
fn noqa_all_leading_comment() {
let source = "# Some comment describing the noqa # noqa";
let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Ok(
Some(
NoqaLexerOutput {
@@ -1916,7 +1916,7 @@ mod tests {
fn noqa_all_trailing_comment() {
let source = "# noqa # Some comment describing the noqa";
let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Ok(
Some(
NoqaLexerOutput {
@@ -1995,7 +1995,7 @@ mod tests {
fn noqa_invalid_codes() {
let source = "# noqa: unused-import, F401, some other code";
let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Err(
MissingCodes,
)
@@ -2139,7 +2139,7 @@ mod tests {
fn noqa_code_invalid_code_suffix() {
let source = "# noqa: F401abc";
let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Err(
InvalidCodeSuffix,
)
@@ -2151,7 +2151,7 @@ mod tests {
fn noqa_invalid_suffix() {
let source = "# noqa[F401]";
let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Err(
InvalidSuffix,
)
@@ -2163,7 +2163,7 @@ mod tests {
fn flake8_exemption_all() {
let source = "# flake8: noqa";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Ok(
Some(
NoqaLexerOutput {
@@ -2184,7 +2184,7 @@ mod tests {
fn flake8_noqa_no_code() {
let source = "# flake8: noqa:";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Err(
MissingCodes,
)
@@ -2196,7 +2196,7 @@ mod tests {
fn flake8_noqa_no_code_invalid_suffix() {
let source = "# flake8: noqa: foo";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Err(
MissingCodes,
)
@@ -2208,7 +2208,7 @@ mod tests {
fn flake8_noqa_no_code_trailing_content() {
let source = "# flake8: noqa: # Foo";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Err(
MissingCodes,
)
@@ -2220,7 +2220,7 @@ mod tests {
fn flake8_malformed_code_1() {
let source = "# flake8: noqa: F";
let directive = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Err(
MissingCodes,
)
@@ -2259,7 +2259,7 @@ mod tests {
fn flake8_malformed_code_3() {
let source = "# flake8: noqa: RUF001F";
let directive = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Err(
InvalidCodeSuffix,
)
@@ -2271,7 +2271,7 @@ mod tests {
fn ruff_exemption_all() {
let source = "# ruff: noqa";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Ok(
Some(
NoqaLexerOutput {
@@ -2292,7 +2292,7 @@ mod tests {
fn ruff_noqa_no_code() {
let source = "# ruff: noqa:";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Err(
MissingCodes,
)
@@ -2304,7 +2304,7 @@ mod tests {
fn ruff_noqa_no_code_invalid_suffix() {
let source = "# ruff: noqa: foo";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Err(
MissingCodes,
)
@@ -2316,7 +2316,7 @@ mod tests {
fn ruff_noqa_no_code_trailing_content() {
let source = "# ruff: noqa: # Foo";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Err(
MissingCodes,
)
@@ -2328,7 +2328,7 @@ mod tests {
fn ruff_malformed_code_1() {
let source = "# ruff: noqa: F";
let directive = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Err(
MissingCodes,
)
@@ -2367,7 +2367,7 @@ mod tests {
fn ruff_malformed_code_3() {
let source = "# ruff: noqa: RUF001F";
let directive = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(directive, @r"
assert_debug_snapshot!(directive, @"
Err(
InvalidCodeSuffix,
)
@@ -2379,7 +2379,7 @@ mod tests {
fn flake8_exemption_all_no_space() {
let source = "#flake8:noqa";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Ok(
Some(
NoqaLexerOutput {
@@ -2400,7 +2400,7 @@ mod tests {
fn ruff_exemption_all_no_space() {
let source = "#ruff:noqa";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Ok(
Some(
NoqaLexerOutput {
@@ -2619,7 +2619,7 @@ mod tests {
fn ruff_exemption_invalid_code_suffix() {
let source = "# ruff: noqa: F401abc";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Err(
InvalidCodeSuffix,
)
@@ -2685,7 +2685,7 @@ mod tests {
fn ruff_exemption_all_leading_comment() {
let source = "# Leading comment # ruff: noqa";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Ok(
Some(
NoqaLexerOutput {
@@ -2706,7 +2706,7 @@ mod tests {
fn ruff_exemption_all_trailing_comment() {
let source = "# ruff: noqa # Trailing comment";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Ok(
Some(
NoqaLexerOutput {
@@ -2754,7 +2754,7 @@ mod tests {
fn ruff_exemption_all_trailing_comment_no_space() {
let source = "# ruff: noqa# Trailing comment";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Ok(
Some(
NoqaLexerOutput {
@@ -2775,7 +2775,7 @@ mod tests {
fn ruff_exemption_all_trailing_comment_no_hash() {
let source = "# ruff: noqa Trailing comment";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Ok(
Some(
NoqaLexerOutput {
@@ -2823,7 +2823,7 @@ mod tests {
fn flake8_exemption_all_case_insensitive() {
let source = "# flake8: NoQa";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Ok(
Some(
NoqaLexerOutput {
@@ -2844,7 +2844,7 @@ mod tests {
fn ruff_exemption_all_case_insensitive() {
let source = "# ruff: NoQa";
let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source);
assert_debug_snapshot!(exemption, @r"
assert_debug_snapshot!(exemption, @"
Ok(
Some(
NoqaLexerOutput {

View File

@@ -296,3 +296,8 @@ pub(crate) const fn is_s310_resolve_string_literal_bindings_enabled(
pub(crate) const fn is_range_suppressions_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/22419
pub(crate) const fn is_py315_support_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_annotations/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_annotations/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_comprehensions/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_copyright/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_copyright/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_copyright/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_copyright/mod.rs
snapshot_kind: text
---

View File

@@ -1,5 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_copyright/mod.rs
snapshot_kind: text
---

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