Compare commits

...

234 Commits

Author SHA1 Message Date
Alex Waygood
288ade8381 [ty] Early return in Type::has_relation_to_impl for Type variants that are always mutually disjoint 2026-01-05 18:44:34 +00: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
Alex Waygood
e1439beab2 [ty] Use UnionType helper methods more consistently (#22357) 2026-01-03 14:19:06 +00:00
Felix Scherz
fd86e699b5 [ty] narrow TypedDict unions with not in (#22349)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2026-01-03 13:12:57 +00:00
Alex Waygood
d0f841bff2 Add help: subdiagnostics for several Ruff rules that can sometimes appear to disagree with ty (#22331) 2026-01-02 22:10:39 +00:00
Alex Waygood
74978cfff2 Error on unused ty: ignore comments when dogfooding ty on our own scripts (#22347) 2026-01-02 20:27:09 +00:00
Alex Waygood
10a417aaf6 [ty] Specify heap_size for SynthesizedTypedDictType (#22345) 2026-01-02 20:09:35 +00:00
Matthew Mckee
a2e0ff57c3 Run cargo sort (#22310) 2026-01-02 19:58:15 +00:00
Nikolas Hearp
0804030ee9 [pylint] Ignore identical members (PLR1714) (#22220)
## Summary

This PR closes #21692. `PLR1714` will no longer flag if all members are
identical. I iterate through the equality comparisons and if they are
all equal the rule does not flag.

## Test Plan

Additional tests were added with identical members.
2026-01-02 12:56:17 -05:00
Alex Waygood
26230b1ed3 [ty] Use IntersectionType::from_elements more (#22329) 2026-01-01 15:01:00 +00:00
github-actions[bot]
295ae836fd [ty] Sync vendored typeshed stubs (#22324)
Co-authored-by: typeshedbot <>
2026-01-01 02:59:55 +00:00
Alex Waygood
9677364847 Bump docstring-adder pin (#22323) 2026-01-01 02:44:24 +00:00
github-actions[bot]
8e45bac3c1 [ty] Sync vendored typeshed stubs (#22321)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2026-01-01 01:29:12 +00:00
Alex Waygood
7366a9e951 Bump docstring-adder pin (#22319) 2025-12-31 22:37:53 +00:00
Brent Westbrook
15aa74206e [pylint] Improve diagnostic range for PLC0206 (#22312)
Summary
--

This PR fixes #14900 by:

- Restricting the diagnostic range from the whole `for` loop to only the
`target in iter` part
- Adding secondary annotations to each use of the `dict[key]` accesses
- Adding a `fix_title` suggesting to use `for key in dict.items()`

I thought this approach sounded slightly nicer than the alternative of
renaming the rule to focus on each indexing operation mentioned in
https://github.com/astral-sh/ruff/issues/14900#issuecomment-2543923625,
but I don't feel too strongly. This was easy to implement with our new
diagnostic infrastructure too.

This produces an example annotation like this:

```
PLC0206 Extracting value from dictionary without calling `.items()`
  --> dict_index_missing_items.py:59:5
   |
58 | # A case with multiple uses of the value to show off the secondary annotations
59 | for instrument in ORCHESTRA:
   |     ^^^^^^^^^^^^^^^^^^^^^^^
60 |     data = json.dumps(
61 |         {
62 |             "instrument": instrument,
63 |             "section": ORCHESTRA[instrument],
   |                        ---------------------
64 |         }
65 |     )
66 |
67 |     print(f"saving data for {instrument} in {ORCHESTRA[instrument]}")
   |                                              ---------------------
68 |
69 |     with open(f"{instrument}/{ORCHESTRA[instrument]}.txt", "w") as f:
   |                               ---------------------
70 |         f.write(data)
   |
help: Use `for instrument, value in ORCHESTRA.items()` instead
```

which I think is a big improvement over:

```
PLC0206 Extracting value from dictionary without calling `.items()`
  --> dict_index_missing_items.py:59:1
   |
58 |   # A case with multiple uses of the value to show off the secondary annotations
59 | / for instrument in ORCHESTRA:
60 | |     data = json.dumps(
61 | |         {
62 | |             "instrument": instrument,
63 | |             "section": ORCHESTRA[instrument],
64 | |         }
65 | |     )
66 | |
67 | |     print(f"saving data for {instrument} in {ORCHESTRA[instrument]}")
68 | |
69 | |     with open(f"{instrument}/{ORCHESTRA[instrument]}.txt", "w") as f:
70 | |         f.write(data)
   | |_____________________^
   |
```

The secondary annotation feels a bit bare without a message, but I
thought it
might be too busy to include one. Something like `value extracted here`
or
`indexed here` might work if we do want to include a brief message.

To avoid collecting a `Vec` of annotation ranges, I added a `&Checker`
to the
rule's visitor to emit diagnostics as we go instead of at the end.

Test Plan
--

Existing tests, plus a new case showing off multiple secondary
annotations
2025-12-31 13:54:58 -05:00
ValdonVitijaa
77c2f4c6cb [flake8-unused-arguments] Mark **kwargs in TypeVar as used (ARG001) (#22214)
## Summary

Fixes false positive in ARG001 when `**kwargs` is passed to
`typing.TypeVar`

Closes #22178

When `**kwargs` is used in a `typing.TypeVar` call, the checker was not
recognizing it as a usage, leading to false positive "unused function
argument" warnings.

### Root Cause

In the AST, keyword arguments are represented by the `Keyword` struct
with an `arg` field of type `Option<Identifier>`:
- Named keywords like `bound=int` have `arg = Some("bound")`
- Dictionary unpacking like `**kwargs` has `arg = None`

The existing code only handled the `Some(id)` case, never visiting the
expression when `arg` was `None`, so `**kwargs` was never marked as
used.

### Changes

Added an `else` branch to handle `**kwargs` unpacking by calling
`visit_non_type_definition(value)` when `arg` is `None`. This ensures
the `kwargs` variable is properly visited and marked as used by the
semantic model.

## Test Plan

Tested with the following code:

```python
import typing

def f(
    *args: object,
    default: object = None,
    **kwargs: object,
) -> None:
    typing.TypeVar(*args, **kwargs)
```

Before :

`ARG001 Unused function argument: kwargs
`

After : 

`All checks passed!`

Run the example with the following command(from the root of ruff and
please change the path to the module that contains the code example):

`cargo run -p ruff -- check /path/to/file.py --isolated --select=ARG
--no-cache`
2025-12-31 11:07:56 -05:00
Rob Hand
758926eecd [ty] Add blurb for newer crates to `ty/CONTIBUTING.md (#22309)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-12-31 07:58:33 +00:00
Matthew Mckee
6433b88ffa Clean up pre-commit config (#22311) 2025-12-31 08:36:20 +01:00
Charlie Marsh
f619783066 [ty] Treat __setattr__ as fallback-only (#22014)
## Summary

Closes https://github.com/astral-sh/ty/issues/1460.
2025-12-30 19:01:10 -05:00
Ibraheem Ahmed
ff05428ce6 [ty] Subtyping for bidirectional inference (#21930)
## Summary

Supersedes https://github.com/astral-sh/ruff/pull/21747. This version
uses the constraint solver directly, which means we should benefit from
constraint solver improvements for free.

Resolves https://github.com/astral-sh/ty/issues/1576.
2025-12-30 17:03:20 -05:00
Matthew Mckee
4f2529f353 [ty] Fix typo in cli docs for respect_ignore_files arg (#22308) 2025-12-30 21:38:50 +01:00
Rob Hand
6f9ea73ac9 [ty] Remove TY_MAX_PARALLELISM as conformance runs no longer panic (#22307) 2025-12-30 20:42:59 +01:00
Charlie Marsh
12dd27da52 [ty] Support narrowing for tuple matches with literal elements (#22303)
## Summary

See:
https://github.com/astral-sh/ruff/pull/22299#issuecomment-3699913849.
2025-12-30 13:45:07 -05:00
Alex Waygood
e0e1e9535e [ty] Convert several comments in ty_extensions.pyi to docstrings (#22305) 2025-12-30 17:41:43 +00:00
github-actions[bot]
7173c7ea3f [ty] Sync vendored typeshed stubs (#22302)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-12-30 17:24:13 +00:00
Alex Waygood
5013752c6c Bump docstring-adder pin (#22301) 2025-12-30 17:03:54 +00:00
Kevin Yang
b2b9d91859 [airflow] Passing positional argument into airflow.lineage.hook.HookLineageCollector.create_asset is not allowed (AIR303) (#22046)
## Summary

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

The new code AIR303 is added for checking function signature change in
Airflow 3.0. The new rule added to AIR303 will check if positional
argument is passed into
`airflow.lineage.hook.HookLineageCollector.create_asset`. Since this
method is updated to accept only keywords argument, passing positional
argument into it is not allowed, and will raise an error. The test is
done by checking whether positional argument with 0 index can be found.

## Test Plan

A new test file is added to the fixtures for the code AIR303. Snapshot
test is updated accordingly.

<img width="1444" height="513" alt="Screenshot from 2025-12-17 20-54-48"
src="https://github.com/user-attachments/assets/bc235195-e986-4743-9bf7-bba65805fb87"
/>

<img width="981" height="433" alt="Screenshot from 2025-12-17 21-34-29"
src="https://github.com/user-attachments/assets/492db71f-58f2-40ba-ad2f-f74852fa5a6b"
/>
2025-12-30 11:39:08 -05:00
Brent Westbrook
c483b59ddd [ruff] Add non-empty-init-module (RUF067) (#22143)
Summary
--

This PR adds a new rule, `non-empty-init-module`, which restricts the
kind of
code that can be included in an `__init__.py` file. By default,
docstrings,
imports, and assignments to `__all__` are allowed. When the new
configuration
option `lint.ruff.strictly-empty-init-modules` is enabled, no code at
all is
allowed.

This closes #9848, where these two variants correspond to different
rules in the

[`flake8-empty-init-modules`](https://github.com/samueljsb/flake8-empty-init-modules/)
linter. The upstream rules are EIM001, which bans all code, and EIM002,
which
bans non-import/docstring/`__all__` code. Since we discussed folding
these into
one rule on [Discord], I just added the rule to the `RUF` group instead
of
adding a new `EIM` plugin.

I'm not really sure we need to flag docstrings even when the strict
setting is
enabled, but I just followed upstream for now. Similarly, as I noted in
a TODO
comment, we could also allow more statements involving `__all__`, such
as
`__all__.append(...)` or `__all__.extend(...)`. The current version only
allows
assignments, like upstream, as well as annotated and augmented
assignments,
unlike upstream.

I think when we discussed this previously, we considered flagging the
module
itself as containing code, but for now I followed the upstream
implementation of
flagging each statement in the module that breaks the rule (actually the
upstream linter flags each _line_, including comments). This will
obviously be a
bit noisier, emitting many diagnostics for the same module. But this
also seems
preferable because it flags every statement that needs to be fixed up
front
instead of only emitting one diagnostic for the whole file that persists
as you
keep removing more lines. It was also easy to implement in
`analyze::statement`
without a separate visitor.

The first commit adds the rule and baseline tests, the second commit
adds the
option and a diff test showing the additional diagnostics when the
setting is
enabled.

I noticed a small (~2%) performance regression on our largest benchmark,
so I also added a cached `Checker::in_init_module` field and method
instead of the `Checker::path` method. This was almost the only reason
for the `Checker::path` field at all, but there's one remaining
reference in a `warn_user!`
[call](https://github.com/astral-sh/ruff/blob/main/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs#L188).

Test Plan
--

New tests adapted from the upstream linter

## Ecosystem Report

I've spot-checked the ecosystem report, and the results look "correct."
This is obviously a very noisy rule if you do include code in
`__init__.py` files. We could make it less noisy by adding more
exceptions (e.g. allowing `if TYPE_CHECKING` blocks, allowing
`__getattr__` functions, allowing imports from `importlib` assignments),
but I'm sort of inclined just to start simple and see what users need.

[Discord]:
https://discord.com/channels/1039017663004942429/1082324250112823306/1440086001035771985

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-12-30 11:32:10 -05:00
Charlie Marsh
57218753be [ty] Narrow TypedDict literal access in match statements (#22299)
## Summary

Closes https://github.com/astral-sh/ty/issues/2279.
2025-12-30 11:29:09 -05:00
Brent Westbrook
2ada8b6634 Document options for more rules (#22295)
Summary
--

This is a follow up to #22198 documenting more rule options I found
while going
through all of our rules.

The second commit renames the internal
`flake8_gettext::Settings::functions_names` field to `function_names` to
match
the external configuration option. I guess this is technically breaking
because
it's exposed to users via `--show-settings`, but I don't think we
consider that
part of our stable API. I can definitely revert that if needed, though.

The other changes are just like #22198, adding new `## Options` sections
to
rules to document the settings they use. I missed these in the previous
PR
because they were used outside the rule implementations themselves. Most
of
these settings are checked where the rules' implementation functions are
called
instead.

Oh, the last commit also updates the removal date for
`typing.ByteString`, which
got pushed back in the 3.14 release. I snuck that in today since I never
opened
this PR last week.

I also fixed one reference link in RUF041.

Test Plan
--

Docs checks in CI
2025-12-30 08:44:11 -05:00
RasmusNygren
0edd97dd41 [ty] Add autocomplete suggestions for class arguments (#22110) 2025-12-30 13:10:56 +00:00
renovate[bot]
f8f4ca8fbc Update dependency react-resizable-panels to v4 (#22279)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-12-30 11:04:25 +01:00
renovate[bot]
3d35dbd334 Update actions/upload-artifact digest to b7c566a (#22250)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-12-30 09:07:58 +00:00
renovate[bot]
a652b411b8 Update NPM Development dependencies (#22289)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-12-30 10:04:24 +01:00
RasmusNygren
4dac3d105d [ty] Add skip_dunders option to CompletionTestBuilder (#22293)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-12-30 08:10:14 +00:00
Shunsuke Shibayama
77ad107617 [ty] increase the max number of diagnostics for sympy in ty_walltime benchmark (#22296) 2025-12-30 13:20:19 +09:00
Charlie Marsh
b925ae5061 [ty] Avoid including property in subclasses properties (#22088)
## Summary

As-is, the following rejects `return self.value` in `def other` in the
subclass
([link](https://play.ty.dev/f55b47b2-313e-45d1-ba45-fde410bed32e))
because `self.value` is resolving to `Unknown | int | float | property`:

```python
class Base:
    _value: float = 0.0

    @property
    def value(self) -> float:
        return self._value

    @value.setter
    def value(self, v: float) -> None:
        self._value = v

    @property
    def other(self) -> float:
        return self.value

    @other.setter
    def other(self, v: float) -> None:
        self.value = v

class Derived(Base):
    @property
    def other(self) -> float:
        return self.value

    @other.setter
    def other(self, v: float) -> None:
        reveal_type(self.value)  # revealed: int | float
        self.value = v
```

I believe the root cause is that we're not excluding properties when
searching for class methods, so we're treating the `other` setter as a
classmethod. I don't fully understand how that ends up materializing as
`| property` on the union though.
2025-12-30 03:28:03 +00:00
Charlie Marsh
9333f15433 [ty] Fix match exhaustiveness for enum | None unions (#22290)
## Summary

If we match on an `TestEnum | None`, then when adding a case like
`~Literal[TestEnum.FOO]` (i.e., after `if value == TestEnum.FOO:
return`), we'd distribute `Literal[TestEnum.BAR]` on the entire builder,
creating `None & Literal[TestEnum.BAR]` which simplified to `Never`.
Instead, we should only expand to the remaining members for pieces of
the intersection that contain the enum.

Now, `(TestEnum | None) & ~Literal[TestEnum.FOO] &
~Literal[TestEnum.BAR]` correctly simplifies to `None` instead of
`Never`.

Closes https://github.com/astral-sh/ty/issues/2260.
2025-12-29 22:19:28 -05:00
Shunsuke Shibayama
c429ef8407 [ty] don't expand type aliases via type mappings unless necessary (#22241)
## Summary

`apply_type_mapping` always expands type aliases and operates on the
resulting types, which can lead to cluttered results due to excessive
type alias expansion in places where it is not actually needed.

Specifically, type aliases are expanded when displaying method
signatures, because we use `TypeMapping::BindSelf` to get the method
signature.

```python
type Scalar = int | float
type Array1d = list[Scalar] | tuple[Scalar]

def f(x: Scalar | Array1d) -> None: pass
reveal_type(f)  # revealed: def f(x: Scalar | Array1d) -> None

class Foo:
    def f(self, x: Scalar | Array1d) -> None: pass
# should be `bound method Foo.f(x: Scalar | Array1d) -> None`
reveal_type(Foo().f)  # revealed: bound method Foo.f(x: int | float | list[int | float] | tuple[int | float]) -> None
```

In this PR, when type mapping is performed on a type alias, the
expansion result without type mapping is compared with the expansion
result after type mapping, and if the two are equivalent, the expansion
is deemed redundant and canceled.

## Test Plan

mdtest updated
2025-12-29 19:02:56 -08:00
Eric Mark Martin
8716b4e230 [ty] implement typing.TypeGuard (#20974)
## Summary

Resolve(s) astral-sh/ty#117, astral-sh/ty#1569

Implement `typing.TypeGuard`. Due to the fact that it [overrides
anything previously known about the checked
value](https://typing.python.org/en/latest/spec/narrowing.html#typeguard)---

> When a conditional statement includes a call to a user-defined type
guard function, and that function returns true, the expression passed as
the first positional argument to the type guard function should be
assumed by a static type checker to take on the type specified in the
TypeGuard return type, unless and until it is further narrowed within
the conditional code block.

---we have to substantially rework the constraints system. In
particular, we make constraints represented as a disjunctive normal form
(DNF) where each term includes a regular constraint, and one or more
disjuncts with a typeguard constraint. Some test cases (including some
with more complex boolean logic) are added to `type_guards.md`.


## Test Plan

- update existing tests
- add new tests for more complex boolean logic with `TypeGuard`
- add new tests for `TypeGuard` variance

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-12-29 17:54:17 -08:00
Alex Waygood
9dadf2724c [ty] Add documentation for ty_extensions.Top and ty_extensions.Bottom (#22245)
Co-authored-by: Carl Meyer <carl@astral.sh>
2025-12-29 19:43:17 +00:00
Charlie Marsh
3d8ae2e476 [ty] Avoid showing misleading hint for unpacked tuple arguments (#22286)
## Summary

We could implement support for showing multiple argument names, though
this seems to match PyCharm.

Closes https://github.com/astral-sh/ty/issues/2250.
2025-12-29 13:25:08 -05:00
Alex Waygood
ebc1323ccb Bump shellcheck to the latest version (#22285) 2025-12-29 18:22:07 +00:00
Alex Waygood
0584081dc8 Don't run ruff-ecosystem on PRs that only touch ruff_benchmark (#22246) 2025-12-29 17:33:53 +00:00
renovate[bot]
b31ff2c5ab Update Rust crate libc to v0.2.178 (#22257)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 18:24:54 +01:00
renovate[bot]
ce27dc9b2d Update dependency monaco-editor to ^0.55.0 (#22264)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 18:24:41 +01:00
renovate[bot]
ab542698ad Update actions/download-artifact digest to 37930b1 (#22249)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-12-29 17:20:38 +00:00
renovate[bot]
a737a56c53 Move quickcheck dependeny pins to workspace Cargo.toml (#22247) 2025-12-29 17:12:31 +00:00
renovate[bot]
a1c3f16358 Update pre-commit dependencies (#22281)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-12-29 17:11:28 +00:00
renovate[bot]
8f54701f0f Update docker/setup-buildx-action action to v3.12.0 (#22266)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 18:11:20 +01:00
renovate[bot]
a03a65fec5 Update actions/cache action to v5 (#22274)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 18:10:52 +01:00
renovate[bot]
f800ad3fad Update docker/metadata-action action to v5.10.0 (#22265)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 18:09:50 +01:00
renovate[bot]
64baa366f2 Update dependency node to v24 (#22278)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 18:09:31 +01:00
renovate[bot]
ce4dd7f12d Update actions/checkout action to v6 (#22275)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 18:08:53 +01:00
renovate[bot]
4b0aa96645 Update GitHub Artifact Actions (#22280)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 18:07:22 +01:00
renovate[bot]
05a5b51ab1 Update taiki-e/install-action action to v2.65.1 (#22273)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 18:02:44 +01:00
renovate[bot]
7cf1ca399a Update dependency mdformat-mkdocs to v5 (#22277)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 17:53:22 +01:00
renovate[bot]
475016616b Update Rust crate serde_with to v3.16.1 (#22270)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 11:48:10 -05:00
renovate[bot]
ea2ff92a06 Update Rust crate supports-hyperlinks to v3.2.0 (#22271)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 16:44:05 +00:00
renovate[bot]
96d7c4bb6a Update Rust crate schemars to v1.1.0 (#22269)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 17:43:01 +01:00
renovate[bot]
9261904c41 Update Rust crate criterion to 0.8.0 (#22267)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 16:42:24 +00:00
renovate[bot]
4d05dcf4cd Update Rust crate toml to v0.9.10 (#22260)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 17:41:17 +01:00
renovate[bot]
81542ca64c Update Rust crate tracing to v0.1.44 (#22261)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 17:39:37 +01:00
renovate[bot]
085a44e38d Update Rust crate tracing-indicatif to v0.3.14 (#22262)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 17:38:47 +01:00
renovate[bot]
2919ec9bd5 Update dependency mdformat to v1 (#22276)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 17:38:15 +01:00
renovate[bot]
28a56796fe Update astral-sh/setup-uv action to v7.1.6 (#22252)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 11:37:26 -05:00
renovate[bot]
78504bd57d Update Rust crate insta to v1.45.0 (#22268)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 17:37:19 +01:00
renovate[bot]
756f8a5c18 Update Rust crate serde_json to v1.0.146 (#22259)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 11:36:50 -05:00
renovate[bot]
36d1cccdc3 Update actions/setup-node action to v6.1.0 (#22263)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 16:36:35 +00:00
renovate[bot]
130c1f83a5 Update Rust crate uuid to v1.19.0 (#22272)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 16:34:49 +00:00
renovate[bot]
86d2fc8531 Update salsa digest to 309c249 (#22251)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 17:32:45 +01:00
renovate[bot]
ab85a38d39 Update Rust crate log to v0.4.29 (#22258)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 17:26:18 +01:00
renovate[bot]
388c1b6f10 Update dependency mkdocs-material to v9.7.1 (#22254)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 17:25:12 +01:00
renovate[bot]
d7bc1a02bc Update cargo-bins/cargo-binstall action to v1.16.5 (#22253)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 17:24:51 +01:00
renovate[bot]
cb87fb7424 Update dependency ruff to v0.14.10 (#22255)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 17:23:43 +01:00
renovate[bot]
d13e01ed17 Update Rust crate camino to v1.2.2 (#22256)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 16:23:24 +00:00
Alex Waygood
fbb5c8aa3c [ty] Fix LiteralString import in ty_extensions.pyi (#22244) 2025-12-29 14:59:19 +00:00
Harutaka Kawamura
6730350bbd [refurb] Mark FURB192 fix as always unsafe (#22210)
## Summary

Close #22204

## Test Plan

Existing checks
2025-12-29 09:17:33 -05:00
samypr100
e71fd9c040 ci(zizmor): remove broad zizmor ignores (#22199) 2025-12-29 10:52:30 +01:00
Matthew Mckee
fde33baaa5 [ty] Make the implicit shadowing message on invalid assignment diagnostic info (#22219) 2025-12-29 10:30:48 +01:00
Micha Reiser
8efa14ae1b [ty] Limit the returned completions to reduce lag (#22240) 2025-12-29 10:16:32 +01:00
Micha Reiser
6776543b62 [ty] Reduce tracing level for constriant.rs logs (#22239) 2025-12-29 09:22:17 +01:00
Carl Meyer
4c4e652b38 [ty] callable type of a type object is not function-like (#22226) 2025-12-28 11:24:45 -08:00
Micha Reiser
d5c39d3f9f [ty] Fix property-tests (#22229) 2025-12-28 09:58:48 +01:00
Matthew Mckee
dea48ecef0 [ty] Add option to disable syntax errors in the language server (#22217)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-12-27 20:24:38 +00:00
Carl Meyer
55c8707be6 [ty] fix display of top ParamSpec specialization (#22227)
## Summary

I only noticed this in the ecosystem report of
https://github.com/astral-sh/ruff/pull/22213 after merging it. The
change to displaying `Top[]` wrapper around the entire signature instead
of just the parameters had the side effect of not showing it at all when
displaying a top ParamSpec specialization. This PR fixes that.

Marking internal since this is a fixup of a not-released PR.

## Test Plan

Added mdtest that fails without this PR.
2025-12-27 11:14:22 -08:00
Carl Meyer
6d5fb09e92 [ty] fix and simplify callable type materializations (#22213)
## Summary

A couple things I noticed when taking another look at the callable type
materializations.

1) Previously we wrongly ignored the return type when
bottom-materializing a callable with gradual signature, and always
changed it to `Never`.
2) We weren't correctly handling overloads that included a gradual
signature. Rather than separately materializing each overload, we would
just mark the entire callable as "top" or replace the entire callable
with the bottom signature.

Really, "top parameters" is something that belongs on the `Parameters`,
not on the entire `CallableType`. Conveniently, we already have
`ParametersKind` where we can track this, right next to where we already
track `ParametersKind::Gradual`. This saves a bit of memory, fixes the
two bugs above, and simplifies the implementation considerably (net
removal of 100+ LOC, a bunch of places that shouldn't need to care about
topness of a callable no longer need to.)

One user-visible change from this is that I now display the "top
callable" as `(Top[...]) -> object` instead of `Top[(...) -> object]`. I
think this is a (minor) improvement, because it wraps exactly the part
in `Top` that needs to be, rather than misleadingly wrapping the entire
callable type, including the return type (which has already been
separately materialized). I think the prior display would be
particularly confusing if the return type also has its own `Top` in it:
previously we could have e.g. `Top[(...) -> Top[list[Unknown]]]`, which
I think is less clear than the new `(Top[...]) -> Top[list[Unknown]]`.

## Test Plan

Added mdtests that failed before this PR and pass after it.

### Ecosystem

The changed diagnostics are all either the change to `Top` display, or
else known non-deterministic output. The added diagnostics are all true
positives:

The added diagnostic at
aa35ca1965/torchvision/transforms/v2/_utils.py (L149)
is a true positive that wasn't caught by the previous version. `str` is
not assignable to `Callable[[Any], Any]` (strings are not callable), nor
is the top callable (top callable includes callables that do not take a
single required positional argument.)

The added diagnostic at
081535ad9b/starlette/routing.py (L67)
is also a (pedantic) true positive. It's the same case as #1567 -- the
code assumes that it is impossible for a subclass of `Response` to
implement `__await__` (yielding something other than a `Response`).

The pytest added diagnostics are also both similar true positives: they
make the assumption that an object cannot simultaneously be a `Sequence`
and callable, or an `Iterable` and callable.
2025-12-27 10:45:07 -08:00
Micha Reiser
fffd3e5cfb [ty] Re-use vec when building a VariableLengthTypeVarTuple with the builder (#22225) 2025-12-27 17:06:30 +01:00
RasmusNygren
7ac1874ca0 [ty] Use the AST to suppress keywords in decorators (#22224) 2025-12-27 13:29:45 +00:00
Alex Waygood
7290bdc41e [ty] Use a length-2 array for UnionTypeInstance::_value_expr_types (#22222) 2025-12-27 11:47:46 +00:00
Simon Lamon
c032e27566 [ty] Rename non-subscriptable error code to not-subscriptable (#22193) 2025-12-27 11:44:35 +00:00
Micha Reiser
da188d5cf6 [ty] Reduce monomorphization in add_binding (#22196) 2025-12-27 11:17:27 +01:00
Micha Reiser
5d32ab8175 [ty] Return slices for Tuple methods (#22192) 2025-12-27 10:30:34 +01:00
Matthew Mckee
6342cec842 [ty] Promote float and complex when promoting literals (#22215)
## Summary

Resolve https://github.com/astral-sh/ty/issues/2226

We need to add a special case in `apply_type_mapping` instead of
directly in `promote_literals_impl` because we do not reach this with
non generic non tuple nominal instances. We still ensure we apply the
normal mapping if we do not see `float` or `complex` instances.

## Test Plan

Update existing mdtest and add a new case to `literal_promotion.md`
2025-12-26 16:19:23 -08:00
Brent Westbrook
95a532f9fd [pylint] Restore the fix safety docs for PLW0133 (#22211)
Summary
--

Noticed while responding to #22201 that the last sentence here just ends
abruptly. It turns out that I missed this change when reviewing #21382.

Test Plan
--

CI
2025-12-26 11:45:15 -05:00
Brent Westbrook
c842de5c4c Document the options used by more rules (#22198)
Summary
--

While analyzing our rules, I wanted to know which of them use
configuration options but noticed that some of them were not documented
(or at least not documented in a separate `## Options` section).

I had Claude generate an initial list of candidate rules, but it
contained a lot of false positives that I filtered out, and I ended up
adding all of these sections myself. I'm not claiming that the options
lists are exhaustive (as in the rules may use additional options beyond
what I found), but this will at least help with my goal of determining
whether or not a rule is configurable at all and also hopefully be
helpful in general.

I mostly just tacked on an `## Options` section without any commentary,
but I added a couple lines of explanation when I felt that the meaning
of the options wasn't obvious from the context.

I also noticed a bit of variation in the `flake8-simplify` rules from
doing this. Some of them offer a diagnostic but no fix depending on the
resulting line length of the suggestion, while others offer neither. I'm
not sure we need to do anything different here, but it seemed worth
mentioning.

Test Plan
--

Docs tests to make sure the links are right
2025-12-26 11:24:49 -05:00
Micha Reiser
9693375e10 [ty] Reduce monomorphization (#22195) 2025-12-26 10:02:20 +01:00
Matthew Mckee
1ec3503cc3 [ty] Fix playground inlay hint location (#22200) 2025-12-26 09:20:57 +01:00
Alex Waygood
19b10993e1 [ty] Automatically re-run ecosystem-analyzer workflow on subsequent pushes to a PR, if the PR has the ecosystem-analyzer label (#22179)
## Summary

This PR reworks our ecosystem-analyzer workflow so that it automatically
reruns if a PR with the `ecosystem-analyzer` label has new commits
pushed to it, or is reopened after previously being closed. It's
currently easy to forget that you need to remove and re-add the label to
trigger a fresh workflow run, which can then mean that there are stale
(misleading) results in the PR comment posted by the bot. It also means
that it takes longer for CI to finish than it would otherwise, because
it might be a few minutes after pushing new commits to the PR before you
remember that you also need to remove and re-add the label.

To write this PR, I consulted:
- The GitHub workflow trigger documentation:
https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request
- This Stack Overflow answer:
https://stackoverflow.com/a/59588725/13990016

## Test Plan

I experimented with pushing commits to this PR and closing/reopening it,
and both of these actions triggered fresh runs of the ecosystem-analyzer
worfklow when the label was present on the PR. However, removing the
label again meant that the workflow was no longer triggered by these
actions.
2025-12-25 17:41:19 +00:00
Micha Reiser
014abe1ee1 [ty] Fix completion in decorators with missing declaration (#22177) 2025-12-25 15:05:47 +00:00
Simon Lamon
dd3a985109 [ty] Spell out "method resolution order" in unsupported-base subdiagnostic (#22194) 2025-12-25 10:26:44 +00:00
Micha Reiser
12f5ea51e3 [ty] Invert dependencies of ty_combine and ty_python_semantic (#22191) 2025-12-25 10:06:06 +01:00
Alex Waygood
f9afcc400c [ty] Improve diagnostic when a user tries to access a function attribute on a Callable type (#22182)
## Summary

Other type checkers allow you to access all `FunctionType` attributes on
any object with a `Callable` type. ty does not, because this is
demonstrably unsound, but this is often a source of confusion for users.
And there were lots of diagnostics in the ecosystem report for
https://github.com/astral-sh/ruff/pull/22145 that were complaining that
"Object of type `(...) -> Unknown` has no attribute `__name__`", for
example.

The discrepancy between what ty does here and what other type checkers
do is discussed a bit in https://github.com/astral-sh/ty/issues/1495.
You can see that there have been lots of issues closed as duplicates of
that issue; we should probably also add an FAQ entry for it.

Anyway, this PR adds a subdiagnostic to help users out when they hit
this diagnostic. Unfortunately something I did meant that rustfmt
increased the indentation of the whole of this huge closure, so this PR
is best reviewed with the "No whitespace" option selected for viewing
the diff.

## Test Plan

Snapshot added
2025-12-24 15:47:11 -05:00
Alex Waygood
768c5a2285 [ty] Use Type::string_literal() more (#22184) 2025-12-24 20:06:57 +00:00
Alex Waygood
139149f87b [ty] Improve diagnostic when callable is used in a type expression instead of collections.abc.Callable or typing.Callable (#22180) 2025-12-24 19:18:51 +00:00
Charlie Marsh
2de4464e92 [ty] Fix implementation of Top[Callable[..., object]] (#22145)
## Summary

Add a proper representation for the `Callable` top type, and use it to
get `callable()` narrowing right.

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

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-12-24 12:49:09 -05:00
Micha Reiser
ded4d4bbe9 [ty] Fix classification of module in import x as y (#22175) 2025-12-24 18:25:29 +01:00
Micha Reiser
eef403f6cf Revert "[ty] Fix completion in decorator without class or function definition" (#22176) 2025-12-24 17:27:21 +01:00
Micha Reiser
adef89eb7c [ty] Fix completion in decorator without class or function definition 2025-12-24 16:57:36 +01:00
Micha Reiser
e3498121b4 [ty] Fix module resolution on network drives (#22173) 2025-12-24 16:36:18 +01:00
Brent Westbrook
5e7fc9a4e1 Render the entire diagnostic message in all output formats (#22164)
Summary
--

This PR fixes https://github.com/astral-sh/ty/issues/2186 by replacing
uses of
`Diagnostic::body` with [`Diagnostic::concise_message`][d]. The initial
report
was only about ty's GitHub and GitLab output formats, but I think it
makes sense
to prefer `concise_message` in the other output formats too. Ruff
currently only
sets the primary message on its diagnostics, which is why this has no
effect on
Ruff, and ty currently only supports the GitHub and GitLab formats that
used
`body`, but removing `body` should help to avoid this problem as Ruff
starts to
use more diagnostic features or ty gets new output formats.

Test Plan
--

Updated existing GitLab and GitHub CLI tests to have `reveal_type`
diagnostics

[d]:
https://github.com/astral-sh/ruff/blob/395bf106ab/crates/ruff_db/src/diagnostic/mod.rs#L185
[t]:
https://github.com/astral-sh/ruff/blob/395bf106ab/crates/ruff/tests/cli/lint.rs#L3509
2025-12-24 10:28:24 -05:00
Alex Waygood
3c5956e93d [ty] Include the specialization of a generic TypedDict as part of its display (#22174)
## Summary

This is the easy bit of https://github.com/astral-sh/ty/issues/2190

## Test Plan

mdtests updated
2025-12-24 14:39:46 +00:00
Charlie Marsh
81f34fbc8e [ty] Store un-widened type in Place (#22093)
## Summary

See: https://github.com/astral-sh/ruff/pull/22025#discussion_r2632724156
2025-12-23 23:19:57 -05:00
Charlie Marsh
184f487c84 [ty] Add a dedicated diagnostic for TypedDict deletions (#22123)
## Summary

Provides a message like:

```
  error[invalid-argument-type]: Cannot delete required key "name" from TypedDict `Movie`
    --> test.py:15:7
     |
  15 | del m["name"]
     |       ^^^^^^
     |
  info: Field defined here
   --> test.py:4:5
    |
  4 |     name: str
    |     --------- `name` declared as required here; consider making it `NotRequired`
    |
  info: Only keys marked as `NotRequired` (or in a TypedDict with `total=False`) can be deleted
```
2025-12-24 03:49:42 +00:00
Charlie Marsh
969c8a547e [ty] Synthesize __delitem__ for TypedDict to allow deleting non-required keys (#22122)
## Summary

TypedDict now synthesizes a proper `__delitem__` method that...

- ...allows deletion of `NotRequired` keys and keys in `total=False`
TypedDicts.
- ...rejects deletion of required keys (synthesizes `__delitem__(k:
Never)`).
2025-12-24 03:39:54 +00:00
Charlie Marsh
acdda78189 [ty] Fix @staticmethod combined with other decorators incorrectly binding self (#22128)
## Summary

We already had `CallableTypeKind::ClassMethodLike` to track callables
that behave like `classmethods` (always bind the first argument). This
PR adds the symmetric `CallableTypeKind::StaticMethodLike` for callables
that behave like `staticmethods` (never bind `self`).

Closes https://github.com/astral-sh/ty/issues/2114.
2025-12-24 03:35:09 +00:00
Charlie Marsh
c28c1f534d [ty] Check __delitem__ instead of __getitem__ for del x[k] (#22121)
## Summary

Previously, `del x[k]` incorrectly required the object to have a
`__getitem__` method. This was wrong because deletion only needs
`__delitem__`, which is independent of `__getitem__`.

Closes https://github.com/astral-sh/ty/issues/1799.
2025-12-24 03:34:20 +00:00
Charlie Marsh
b723917463 [ty] Support tuple narrowing based on member checks (#22167)
## Summary

Closes https://github.com/astral-sh/ty/issues/2179.
2025-12-23 20:15:50 -05:00
Charlie Marsh
5decf94644 [ty] Synthesize a _fields attribute for NamedTuples (#22163)
## Summary

Closes #2176.
2025-12-23 21:38:41 +00:00
Charlie Marsh
89a55dd09f [ty] Synthesize a _replace method for NamedTuples (#22153)
## Summary

Closes https://github.com/astral-sh/ty/issues/2170.
2025-12-23 16:33:55 -05:00
Shunsuke Shibayama
8710a8c4ac [ty] don't expand type aliases in implicit tuple aliases (#22015)
## Summary

This PR fixes https://github.com/astral-sh/ty/issues/1848.

```python
T = tuple[int, 'U']

class C(set['U']):
    pass

type U = T | C
```

The reason why the fixed point iteration did not converge was because
the types stored in the implicit tuple type alias `Specialization`
changed each time.

```
1st: <class 'tuple[int, C]'>
2nd: <class 'tuple[int, tuple[int, C] | C]'>
3rd: <class 'tuple[int, tuple[int, tuple[int, C] | C] | C]'>
...
```

And this was because `UnionType::from_elements` was used when creating
union types for tuple operations, which causes type aliases inside to be
expanded.
This PR replaces these with `UnionType::from_elements_leave_aliases`.

## Test Plan

New corpus test
2025-12-23 13:22:54 -08:00
Jack O'Connor
e245c1d76e [ty] narrow tagged unions of TypedDict (#22104)
Identify and narrow cases like this:

```py
class Foo(TypedDict):
    tag: Literal["foo"]

class Bar(TypedDict):
    tag: Literal["bar"]

def _(union: Foo | Bar):
    if union["tag"] == "foo":
        reveal_type(union)  # Foo
```

Fixes part of https://github.com/astral-sh/ty/issues/1479.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-12-23 19:30:08 +00:00
Charlie Marsh
4c175fa0e1 [ty] Bind self with instance in __get__ (#22155)
## Summary

See: https://github.com/astral-sh/ruff/pull/22153/changes#r2641788438.
2025-12-23 11:25:58 -08:00
Micha Reiser
ed64c4d943 [ty] Abort printing diagnostics when pressing Ctrl+C (#22083) 2025-12-23 18:03:58 +01:00
Matthew Mckee
f1e6c9c3a0 [ty] Use markdown for completions documentation (#21752) 2025-12-23 17:07:53 +01:00
Wizzerinus | Alex K.
d9fe996e64 [ty] Support custom builtins (#22021) 2025-12-23 13:48:14 +00:00
Matthew Mckee
5ea30c4c53 Show both ty.toml and pyproject.toml examples in configuration reference (#22144)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-12-23 09:49:44 +01:00
Micha Reiser
ccc9132f73 [ty] Use ModuleName::new_static in more places (#22156) 2025-12-23 09:24:49 +01:00
Ibraheem Ahmed
65021fcee9 [ty] Support type inference between protocol instances (#22120) 2025-12-23 09:24:01 +01:00
Micha Reiser
aa21b70a8b [ty] Fix path of instrumented benchnmark binary (#22157) 2025-12-23 09:12:18 +01:00
Vincent Ging Ho Yim
22ce0c8a51 Decrease Markdown heading level (#22152) 2025-12-23 07:44:45 +00:00
Micha Reiser
d6a7c9b4ed [ty] Add respect-type-ignore-comments configuration option (#22137) 2025-12-23 08:36:51 +01:00
Charlie Marsh
4745d15fff [ty] Respect debug text interpolation in f-strings (#22151)
## Summary

Per @carljm's comment, we just fall back to `str`.

Closes https://github.com/astral-sh/ty/issues/2151.
2025-12-22 20:21:28 -05:00
Shunsuke Shibayama
06db474f20 [ty] stabilize union-type ordering in fixed-point iteration (#22070)
## Summary

This PR fixes https://github.com/astral-sh/ty/issues/2085.

Based on the reported code, the panicking MRE is:

```python
class Test:
    def __init__(self, x: int):
        self.left = x
        self.right = x
    def method(self):
        self.left, self.right = self.right, self.left
        if self.right:
            self.right = self.right
```

The type inference (`implicit_attribute_inner`) for `self.right`
proceeds as follows:

```
0: Divergent(Id(6c07))
1: Unknown | int | (Divergent(Id(1c00)) & ~AlwaysFalsy)
2: Unknown | int | (Divergent(Id(6c07)) & ~AlwaysFalsy) | (Divergent(Id(1c00)) & ~AlwaysFalsy)
3: Unknown | int | (Divergent(Id(1c00)) & ~AlwaysFalsy) | (Divergent(Id(6c07)) & ~AlwaysFalsy)
4: Unknown | int | (Divergent(Id(6c07)) & ~AlwaysFalsy) | (Divergent(Id(1c00)) & ~AlwaysFalsy)
...
```

The problem is that the order of union types is not stable between
cycles. To solve this, when unioning the previous union type with the
current union type, we should use the previous type as the base and add
only the new elements in this cycle (In the current implementation, this
unioning order was reversed).

## Test Plan

New corpus test
2025-12-22 16:16:03 -08:00
Charlie Marsh
664686bdbc [ty] Exclude parameterized tuple types from narrowing when disjoint from comparison values (#22129)
## Summary

IIUC, tuples with a known structure (`tuple_spec`) use the standard
tuple `__eq__` which only returns `True` for other tuples, so they can
be safely excluded when disjoint from string literals or other non-tuple
types.

Closes https://github.com/astral-sh/ty/issues/2140.
2025-12-22 20:44:49 +00:00
William Woodruff
4a937543b9 Ecosystem report: publish site via astral-sh-bot (#22142) 2025-12-22 12:57:49 -05:00
Micha Reiser
ec034fc359 [ty] Add new diagnosticMode: off (#22073) 2025-12-22 16:46:02 +01:00
Micha Reiser
29d7f22c1f [ty] Add ty.configuration and ty.configurationFile options (#22053) 2025-12-22 16:13:20 +01:00
Micha Reiser
8fc4349fd3 [ty] Split suppression.rs into multiple smaller modules (#22141) 2025-12-22 16:08:56 +01:00
Matthew Mckee
816b19c4a6 [ty] Rename set_invalid_syntax to set_invalid_type_annotation (#22140) 2025-12-22 14:29:03 +00:00
Peter Law
87406b43ea Fix iter example in usafe fixes doc (#22118)
## Summary

This appears to have been a copy/paste error from the list example, as
the subscript is not present in the original next/iter example only in
the case where the error case is shown. While in the specific example
code the subscript actually has no effect, it does make the example
slightly confusing.

Consider the following variations, first the example from the docs
unchanged and second the same code but not hitting the intended error
case (due to using a non-empty collection):
```console
$ python3 -c 'next(iter(range(0)))[0]'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
StopIteration

$ python3 -c 'next(iter(range(1)))[0]'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
TypeError: 'int' object is not subscriptable
```

## Test Plan

Not directly tested, however see inline snippets above.
2025-12-22 09:25:28 -05:00
Matthew Mckee
422e99ea70 [ty] Add inlay hint request time log (#22138) 2025-12-22 15:08:49 +01:00
Micha Reiser
ea6730f546 [ty] Speed-up instrumented benchmarks (#22133) 2025-12-22 15:06:22 +01:00
Matthew Mckee
a46835c224 [ty] Set flag to avoid type[T@f] being inserted when you double-click on the inlay (#22139) 2025-12-22 14:00:45 +00:00
Micha Reiser
884e83591e [ty] Update salsa (#22072) 2025-12-22 13:22:54 +01:00
Alex Waygood
6b3dd28e63 [ty] Make a server snapshot less painful to update (#22132) 2025-12-22 12:13:58 +00:00
Harutaka Kawamura
572f57aa3c Fix GitHub Actions output format for multi-line diagnostics (#22108) 2025-12-22 10:08:07 +01:00
Micha Reiser
ed423e0ae2 [ty] Speedup ty-walltime benchmarks (#22126)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-12-22 08:44:17 +01:00
Charlie Marsh
fee4e2d72a [ty] Distribute type[] over unions (#22115)
## Summary

Closes https://github.com/astral-sh/ty/issues/2121.
2025-12-21 18:45:29 -05:00
Will Duke
b6e84eca16 [ty] Document invalid-syntax-in-forward-annotation and escape-character-in-forward-annotation (#22130)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-12-21 19:35:44 +00:00
Micha Reiser
b4c2825afd [ty] Move module resolver code into its own crate (#22106) 2025-12-21 11:00:34 +00:00
Ibraheem Ahmed
ad41728204 [ty] Avoid temporarily storing invalid multi-inference attempts (#22103)
## Summary

I missed this in https://github.com/astral-sh/ruff/pull/22062. This
avoids exponential runtime in the following snippet:

```py
class X1: ...
class X2: ...
class X3: ...
class X4: ...
class X5: ...
class X6: ...
...

def f(
    x:
        list[X1 | None]
      | list[X2 | None]
      | list[X3 | None]
      | list[X4 | None]
      | list[X5 | None]
      | list[X6 | None]
      ...
):
    ...

def g[T](x: T) -> list[T]:
    return [x]

def id[T](x: T) -> T:
    return x

f(id(id(id(id(g(X64()))))))
```

Eventually I want to refactor our multi-inference infrastructure (which
is currently very brittle) to handle this implicitly, but this is a
temporary performance fix until that happens.
2025-12-20 08:20:53 -08:00
Hugo
3ec63b964c [ty] Add support for dict(...) calls in typed dict contexts (#22113)
## Summary

fixes https://github.com/astral-sh/ty/issues/2127
- handle `dict(...)` calls in TypedDict context with bidirectional
inference
- validate keys/values using the existing TypedDict constructor implem
- mdtest: add 1 positive test, 1 negative test for invalid coverage

## Test Plan

```sh
cargo clippy --workspace --all-targets --all-features -- -D warnings  # Rust linting
cargo test  # Rust testing
uvx pre-commit run --all-files --show-diff-on-failure  # Rust and Python formatting, Markdown and Python linting, etc.
```
fully green
2025-12-20 07:59:03 -08:00
Matthew Mckee
f9a0e1e3f6 [ty] Fix panic introduced in #22076 (#22112)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

Was looking over that PR and this looked wrong.

panic introduced in #22076 

## Test Plan

Before running:

```bash
cargo run -p ty check test.py --force-exclude --no-progress 
```

would result in a panic

```text
thread 'main' (162713) panicked at crates/ty/src/args.rs:459:17:
internal error: entered unreachable code: Clap should make this impossible
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

Now it does not.
2025-12-20 07:53:45 -08:00
William Woodruff
ef4507be96 Remove in-workflow deployment to Cloudflare Pages (#22098) 2025-12-20 10:21:47 -05:00
Alex Waygood
3398ab23a9 Update comments in sync_typeshed.yaml workflow (#22109) 2025-12-20 12:19:04 +00:00
Micha Reiser
5b475b45aa [ty] Add --force-exclude option (#22076) 2025-12-20 10:03:41 +01:00
Ibraheem Ahmed
2a959ef3f2 [ty] Avoid narrowing on non-generic calls (#22102)
## Summary

Resolves https://github.com/astral-sh/ty/issues/2026.
2025-12-19 23:18:07 -05:00
Ibraheem Ahmed
674d3902c6 [ty] Only prefer declared types in non-covariant positions (#22068)
## Summary

The following snippet currently errors because we widen the inferred
type, even though `X` is covariant over `T`. If `T` was contravariant or
invariant, this would be fine, as it would lead to an assignability
error anyways.

```python
class X[T]:
    def __init__(self: X[None]): ...

    def pop(self) -> T:
        raise NotImplementedError

# error: Argument to bound method `__init__` is incorrect: Expected `X[None]`, found `X[int | None]`
x: X[int | None] = X()
```

There are some cases where it is still helpful to prefer covariant
declared types, but this error seems hard to fix otherwise, and makes
our heuristics more consistent overall.
2025-12-19 17:27:31 -05:00
Alex Waygood
1a18ada931 [ty] Run mypy_primer and typing-conformance workflows on fewer PRs (#22096)
## Summary

Fixes https://github.com/astral-sh/ty/issues/2090. Quoting my rationale
from that issue:

> A PR that only touches code in [one of these crates] should never have
any impact on memory usage or diagnostics produced. And the comments
from the bot just lead to additional notifications which is annoying.

I _think_ I've got the syntax right here. The
[docs](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax)
say:

>  The order that you define paths patterns matters:
>
> - A matching negative pattern (prefixed with !) after a positive match
will exclude the path.
> - A matching positive pattern after a negative match will include the
path again.

## Test Plan

No idea? Merge it and see?
2025-12-19 19:31:20 +00:00
Alex Waygood
dde0d0af68 [ty] List rules in alphabetical order in the reference docs (#22097)
## Summary

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

It wasn't obvious to me that there was a deliberate order to the way
these rules were listed in our reference docs -- it looked like it was
_nearly_ alphabetical, but not quite. I think it's simpler if we just
list them in alphabetical order.

## Test Plan

The output from running `cargo dev generate-all` (committed as part of
this PR) looks correct!
2025-12-19 19:11:05 +00:00
Chris Bachhuber
b342f60b40 Update T201 suggestion to not use root logger to satisfy LOG015 (#22059)
## Summary

Currently, the proposed fix for https://docs.astral.sh/ruff/rules/print/
violates https://docs.astral.sh/ruff/rules/root-logger-call/. Thus,
let's change the proposal to make LOG015 happy as well.

## Test Plan

Test manually in a project that has both T201 and LOG015 enabled and run
them over the previous and proposed code. Is there continuous testing of
the code snippets from the docs?
2025-12-19 11:08:12 -08:00
Alex Waygood
2151c3d351 [ty] Document that several rules are disabled by default because of the number of false positives they produce (#22095) 2025-12-19 18:45:18 +00:00
Aria Desires
cdb7a9fb33 [ty] Classify docstrings in semantic tokens (syntax highlighting) (#22031)
## Summary

* Related to, but does not handle
https://github.com/astral-sh/ty/issues/2021

## Test Plan

I also added some snapshot tests for future work on non-standard
attribute docstrings (didn't want to highlight them if we don't
recognize them elsewhere).
2025-12-19 13:36:01 -05:00
github-actions[bot]
df1552b9a4 [ty] Sync vendored typeshed stubs (#22091)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-12-19 18:23:09 +00:00
Alex Waygood
77de3df150 [ty] Fix sync-typeshed workflow timeouts (#22090) 2025-12-19 18:09:08 +00:00
Charlie Marsh
0f18a08a0a [ty] Respect intersections in iterations (#21965)
## Summary

This PR implements the strategy described in
https://github.com/astral-sh/ty/issues/1871: we iterate over the
positive types, resolve them, then intersect the results.
2025-12-19 12:36:37 -05:00
William Woodruff
b63b3c13fb Upload full ecosystem report as a GitHub Actions artifact (#22086) 2025-12-19 11:41:21 -05:00
Ibraheem Ahmed
270d755621 [ty] Avoid storing invalid multi-inference attempts (#22062)
## Summary

This should make revealed types a little nicer, as well as avoid
confusing the constraint solver in some cases (which were showing up in
https://github.com/astral-sh/ruff/pull/21930).
2025-12-19 15:38:47 +00:00
Micha Reiser
9809405b05 [ty] Only clear output between two successful checks (#22078) 2025-12-19 15:16:54 +01:00
Alex Waygood
28cdbb18c5 [ty] Minor followups to #22048 (#22082) 2025-12-19 13:58:59 +00:00
Alex Waygood
f9d1a282fb [ty] Collect mdtest failures as part of the assertion message rather than printing them to the terminal immediately (#22081) 2025-12-19 13:53:31 +00:00
David Peter
0bac023cd2 [ty] Lockfiles for mdtests with external dependencies (#22077)
## Summary

Add lockfiles for all mdtests which make use of external dependencies.
When running tests normally, we use this lockfile when creating the
temporary venv using `uv sync --locked`. A new
`MDTEST_UPGRADE_LOCKFILES` environment variable is used to switch to a
mode in which those lockfiles can be updated or regenerated. When using
the Python mdtest runner, this environment variable is automatically set
(because we use this command while developing, not to simulate exactly
what happens in CI). A command-line flag is provided to opt out of this.

## Test Plan

### Using the mdtest runner

#### Adding a new test (no lockfile yet)

* Removed `attrs.lock` to simulate this
* Ran `uv run crates/ty_python_semantic/mdtest.py -e external/`. The
lockfile is generated and the test succeeds.

#### Upgrading/downgrading a dependency

* Changed pydantic requirement from `pydantic==2.12.2` to
`pydantic==2.12.5` (also tested with `2.12.0`)
* Ran `uv run crates/ty_python_semantic/mdtest.py -e external/`. The
lockfile is updated and the test succeeds.

### Using cargo

#### Adding a new test (no lockfile yet)

* Removed `attrs.lock` to simulate this
* Ran `MDTEST_EXTERNAL=1 cargo test -p ty_python_semantic --test mdtest
mdtest__external` "naively", which outputs:
> Failed to setup in-memory virtual environment with dependencies:
Lockfile not found at
'/home/shark/ruff/crates/ty_python_semantic/resources/mdtest/external/attrs.lock'.
Run with `MDTEST_UPGRADE_LOCKFILES=1` to generate it.
* Ran `MDTEST_UPGRADE_LOCKFILES=1 MDTEST_EXTERNAL=1 cargo test -p
ty_python_semantic --test mdtest mdtest__external`. The lockfile is
updated and the test succeeds.

#### Upgrading/downgrading a dependency

* Changed pydantic requirement from `pydantic==2.12.2` to
`pydantic==2.12.5` (also tested with `2.12.0`)
* Ran `MDTEST_EXTERNAL=1 cargo test -p ty_python_semantic --test mdtest
mdtest__external` "naively", which outputs a similar error message as
above.
* Ran the command suggested in the error message (`MDTEST_EXTERNAL=1
MDTEST_UPGRADE_LOCKFILES=1 cargo test -p ty_python_semantic --test
mdtest mdtest__external`). The lockfile is updated and the test
succeeds.
2025-12-19 14:29:52 +01:00
Micha Reiser
30efab8138 [ty] Only print dashed line for failing tests (#22080) 2025-12-19 14:20:03 +01:00
RasmusNygren
58d25129aa [ty] Visit class arguments in source order for semantic tokens (#22063)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-12-19 13:19:49 +00:00
Micha Reiser
e177cc2a5a [ty] Improve union builder performance (#22048) 2025-12-19 08:29:16 +01:00
Micha Reiser
30ce679b9a [ty] Fix rules severity URL (#22069) 2025-12-19 07:25:02 +00:00
Charlie Marsh
76854fdb15 [ty] Unwrap enum.nonmember values (#22025)
## Summary

This PR unwraps the `enum.nonmember` type to match runtime behavior.

Closes https://github.com/astral-sh/ty/issues/1974.
2025-12-18 19:59:49 -05:00
Douglas Creager
5a2d3cda3d [ty] Remove some nondeterminism in constraint set tests (#22064)
We're seeing a lot of nondeterminism in the ecosystem tests at the
moment, which started (or at least got worse) once `Callable` inference
landed.

This PR attempts to remove this nondeterminism. We recently
(https://github.com/astral-sh/ruff/pull/21983) added a `source_order`
field to BDD nodes, which tracks when their constraint was added to the
BDD. Since we build up constraints based on the order that they appear
in the underlying source, that gives us a stable ordering even though we
use an arbitrary salsa-derived ordering for the BDD variables.

The issue (at least for some of the flakiness) is that we add "derived"
constraints when walking a BDD tree, and those derived constraints
inherit or borrow the `source_order` of the "real" constraint that
implied them. That means we can get multiple constraints in our
specialization that all have the same `source_order`. If we're not
careful, those "tied" constraints can be ordered arbitrarily.

The fix requires ~three~ ~four~ several steps:

- When starting to construct a sequent map (the data structure that
stores the derived constraints), we first sort all of the "real"
constraints by their `source_order`. That ensures that we insert things
into the sequent map in a stable order.
- During sequent map construction, derived facts are discovered by a
deterministic process applied to constraints in a (now) stable order. So
derived facts are now also inserted in a stable order.
- We update the fields of `SequentMap` to use `FxOrderSet` instead of
`FxHashSet`, so that we retain that stable insertion order.
- When walking BDD paths when constructing a specialization, we were
already sorting the constraints by their `source_order`. However, we
were not considering that we might get derived constraints, and
therefore constraints with "ties". Because of that, we need to make sure
to use a _stable_ sort, that retains the insertion order for those ties.

All together, this...should...fix the nondeterminism. (Unfortunately, I
haven't been able to effectively test this, since I haven't been able to
coerce local tests to flop into the other order that we sometimes see in
CI.)
2025-12-18 19:00:20 -05:00
Jack O'Connor
fa57253980 [ty] Implement disjointness for TypedDicts (#22044)
This is a preliminary step towards tagged union narrowing for `TypedDict`:
https://github.com/astral-sh/ty/issues/1479
2025-12-18 13:20:22 -08:00
Amethyst Reese
b7fbd986bc [ruff] fix preview-since values for RUF103 and RUF104 (#22061)
Missed including this in the follow-up on #21908
2025-12-18 13:18:04 -08:00
Amethyst Reese
3d334a313e Report diagnostics for invalid/unmatched range suppression comments (#21908)
## Summary

- Adds new RUF103 and RUF104 diagnostics for invalid and unmatched
suppression comments
- Reports RUF100 for any unused range suppression
- Reports RUF102 for range suppression comment with invalid rule codes
- Reports RUF103 for range suppression comment with invalid suppression syntax
- Reports RUF104 diagnostics for any unmatched range suppression comment (disable w/o enable)


## Test Plan

Updated snapshots from test cases with unmatched suppression comments

Issue #3711
Fixes #21878
Fixes #21875
2025-12-18 12:58:58 -08:00
Aria Desires
2e44a861cb [ty] Disable possibly-missing-imports by default (#22041)
@carljm put forth a reasonably compelling argument that just disabling
this lint might be advisable. If we agree, here's the implementation.

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

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-12-18 20:06:34 +00:00
Dylan
45bbb4cbff Bump 0.14.10 (#22058) 2025-12-18 13:08:17 -06:00
Micha Reiser
42b972753a [ty] Use datatest instead of dirtest (#21937) 2025-12-18 18:05:02 +00:00
Micha Reiser
f7ec178400 [ty] Gracefully handle client requests that can't be deserialized (#22051) 2025-12-18 18:01:01 +00:00
Rasmus Nygren
c315164732 [ty] Don't suggest keyword statements when only expressions are valid
There are cases where the python grammar enforces expressions
after certain statements. In such cases we want to suppress
irrelevant keywords from the auto-complete suggestions.

E.g. `with a<CURSOR>`, suggesting `raise` here never makes sense
because it is not valid by the grammar.
2025-12-18 12:27:57 -05:00
Andrew Gallant
bb1955e98c [ty] Use cursor context in a few more places...
... and also add a `ContextCursor::covering_node` helper, since it's
used so much.
2025-12-18 11:00:09 -05:00
Andrew Gallant
070e08a043 [ty] Move completion function to the top
This is the main entry point to this module. It should be at the top.
2025-12-18 11:00:09 -05:00
Andrew Gallant
bab3924833 [ty] Refactor completion generation
This refactor is intended to give more structure to how we generate
completions. There's now a `Context` for "how do we figure out what kind
of completions to offer" and also a `CollectionContext` for "how do we
figure out which completions are appropriate or not." We double down on
`Completions` as a collector and a single point of truth for this. It
now handles adding information to `Completion` (based on the context)
and also skipping completions that are inappropriate (instead of
filtering them after-the-fact).

We also bundle a bunch of state into a new `ContextCursor` type, and
then define a bunch of predicates/accessors on that type that were
previously free functions with loads of parameters.

Finally, we introduce more structure to ranking. Instead of an anonymous
tuple, we define an explicit type with some helper types to hopefully
make the influence on ranking from each constituent piece a bit clearer.

This does seem to fix one bug around detecting the target for non-import
completions, but otherwise should not have any changes in behavior.

This is meant to be a precursor to improving completion ranking.
2025-12-18 11:00:09 -05:00
mahiro
10748b2fdb [flake8-pytest-style] Allow match and check keyword arguments without an expected exception type (PT010) (#21964)
## Summary

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

Updates PT010(`pytest-raises-without-exception`) to recognize `match`
and `check` keyword arguments as valid alternatives to specifying an
exception class.

As of pytest 8.4.0, `pytest.raises()` can be called with only `match` or
`check` keyword arguments without an expected exception.

Fixes #18653

## Test Plan

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

- Added test cases for `match`-only, `check`-only, and both arguments.
- `cargo test -p ruff_linter -- "pytestraiseswithoutexception"` passes
2025-12-18 10:42:06 -05:00
Aria Desires
56539db520 [ty] Fix some configuration panics in the LSP (#22040)
## Summary

This is a revival of https://github.com/astral-sh/ruff/pull/21047 now
that we have a reproducer again.

* Fixes https://github.com/astral-sh/ty/issues/2031
* Fixes https://github.com/astral-sh/ty/issues/859

## Test Plan

e2e test from @zanieb

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-12-18 09:47:02 -05:00
Aria Desires
8d32ad1cab [ty] Add support for attribute docstrings (#22036)
## Summary

I should have factored this better but this includes a drive-by move of
find_node to ruff_python_ast so ty_python_semantic can use it too.

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

## Test Plan

Snapshots galore
2025-12-18 12:18:20 +00:00
Micha Reiser
b2a8c42b51 [ty] Correctly encode multiline tokens for clients not supporting multiline tokens (#22033) 2025-12-18 11:38:21 +00:00
Aria Desires
7bb5dd87ff [ty] Fix goto-declaration on the RHS of from module import submodule (#22042) 2025-12-18 08:28:05 +01:00
Charlie Marsh
06305f3c02 Make analyze_single_pattern_predicate a #[salsa::tracked] function (#22045)
## Summary

We had a report of a blowup with the following snippet:

```python
from enum import StrEnum

class BigEnum(StrEnum):
    VALUE_01 = "VALUE_01"
    VALUE_02 = "VALUE_02"
    VALUE_03 = "VALUE_03"
    VALUE_04 = "VALUE_04"
    VALUE_05 = "VALUE_05"
    VALUE_06 = "VALUE_06"
    VALUE_07 = "VALUE_07"
    VALUE_08 = "VALUE_08"
    VALUE_09 = "VALUE_09"
    VALUE_10 = "VALUE_10"
    VALUE_11 = "VALUE_11"
    VALUE_12 = "VALUE_12"
    VALUE_13 = "VALUE_13"
    VALUE_14 = "VALUE_14"
    VALUE_15 = "VALUE_15"
    VALUE_16 = "VALUE_16"
    VALUE_17 = "VALUE_17"
    VALUE_18 = "VALUE_18"
    VALUE_19 = "VALUE_19"
    VALUE_20 = "VALUE_20"
    VALUE_21 = "VALUE_21"
    VALUE_22 = "VALUE_22"
    VALUE_23 = "VALUE_23"
    VALUE_24 = "VALUE_24"
    VALUE_25 = "VALUE_25"
    VALUE_26 = "VALUE_26"
    VALUE_27 = "VALUE_27"
    VALUE_28 = "VALUE_28"
    VALUE_29 = "VALUE_29"
    VALUE_30 = "VALUE_30"
    VALUE_31 = "VALUE_31"
    VALUE_32 = "VALUE_32"
    VALUE_33 = "VALUE_33"
    VALUE_34 = "VALUE_34"
    VALUE_35 = "VALUE_35"
    VALUE_36 = "VALUE_36"
    VALUE_37 = "VALUE_37"
    VALUE_38 = "VALUE_38"
    VALUE_39 = "VALUE_39"
    VALUE_40 = "VALUE_40"
    VALUE_41 = "VALUE_41"
    VALUE_42 = "VALUE_42"
    VALUE_43 = "VALUE_43"
    VALUE_44 = "VALUE_44"
    VALUE_45 = "VALUE_45"
    VALUE_46 = "VALUE_46"
    VALUE_47 = "VALUE_47"
    VALUE_48 = "VALUE_48"
    VALUE_49 = "VALUE_49"
    VALUE_50 = "VALUE_50"
    VALUE_51 = "VALUE_51"
    VALUE_52 = "VALUE_52"
    VALUE_53 = "VALUE_53"
    VALUE_54 = "VALUE_54"
    VALUE_55 = "VALUE_55"
    VALUE_56 = "VALUE_56"
    VALUE_57 = "VALUE_57"
    VALUE_58 = "VALUE_58"
    VALUE_59 = "VALUE_59"
    VALUE_60 = "VALUE_60"
    VALUE_61 = "VALUE_61"
    VALUE_62 = "VALUE_62"
    VALUE_63 = "VALUE_63"
    VALUE_64 = "VALUE_64"
    VALUE_65 = "VALUE_65"
    VALUE_66 = "VALUE_66"
    VALUE_67 = "VALUE_67"
    VALUE_68 = "VALUE_68"
    VALUE_69 = "VALUE_69"
    VALUE_70 = "VALUE_70"
    VALUE_71 = "VALUE_71"
    VALUE_72 = "VALUE_72"
    VALUE_73 = "VALUE_73"
    VALUE_74 = "VALUE_74"
    VALUE_75 = "VALUE_75"
    VALUE_76 = "VALUE_76"
    VALUE_77 = "VALUE_77"
    VALUE_78 = "VALUE_78"
    VALUE_79 = "VALUE_79"
    VALUE_80 = "VALUE_80"
    VALUE_81 = "VALUE_81"
    VALUE_82 = "VALUE_82"
    VALUE_83 = "VALUE_83"
    VALUE_84 = "VALUE_84"
    VALUE_85 = "VALUE_85"
    VALUE_86 = "VALUE_86"
    VALUE_87 = "VALUE_87"
    VALUE_88 = "VALUE_88"
    VALUE_89 = "VALUE_89"
    VALUE_90 = "VALUE_90"
    VALUE_91 = "VALUE_91"
    VALUE_92 = "VALUE_92"
    VALUE_93 = "VALUE_93"
    VALUE_94 = "VALUE_94"
    VALUE_95 = "VALUE_95"
    VALUE_96 = "VALUE_96"
    VALUE_97 = "VALUE_97"
    VALUE_98 = "VALUE_98"
    VALUE_99 = "VALUE_99"

    def get_info(self) -> tuple[str, int]:
        match self:
            case BigEnum.VALUE_01:
                return self.value, 1
            case BigEnum.VALUE_02:
                return self.value, 2
            case BigEnum.VALUE_03:
                return self.value, 3
            case BigEnum.VALUE_04:
                return self.value, 4
            case BigEnum.VALUE_05:
                return self.value, 5
            case BigEnum.VALUE_06:
                return self.value, 6
            case BigEnum.VALUE_07:
                return self.value, 7
            case BigEnum.VALUE_08:
                return self.value, 8
            case BigEnum.VALUE_09:
                return self.value, 9
            case BigEnum.VALUE_10:
                return self.value, 10
            case BigEnum.VALUE_11:
                return self.value, 11
            case BigEnum.VALUE_12:
                return self.value, 12
            case BigEnum.VALUE_13:
                return self.value, 13
            case BigEnum.VALUE_14:
                return self.value, 14
            case BigEnum.VALUE_15:
                return self.value, 15
            case BigEnum.VALUE_16:
                return self.value, 16
            case BigEnum.VALUE_17:
                return self.value, 17
            case BigEnum.VALUE_18:
                return self.value, 18
            case BigEnum.VALUE_19:
                return self.value, 19
            case BigEnum.VALUE_20:
                return self.value, 20
            case BigEnum.VALUE_21:
                return self.value, 21
            case BigEnum.VALUE_22:
                return self.value, 22
            case BigEnum.VALUE_23:
                return self.value, 23
            case BigEnum.VALUE_24:
                return self.value, 24
            case BigEnum.VALUE_25:
                return self.value, 25
            case BigEnum.VALUE_26:
                return self.value, 26
            case BigEnum.VALUE_27:
                return self.value, 27
            case BigEnum.VALUE_28:
                return self.value, 28
            case BigEnum.VALUE_29:
                return self.value, 29
            case BigEnum.VALUE_30:
                return self.value, 30
            case BigEnum.VALUE_31:
                return self.value, 31
            case BigEnum.VALUE_32:
                return self.value, 32
            case BigEnum.VALUE_33:
                return self.value, 33
            case BigEnum.VALUE_34:
                return self.value, 34
            case BigEnum.VALUE_35:
                return self.value, 35
            case BigEnum.VALUE_36:
                return self.value, 36
            case BigEnum.VALUE_37:
                return self.value, 37
            case BigEnum.VALUE_38:
                return self.value, 38
            case BigEnum.VALUE_39:
                return self.value, 39
            case BigEnum.VALUE_40:
                return self.value, 40
            case BigEnum.VALUE_41:
                return self.value, 41
            case BigEnum.VALUE_42:
                return self.value, 42
            case BigEnum.VALUE_43:
                return self.value, 43
            case BigEnum.VALUE_44:
                return self.value, 44
            case BigEnum.VALUE_45:
                return self.value, 45
            case BigEnum.VALUE_46:
                return self.value, 46
            case BigEnum.VALUE_47:
                return self.value, 47
            case BigEnum.VALUE_48:
                return self.value, 48
            case BigEnum.VALUE_49:
                return self.value, 49
            case BigEnum.VALUE_50:
                return self.value, 50
            case BigEnum.VALUE_51:
                return self.value, 51
            case BigEnum.VALUE_52:
                return self.value, 52
            case BigEnum.VALUE_53:
                return self.value, 53
            case BigEnum.VALUE_54:
                return self.value, 54
            case BigEnum.VALUE_55:
                return self.value, 55
            case BigEnum.VALUE_56:
                return self.value, 56
            case BigEnum.VALUE_57:
                return self.value, 57
            case BigEnum.VALUE_58:
                return self.value, 58
            case BigEnum.VALUE_59:
                return self.value, 59
            case BigEnum.VALUE_60:
                return self.value, 60
            case BigEnum.VALUE_61:
                return self.value, 61
            case BigEnum.VALUE_62:
                return self.value, 62
            case BigEnum.VALUE_63:
                return self.value, 63
            case BigEnum.VALUE_64:
                return self.value, 64
            case BigEnum.VALUE_65:
                return self.value, 65
            case BigEnum.VALUE_66:
                return self.value, 66
            case BigEnum.VALUE_67:
                return self.value, 67
            case BigEnum.VALUE_68:
                return self.value, 68
            case BigEnum.VALUE_69:
                return self.value, 69
            case BigEnum.VALUE_70:
                return self.value, 70
            case BigEnum.VALUE_71:
                return self.value, 71
            case BigEnum.VALUE_72:
                return self.value, 72
            case BigEnum.VALUE_73:
                return self.value, 73
            case BigEnum.VALUE_74:
                return self.value, 74
            case BigEnum.VALUE_75:
                return self.value, 75
            case BigEnum.VALUE_76:
                return self.value, 76
            case BigEnum.VALUE_77:
                return self.value, 77
            case BigEnum.VALUE_78:
                return self.value, 78
            case BigEnum.VALUE_79:
                return self.value, 79
            case BigEnum.VALUE_80:
                return self.value, 80
            case BigEnum.VALUE_81:
                return self.value, 81
            case BigEnum.VALUE_82:
                return self.value, 82
            case BigEnum.VALUE_83:
                return self.value, 83
            case BigEnum.VALUE_84:
                return self.value, 84
            case BigEnum.VALUE_85:
                return self.value, 85
            case BigEnum.VALUE_86:
                return self.value, 86
            case BigEnum.VALUE_87:
                return self.value, 87
            case BigEnum.VALUE_88:
                return self.value, 88
            case BigEnum.VALUE_89:
                return self.value, 89
            case BigEnum.VALUE_90:
                return self.value, 90
            case BigEnum.VALUE_91:
                return self.value, 91
            case BigEnum.VALUE_92:
                return self.value, 92
            case BigEnum.VALUE_93:
                return self.value, 93
            case BigEnum.VALUE_94:
                return self.value, 94
            case BigEnum.VALUE_95:
                return self.value, 95
            case BigEnum.VALUE_96:
                return self.value, 96
            case BigEnum.VALUE_97:
                return self.value, 97
            case BigEnum.VALUE_98:
                return self.value, 98
            case BigEnum.VALUE_99:
                return self.value, 99
```

On my machine, memoizing the computation brings us from 70s to 0.6s.
2025-12-17 22:01:50 -05:00
Amethyst Reese
9cc132f098 [eradicate] ignore ruff:disable and ruff:enable comments in ERA001 (#22038)
## Summary

Don't flag `# ruff: disable` or `# ruff: enable` comments as
commented-out code.

## Test Plan

New test cases.

Issue #3711
2025-12-17 17:04:49 -08:00
Shantanu
cf8d2e35a8 New rule to prevent implicit string concatenation in collections (#21972)
This is a common footgun, see the example in
https://github.com/astral-sh/ruff/issues/13014#issuecomment-3411496519

Fixes #13014 , fixes #13031
2025-12-17 17:37:01 -05:00
Brent Westbrook
0290f5dc3b [flake8-bandit] Fix broken link (S704) (#22039)
Summary
--

While going through all the rules I noticed a broken link in the S704
docs. (I then got really confused because it appeared to be correct in
the _other_ `unsafe_markup_use.rs` file for the removed RUF version of
the rule).

Test Plan
--

[Before](https://docs.astral.sh/ruff/rules/unsafe-markup-use/):

<img width="537" height="171" alt="image"
src="https://github.com/user-attachments/assets/01007cab-6673-48e5-b3a5-6006bc78a027"
/>


After:

<img width="451" height="189" alt="image"
src="https://github.com/user-attachments/assets/4e5d0e0d-76be-4f66-b747-e209f11ab11a"
/>

I also did at least a cursory grep for other cases with escaped link
brackets (`\[`) and only turned up this rule on main.
2025-12-17 17:35:04 -05:00
Charlie Marsh
5bb9ee2a9d [ty] Respect deferred values in keyword arguments et al for .pyi files (#22029)
## Summary

Closes https://github.com/astral-sh/ty/issues/2019.
2025-12-17 14:02:10 -08:00
Aria Desires
638f230910 [ty] improve rendering of signatures in hovers (#22007)
This is the return of #21438 because we never found anything better and
I think it would be good to have this for the beta.
2025-12-17 20:09:31 +00:00
Douglas Creager
b36ff75a24 [ty] Don't add identical lower/upper bounds multiple times when inferring specializations (#22030)
When inferring a specialization of a `Callable` type, we use the new
constraint set implementation. In the example in
https://github.com/astral-sh/ty/issues/1968, we end up with a constraint
set that includes all of the following clauses:

```
     U_co ≤ M1 | M2 | M3 | M4 | M5 | M6 | M7
M1 ≤ U_co ≤ M1 | M2 | M3 | M4 | M5 | M6 | M7
M2 ≤ U_co ≤ M1 | M2 | M3 | M4 | M5 | M6 | M7
M3 ≤ U_co ≤ M1 | M2 | M3 | M4 | M5 | M6 | M7
M4 ≤ U_co ≤ M1 | M2 | M3 | M4 | M5 | M6 | M7
M5 ≤ U_co ≤ M1 | M2 | M3 | M4 | M5 | M6 | M7
M6 ≤ U_co ≤ M1 | M2 | M3 | M4 | M5 | M6 | M7
M7 ≤ U_co ≤ M1 | M2 | M3 | M4 | M5 | M6 | M7
```

In general, we take the upper bounds of those constraints to get the
specialization. However, the upper bounds of those constraints are not
all guaranteed to be the same, and so first we need to intersect them
all together. In this case, the upper bounds are all identical, so their
intersection is trivial:

```
U_co = M1 | M2 | M3 | M4 | M5 | M6 | M7
```

But we were still doing the work of calculating that trivial
intersection 7 times. And each time we have to do 7^2 comparisons of the
`M*` classes, ending up with O(n^3) overall work.

This pattern is common enough that we can put in a quick heuristic to
prune identical copies of the same type before performing the
intersection.

Fixes https://github.com/astral-sh/ty/issues/1968
2025-12-17 13:35:52 -05:00
Charlie Marsh
30c3f9aafe [ty] Apply narrowing to len calls based on argument size (#22026)
## Summary

Closes https://github.com/astral-sh/ty/issues/1983.
2025-12-17 13:15:58 -05:00
chiri
883701ae88 [flake8-use-pathlib] Make fixes unsafe when types change in compound statements (PTH104, PTH105, PTH109, PTH115) (#22009)
## Summary

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

## Test Plan

`cargo nextest run flake8_use_pathlib`
2025-12-17 12:31:27 -05:00
Alex Waygood
0bd7a94c27 [ty] Improve unsupported-base and invalid-super-argument diagnostics to avoid extremely long lines when encountering verbose types (#22022) 2025-12-17 14:43:11 +00:00
Bhuminjay Soni
421f88bb32 [refurb] Extend support for Path.open (FURB101, FURB103) (#21080)
## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
This PR fixes https://github.com/astral-sh/ruff/issues/18409

## Test Plan

<!-- How was it tested? -->
I have added tests in FURB103.

---------

Signed-off-by: 11happy <soni5happy@gmail.com>
Signed-off-by: 11happy <bhuminjaysoni@gmail.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-12-17 09:18:13 -05:00
David Peter
b0eb39d112 [ty] ecosystem-analyzer: Flush full stderr output in case of panics (#22023)
Pulls in
2e1816eac0
2025-12-17 15:02:59 +01:00
charliecloudberry
260f463edd Update setup.md (#22024) 2025-12-17 14:56:25 +01:00
1467 changed files with 31658 additions and 13711 deletions

1
.gitattributes vendored
View File

@@ -22,6 +22,7 @@ crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_CR.py text eol=cr
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_LF.py text eol=lf
crates/ruff_python_parser/resources/inline linguist-generated=true
crates/ty_python_semantic/resources/mdtest/external/*.lock linguist-generated=true
ruff.schema.json -diff linguist-generated=true text=auto eol=lf
ty.schema.json -diff linguist-generated=true text=auto eol=lf

View File

@@ -4,10 +4,17 @@
self-hosted-runner:
# Various runners we use that aren't recognized out-of-the-box by actionlint:
labels:
- depot-ubuntu-24.04-4
- depot-ubuntu-latest-8
- depot-ubuntu-22.04-16
- depot-ubuntu-22.04-32
- depot-windows-2022-16
- depot-ubuntu-22.04-arm-4
- github-windows-2025-x86_64-8
- github-windows-2025-x86_64-16
- codspeed-macro
paths:
".github/workflows/mypy_primer.yaml":
ignore:
- 'condition "false" is always evaluated to false. remove the if: section'

View File

@@ -4,5 +4,6 @@
# Enable off-by-default rules.
[rules]
possibly-unresolved-reference = "warn"
possibly-missing-import = "warn"
unused-ignore-comment = "warn"
division-by-zero = "warn"

View File

@@ -39,7 +39,7 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
submodules: recursive
persist-credentials: false
@@ -59,7 +59,7 @@ jobs:
"${MODULE_NAME}" --help
python -m "${MODULE_NAME}" --help
- name: "Upload sdist"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: wheels-sdist
path: dist
@@ -68,7 +68,7 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
runs-on: macos-14
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
submodules: recursive
persist-credentials: false
@@ -84,7 +84,7 @@ jobs:
target: x86_64
args: --release --locked --out dist
- name: "Upload wheels"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: wheels-macos-x86_64
path: dist
@@ -99,7 +99,7 @@ jobs:
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: artifacts-macos-x86_64
path: |
@@ -110,7 +110,7 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
runs-on: macos-14
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
submodules: recursive
persist-credentials: false
@@ -131,7 +131,7 @@ jobs:
ruff --help
python -m ruff --help
- name: "Upload wheels"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: wheels-aarch64-apple-darwin
path: dist
@@ -146,7 +146,7 @@ jobs:
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: artifacts-aarch64-apple-darwin
path: |
@@ -166,7 +166,7 @@ jobs:
- target: aarch64-pc-windows-msvc
arch: x64
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
submodules: recursive
persist-credentials: false
@@ -192,7 +192,7 @@ jobs:
"${MODULE_NAME}" --help
python -m "${MODULE_NAME}" --help
- name: "Upload wheels"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: wheels-${{ matrix.platform.target }}
path: dist
@@ -203,7 +203,7 @@ jobs:
7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/ruff.exe
sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: artifacts-${{ matrix.platform.target }}
path: |
@@ -219,7 +219,7 @@ jobs:
- x86_64-unknown-linux-gnu
- i686-unknown-linux-gnu
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
submodules: recursive
persist-credentials: false
@@ -242,7 +242,7 @@ jobs:
"${MODULE_NAME}" --help
python -m "${MODULE_NAME}" --help
- name: "Upload wheels"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: wheels-${{ matrix.target }}
path: dist
@@ -260,7 +260,7 @@ jobs:
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: artifacts-${{ matrix.target }}
path: |
@@ -296,7 +296,7 @@ jobs:
arch: riscv64
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
submodules: recursive
persist-credentials: false
@@ -327,7 +327,7 @@ jobs:
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
ruff --help
- name: "Upload wheels"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: wheels-${{ matrix.platform.target }}
path: dist
@@ -345,7 +345,7 @@ jobs:
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: artifacts-${{ matrix.platform.target }}
path: |
@@ -361,7 +361,7 @@ jobs:
- x86_64-unknown-linux-musl
- i686-unknown-linux-musl
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
submodules: recursive
persist-credentials: false
@@ -389,7 +389,7 @@ jobs:
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
.venv/bin/${{ env.MODULE_NAME }} --help
- name: "Upload wheels"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: wheels-${{ matrix.target }}
path: dist
@@ -407,7 +407,7 @@ jobs:
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: artifacts-${{ matrix.target }}
path: |
@@ -427,7 +427,7 @@ jobs:
arch: armv7
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
submodules: recursive
persist-credentials: false
@@ -456,7 +456,7 @@ jobs:
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
.venv/bin/${{ env.MODULE_NAME }} --help
- name: "Upload wheels"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: wheels-${{ matrix.platform.target }}
path: dist
@@ -474,7 +474,7 @@ jobs:
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: artifacts-${{ matrix.platform.target }}
path: |

View File

@@ -20,6 +20,12 @@ on:
env:
RUFF_BASE_IMG: ghcr.io/${{ github.repository_owner }}/ruff
permissions:
contents: read
# TODO(zanieb): Ideally, this would be `read` on dry-run but that will require
# significant changes to the workflow.
packages: write # zizmor: ignore[excessive-permissions]
jobs:
docker-build:
name: Build Docker image (ghcr.io/astral-sh/ruff) for ${{ matrix.platform }}
@@ -33,12 +39,12 @@ jobs:
- linux/amd64
- linux/arm64
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
submodules: recursive
persist-credentials: false
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
@@ -63,7 +69,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
with:
images: ${{ env.RUFF_BASE_IMG }}
# Defining this makes sure the org.opencontainers.image.version OCI label becomes the actual release version and not the branch name
@@ -96,7 +102,7 @@ jobs:
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digests
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: digests-${{ env.PLATFORM_TUPLE }}
path: /tmp/digests/*
@@ -113,17 +119,17 @@ jobs:
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
steps:
- name: Download digests
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
with:
images: ${{ env.RUFF_BASE_IMG }}
# Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
@@ -167,7 +173,7 @@ jobs:
- debian:bookworm-slim,bookworm-slim,debian-slim
- buildpack-deps:bookworm,bookworm,debian
steps:
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
@@ -219,7 +225,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
# ghcr.io prefers index level annotations
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
@@ -256,17 +262,17 @@ jobs:
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
steps:
- name: Download digests
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
with:

View File

@@ -49,8 +49,10 @@ jobs:
py-fuzzer: ${{ steps.check_py_fuzzer.outputs.changed }}
# Flag that is set to "true" when code related to the playground changes.
playground: ${{ steps.check_playground.outputs.changed }}
# Flag that is set to "true" when code related to the benchmarks changes.
benchmarks: ${{ steps.check_benchmarks.outputs.changed }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
persist-credentials: false
@@ -95,6 +97,7 @@ jobs:
':!crates/ruff_python_formatter/**' \
':!crates/ruff_formatter/**' \
':!crates/ruff_dev/**' \
':!crates/ruff_benchmark/**' \
':scripts/*' \
':python/**' \
':.github/workflows/ci.yaml' \
@@ -202,6 +205,21 @@ jobs:
':crates/ruff_python_trivia/**' \
':crates/ruff_source_file/**' \
':crates/ruff_text_size/**' \
':.github/workflows/ci.yaml' \
; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Check if the benchmark code changed
id: check_benchmarks
env:
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
run: |
if git diff --quiet "${MERGE_BASE}...HEAD" -- \
':Cargo.toml' \
':Cargo.lock' \
':crates/ruff_benchmark/**' \
':.github/workflows/ci.yaml' \
; then
@@ -215,7 +233,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: "Install Rust toolchain"
@@ -229,7 +247,7 @@ jobs:
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
timeout-minutes: 20
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
@@ -251,7 +269,7 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
@@ -263,15 +281,15 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-insta
- name: "Install uv"
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
with:
enable-cache: "true"
- name: ty mdtests (GitHub annotations)
@@ -314,7 +332,7 @@ jobs:
(needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main')
timeout-minutes: 20
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
@@ -325,11 +343,11 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-nextest
- name: "Install uv"
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
with:
enable-cache: "true"
- name: "Run tests"
@@ -349,7 +367,7 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
@@ -358,11 +376,11 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo nextest"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-nextest
- name: "Install uv"
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
with:
enable-cache: "true"
- name: "Run tests"
@@ -377,7 +395,7 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 10
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
@@ -385,9 +403,9 @@ jobs:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 22
node-version: 24
cache: "npm"
cache-dependency-path: playground/package-lock.json
- uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0
@@ -409,7 +427,7 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: SebRollen/toml-action@b1b3628f55fc3a28208d4203ada8b737e9687876 # v1.2.0
@@ -438,7 +456,7 @@ jobs:
if: ${{ github.ref == 'refs/heads/main' || needs.determine_changes.outputs.fuzz == 'true' || needs.determine_changes.outputs.code == 'true' }}
timeout-minutes: 10
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
@@ -450,7 +468,7 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@3fc81674af4165a753833a94cae9f91d8849049f # v1.16.2
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
@@ -465,10 +483,10 @@ jobs:
env:
FORCE_COLOR: 1
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
shared-key: ruff-linux-debug
@@ -497,13 +515,13 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 5
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: "Install Rust toolchain"
run: rustup component add rustfmt
# Run all code generation scripts, and verify that the current output is
@@ -530,15 +548,20 @@ jobs:
needs: determine_changes
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
# Ecosystem check needs linter and/or formatter changes.
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && needs.determine_changes.outputs.code == 'true' }}
if: |
!contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' &&
(
needs.determine_changes.outputs.linter == 'true' ||
needs.determine_changes.outputs.formatter == 'true'
)
timeout-minutes: 20
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
ref: ${{ github.event.pull_request.base.ref }}
persist-credentials: false
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
with:
python-version: ${{ env.PYTHON_VERSION }}
activate-environment: true
@@ -559,7 +582,7 @@ jobs:
cargo build --bin ruff
mv target/debug/ruff target/debug/ruff-baseline
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
clean: false
@@ -625,7 +648,7 @@ jobs:
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
name: Upload Results
with:
name: ecosystem-result
@@ -640,11 +663,11 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && (needs.determine_changes.outputs.ty == 'true' || needs.determine_changes.outputs.py-fuzzer == 'true') }}
timeout-minutes: ${{ github.repository == 'astral-sh/ruff' && 10 || 20 }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
persist-credentials: false
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
@@ -687,10 +710,10 @@ jobs:
needs: determine_changes
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@3fc81674af4165a753833a94cae9f91d8849049f # v1.16.2
- uses: cargo-bins/cargo-binstall@80aaafe04903087c333980fa2686259ddd34b2d9 # v1.16.6
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -700,10 +723,10 @@ jobs:
needs: determine_changes
if: ${{ needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main' }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
@@ -722,7 +745,7 @@ jobs:
timeout-minutes: 20
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
@@ -751,18 +774,18 @@ jobs:
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 22
node-version: 24
- name: "Cache pre-commit"
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
@@ -771,7 +794,7 @@ jobs:
echo '```console' > "$GITHUB_STEP_SUMMARY"
# Enable color output for pre-commit and remove it for the summary
# Use --hook-stage=manual to enable slower pre-commit hooks that are skipped by default
SKIP=cargo-fmt,clippy,dev-generate-all uvx --python="${PYTHON_VERSION}" pre-commit run --all-files --show-diff-on-failure --color=always --hook-stage=manual | \
SKIP=cargo-fmt uvx --python="${PYTHON_VERSION}" pre-commit run --all-files --show-diff-on-failure --color=always --hook-stage=manual | \
tee >(sed -E 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[mGK]//g' >> "$GITHUB_STEP_SUMMARY") >&1
exit_code="${PIPESTATUS[0]}"
echo '```' >> "$GITHUB_STEP_SUMMARY"
@@ -782,7 +805,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
@@ -791,7 +814,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: Install uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
with:
python-version: 3.13
activate-environment: true
@@ -813,7 +836,7 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 10
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
@@ -839,7 +862,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
name: "Checkout ruff source"
with:
persist-credentials: false
@@ -855,7 +878,7 @@ jobs:
- name: Build Ruff binary
run: cargo build -p ruff --bin ruff
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
name: "Checkout ruff-lsp source"
with:
persist-credentials: false
@@ -890,7 +913,7 @@ jobs:
- determine_changes
if: ${{ (needs.determine_changes.outputs.playground == 'true') }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: "Install Rust toolchain"
@@ -898,9 +921,9 @@ jobs:
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 22
node-version: 24
cache: "npm"
cache-dependency-path: playground/package-lock.json
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
@@ -926,7 +949,9 @@ jobs:
(
github.ref == 'refs/heads/main' ||
needs.determine_changes.outputs.formatter == 'true' ||
needs.determine_changes.outputs.linter == 'true'
needs.determine_changes.outputs.linter == 'true' ||
needs.determine_changes.outputs.parser == 'true' ||
needs.determine_changes.outputs.benchmarks == 'true'
)
timeout-minutes: 20
permissions:
@@ -934,112 +959,198 @@ jobs:
id-token: write # required for OIDC authentication with CodSpeed
steps:
- name: "Checkout Branch"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-codspeed
- name: "Build benchmarks"
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
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
benchmarks-instrumented-ty:
name: "benchmarks instrumented (ty)"
runs-on: ubuntu-24.04
benchmarks-instrumented-ty-build:
name: "benchmarks instrumented ty (build)"
runs-on: depot-ubuntu-24.04-4
needs: determine_changes
if: |
github.repository == 'astral-sh/ruff' &&
(
github.ref == 'refs/heads/main' ||
needs.determine_changes.outputs.ty == 'true'
needs.determine_changes.outputs.ty == 'true' ||
needs.determine_changes.outputs.benchmarks == 'true'
)
timeout-minutes: 20
permissions:
contents: read # required for actions/checkout
id-token: write # required for OIDC authentication with CodSpeed
steps:
- name: "Checkout Branch"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-codspeed
- name: "Build benchmarks"
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty
run: cargo codspeed build -m instrumentation --features "codspeed,ty_instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty
- name: "Upload benchmark binary"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: benchmarks-instrumented-ty-binary
path: target/codspeed/simulation/ruff_benchmark
retention-days: 1
benchmarks-instrumented-ty-run:
name: "benchmarks instrumented ty (${{ matrix.benchmark }})"
runs-on: ubuntu-24.04
needs: benchmarks-instrumented-ty-build
timeout-minutes: 20
permissions:
contents: read # required for actions/checkout
id-token: write # required for OIDC authentication with CodSpeed
strategy:
fail-fast: false
matrix:
benchmark:
- "check_file|micro|anyio"
- "attrs|hydra|datetype"
steps:
- name: "Checkout Branch"
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: "Install codspeed"
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-codspeed
- name: "Download benchmark binary"
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: benchmarks-instrumented-ty-binary
path: target/codspeed/simulation/ruff_benchmark
- name: "Restore binary permissions"
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
run: cargo codspeed run --bench ty "${{ matrix.benchmark }}"
benchmarks-walltime:
name: "benchmarks walltime (${{ matrix.benchmarks }})"
runs-on: codspeed-macro
benchmarks-walltime-build:
name: "benchmarks walltime (build)"
# We only run this job if `github.repository == 'astral-sh/ruff'`,
# so hardcoding depot here is fine
runs-on: depot-ubuntu-22.04-arm-4
needs: determine_changes
if: ${{ github.repository == 'astral-sh/ruff' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main') }}
if: |
github.repository == 'astral-sh/ruff' &&
(
!contains(github.event.pull_request.labels.*.name, 'no-test') &&
(
needs.determine_changes.outputs.ty == 'true' ||
needs.determine_changes.outputs.benchmarks == 'true' ||
github.ref == 'refs/heads/main'
)
)
timeout-minutes: 20
steps:
- name: "Checkout Branch"
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-codspeed
- name: "Build benchmarks"
run: cargo codspeed build -m walltime --features "codspeed,ty_walltime" --profile profiling --no-default-features -p ruff_benchmark
- name: "Upload benchmark binary"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: benchmarks-walltime-binary
path: target/codspeed/walltime/ruff_benchmark
retention-days: 1
benchmarks-walltime-run:
name: "benchmarks walltime (${{ matrix.benchmark }})"
runs-on: codspeed-macro
needs: benchmarks-walltime-build
timeout-minutes: 20
permissions:
contents: read # required for actions/checkout
id-token: write # required for OIDC authentication with CodSpeed
strategy:
matrix:
benchmarks:
- "medium|multithreaded"
- "small|large"
benchmark:
- colour_science
- "pandas|tanjun|altair"
- "static_frame|sympy"
- "pydantic|multithreaded|freqtrade"
steps:
- name: "Checkout Branch"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- name: "Install Rust toolchain"
run: rustup show
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: "Install codspeed"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-codspeed
- name: "Build benchmarks"
run: cargo codspeed build --features "codspeed,walltime" --profile profiling --no-default-features -p ruff_benchmark
- name: "Download benchmark binary"
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: benchmarks-walltime-binary
path: target/codspeed/walltime/ruff_benchmark
- name: "Restore binary permissions"
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
@@ -1047,4 +1158,4 @@ jobs:
CODSPEED_PERF_ENABLED: false
with:
mode: walltime
run: cargo codspeed run --bench ty_walltime "${{ matrix.benchmarks }}"
run: cargo codspeed run --bench ty_walltime -m walltime "${{ matrix.benchmark }}"

View File

@@ -31,10 +31,10 @@ jobs:
# Don't run the cron job on forks:
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
@@ -62,7 +62,7 @@ jobs:
name: Create an issue if the daily fuzz surfaced any bugs
runs-on: ubuntu-latest
needs: fuzz
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.fuzz.result == 'failure' }}
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.fuzz.result != 'success' }}
permissions:
issues: write
steps:

View File

@@ -6,6 +6,11 @@ on:
pull_request:
paths:
- "crates/ty*/**"
- "!crates/ty_ide/**"
- "!crates/ty_server/**"
- "!crates/ty_test/**"
- "!crates/ty_completion_eval/**"
- "!crates/ty_wasm/**"
- "crates/ruff_db"
- "crates/ruff_python_ast"
- "crates/ruff_python_parser"
@@ -36,14 +41,14 @@ jobs:
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
@@ -65,7 +70,7 @@ jobs:
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- name: Upload diff
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: mypy_primer_diff
path: mypy_primer.diff
@@ -75,14 +80,14 @@ jobs:
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
@@ -103,7 +108,7 @@ jobs:
scripts/mypy_primer.sh
- name: Upload diff
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: mypy_primer_memory_diff
path: mypy_primer_memory.diff
@@ -117,14 +122,14 @@ jobs:
# TODO: Enable once we fixed the non-deterministic diagnostics
if: false
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:

View File

@@ -17,11 +17,14 @@ on:
required: true
type: string
permissions:
contents: read
jobs:
mkdocs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
ref: ${{ inputs.ref }}
persist-credentials: true

View File

@@ -26,14 +26,14 @@ jobs:
env:
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 22
node-version: 24
package-manager-cache: false
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
- name: "Install Node dependencies"

View File

@@ -22,8 +22,8 @@ jobs:
id-token: write
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
pattern: wheels-*
path: wheels

View File

@@ -30,14 +30,14 @@ jobs:
env:
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 22
node-version: 24
package-manager-cache: false
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
- name: "Install Node dependencies"

View File

@@ -29,7 +29,7 @@ jobs:
target: [web, bundler, nodejs]
fail-fast: false
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: "Install Rust toolchain"
@@ -45,9 +45,9 @@ jobs:
jq '.name="@astral-sh/ruff-wasm-${{ matrix.target }}"' crates/ruff_wasm/pkg/package.json > /tmp/package.json
mv /tmp/package.json crates/ruff_wasm/pkg
- run: cp LICENSE crates/ruff_wasm/pkg # wasm-pack does not put the LICENSE file in the pkg
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 22
node-version: 24
registry-url: "https://registry.npmjs.org"
- name: "Publish (dry-run)"
if: ${{ inputs.plan == '' || fromJson(inputs.plan).announcement_tag_is_implicit }}

View File

@@ -70,7 +70,7 @@ jobs:
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.2/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: cargo-dist-cache
path: ~/.cargo/bin/dist
@@ -86,7 +86,7 @@ jobs:
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
@@ -128,14 +128,14 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
- name: Fetch local artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
with:
pattern: artifacts-*
path: target/distrib/
@@ -153,7 +153,7 @@ jobs:
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: artifacts-build-global
path: |
@@ -179,14 +179,14 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Fetch artifacts from scratch-storage
- name: Fetch artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
with:
pattern: artifacts-*
path: target/distrib/
@@ -200,7 +200,7 @@ jobs:
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
# Overwrite the previous copy
name: artifacts-dist-manifest
@@ -256,7 +256,7 @@ jobs:
submodules: recursive
# Create a GitHub Release while uploading all files to it
- name: "Download GitHub Artifacts"
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
with:
pattern: artifacts-*
path: artifacts

View File

@@ -16,8 +16,7 @@ name: Sync typeshed
# 3. Once the Windows worker is done, a MacOS worker:
# a. Checks out the branch created by the Linux worker
# b. Syncs all docstrings available on MacOS that are not available on Linux or Windows
# c. Attempts to update any snapshots that might have changed
# (this sub-step is allowed to fail)
# c. Formats the code again
# d. Commits the changes and pushes them to the same upstream branch
# e. Creates a PR against the `main` branch using the branch all three workers have pushed to
# 4. If any of steps 1-3 failed, an issue is created in the `astral-sh/ruff` repository
@@ -62,12 +61,12 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
name: Checkout Ruff
with:
path: ruff
persist-credentials: true
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
name: Checkout typeshed
with:
repository: python/typeshed
@@ -77,7 +76,7 @@ jobs:
run: |
git config --global user.name typeshedbot
git config --global user.email '<>'
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: Sync typeshed stubs
run: |
rm -rf "ruff/${VENDORED_TYPESHED}"
@@ -126,12 +125,12 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
name: Checkout Ruff
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: Setup git
run: |
git config --global user.name typeshedbot
@@ -165,12 +164,12 @@ jobs:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
name: Checkout Ruff
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: Setup git
run: |
git config --global user.name typeshedbot
@@ -198,42 +197,6 @@ jobs:
run: |
rm "${VENDORED_TYPESHED}/pyproject.toml"
git commit -am "Remove pyproject.toml file"
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Install Rust toolchain"
if: ${{ success() }}
run: rustup show
- name: "Install mold"
if: ${{ success() }}
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
if: ${{ success() }}
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
with:
tool: cargo-nextest
- name: "Install cargo insta"
if: ${{ success() }}
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
with:
tool: cargo-insta
- name: Update snapshots
if: ${{ success() }}
run: |
cargo r \
--profile=profiling \
-p ty_completion_eval \
-- all --tasks ./crates/ty_completion_eval/completion-evaluation-tasks.csv
# The `cargo insta` docs indicate that `--unreferenced=delete` might be a good option,
# but from local testing it appears to just revert all changes made by `cargo insta test --accept`.
#
# If there were only snapshot-related failures, `cargo insta test --accept` will have exit code 0,
# but if there were also other mdtest failures (for example), it will return a nonzero exit code.
# We don't care about other tests failing here, we just want snapshots updated where possible,
# so we use `|| true` here to ignore the exit code.
cargo insta test --accept --color=always --all-features --test-runner=nextest || true
- name: Commit snapshot changes
if: ${{ success() }}
run: git commit -am "Update snapshots" || echo "No snapshot changes to commit"
- name: Push changes upstream and create a PR
if: ${{ success() }}
run: |
@@ -245,7 +208,7 @@ jobs:
name: Create an issue if the typeshed sync failed
runs-on: ubuntu-latest
needs: [sync, docstrings-windows, docstrings-macos-and-pr]
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && (needs.sync.result == 'failure' || needs.docstrings-windows.result == 'failure' || needs.docstrings-macos-and-pr.result == 'failure') }}
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && (needs.sync.result != 'success' || needs.docstrings-windows.result != 'success' || needs.docstrings-macos-and-pr.result != 'success') }}
permissions:
issues: write
steps:

View File

@@ -4,7 +4,13 @@ permissions: {}
on:
pull_request:
types: [labeled]
# The default for `pull_request` is to trigger on `synchronize`, `opened` and `reopened`.
# We also add `labeled` here so that the workflow triggers when a label is initially added.
types:
- labeled
- synchronize
- opened
- reopened
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
@@ -17,23 +23,22 @@ env:
RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: 1
REF_NAME: ${{ github.ref_name }}
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
jobs:
ty-ecosystem-analyzer:
name: Compute diagnostic diff
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 20
if: contains(github.event.label.name, 'ecosystem-analyzer')
if: contains( github.event.pull_request.labels.*.name, 'ecosystem-analyzer')
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
with:
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
@@ -67,7 +72,7 @@ jobs:
cd ..
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@55df3c868f3fa9ab34cff0498dd6106722aac205"
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@2e1816eac09c90140b1ba51d19afc5f59da460f5"
ecosystem-analyzer \
--repository ruff \
@@ -112,39 +117,30 @@ jobs:
cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY"
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
id: deploy
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- name: "Upload full report"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages deploy dist --project-name=ty-ecosystem --branch ${{ github.head_ref }} --commit-hash ${GITHUB_SHA}
- name: "Append deployment URL"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
env:
DEPLOYMENT_URL: ${{ steps.deploy.outputs.pages-deployment-alias-url }}
run: |
echo >> comment.md
echo "**[Full report with detailed diff]($DEPLOYMENT_URL/diff)** ([timing results]($DEPLOYMENT_URL/timing))" >> comment.md
name: full-report
path: dist/
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- name: Upload comment
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: comment.md
path: comment.md
- name: Upload diagnostics diff
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: diff.html
path: dist/diff.html
- name: Upload timing diff
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: timing.html
path: dist/timing.html

View File

@@ -1,3 +1,7 @@
# This workflow is a cron job that generates a report describing
# all diagnostics ty emits across the whole ecosystem. The report
# is uploaded to https://ty-ecosystem-ext.pages.dev/ on a weekly basis.
name: ty ecosystem-report
permissions: {}
@@ -14,7 +18,6 @@ env:
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: 1
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
jobs:
ty-ecosystem-report:
@@ -22,21 +25,21 @@ jobs:
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
with:
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
enable-cache: true
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
workspaces: "ruff"
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
lookup-only: false
- name: Install Rust toolchain
run: rustup show
@@ -52,7 +55,7 @@ jobs:
cd ..
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@55df3c868f3fa9ab34cff0498dd6106722aac205"
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@2e1816eac09c90140b1ba51d19afc5f59da460f5"
ecosystem-analyzer \
--verbose \
@@ -70,11 +73,10 @@ jobs:
ecosystem-diagnostics.json \
--output dist/index.html
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
id: deploy
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
# NOTE: astral-sh-bot uses this artifact to publish the ecosystem report.
# Make sure to update the bot if you rename the artifact.
- name: "Upload ecosystem report"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages deploy dist --project-name=ty-ecosystem --branch main --commit-hash ${GITHUB_SHA}
name: full-report
path: dist/

View File

@@ -6,6 +6,11 @@ on:
pull_request:
paths:
- "crates/ty*/**"
- "!crates/ty_ide/**"
- "!crates/ty_server/**"
- "!crates/ty_test/**"
- "!crates/ty_completion_eval/**"
- "!crates/ty_wasm/**"
- "crates/ruff_db"
- "crates/ruff_python_ast"
- "crates/ruff_python_parser"
@@ -32,13 +37,13 @@ jobs:
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
repository: python/typing
ref: ${{ env.CONFORMANCE_SUITE_COMMIT }}
@@ -54,9 +59,6 @@ jobs:
- name: Compute diagnostic diff
shell: bash
env:
# TODO: Remove this once we fixed the remaining panics in the conformance suite.
TY_MAX_PARALLELISM: 1
run: |
RUFF_DIR="$GITHUB_WORKSPACE/ruff"
@@ -99,7 +101,7 @@ jobs:
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- name: Upload diff
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: typing_conformance_diagnostics_diff
path: typing_conformance_diagnostics.diff
@@ -107,7 +109,7 @@ jobs:
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- name: Upload conformance suite commit
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: conformance-suite-commit
path: conformance-suite-commit

23
.github/zizmor.yml vendored
View File

@@ -1,23 +0,0 @@
# Configuration for the zizmor static analysis tool, run via pre-commit in CI
# https://docs.zizmor.sh/configuration/
#
# TODO: can we remove the ignores here so that our workflows are more secure?
rules:
cache-poisoning:
ignore:
- build-docker.yml
excessive-permissions:
# it's hard to test what the impact of removing these ignores would be
# without actually running the release workflow...
ignore:
- build-docker.yml
- publish-docs.yml
secrets-inherit:
# `cargo dist` makes extensive use of `secrets: inherit`,
# and we can't easily fix that until an upstream release changes that.
disable: true
template-injection:
ignore:
# like with `secrets-inherit`, `cargo dist` introduces some
# template injections. We've manually audited these usages for safety.
- release.yml

View File

@@ -22,7 +22,7 @@ exclude: |
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: check-merge-conflict
@@ -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
@@ -46,7 +46,7 @@ repos:
)$
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.45.0
rev: v0.47.0
hooks:
- id: markdownlint-fix
exclude: |
@@ -56,7 +56,7 @@ repos:
)$
- repo: https://github.com/adamchainz/blacken-docs
rev: 1.19.1
rev: 1.20.0
hooks:
- id: blacken-docs
language: python # means renovate will also update `additional_dependencies`
@@ -67,10 +67,10 @@ repos:
.*?invalid(_.+)*_syntax\.md
)$
additional_dependencies:
- black==25.1.0
- black==25.12.0
- repo: https://github.com/crate-ci/typos
rev: v1.34.0
rev: v1.40.0
hooks:
- id: typos
@@ -84,7 +84,7 @@ repos:
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.7
rev: v0.14.10
hooks:
- id: ruff-format
- id: ruff-check
@@ -94,7 +94,7 @@ repos:
# Prettier
- repo: https://github.com/rbubley/mirrors-prettier
rev: v3.6.2
rev: v3.7.4
hooks:
- id: prettier
types: [yaml]
@@ -102,19 +102,19 @@ repos:
# zizmor detects security vulnerabilities in GitHub Actions workflows.
# Additional configuration for the tool is found in `.github/zizmor.yml`
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.16.0
rev: v1.19.0
hooks:
- id: zizmor
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.33.2
rev: 0.36.0
hooks:
- id: check-github-workflows
# `actionlint` hook, for verifying correct syntax in GitHub Actions workflows.
# Some additional configuration for `actionlint` can be found in `.github/actionlint.yaml`.
- repo: https://github.com/rhysd/actionlint
rev: v1.7.7
rev: v1.7.9
hooks:
- id: actionlint
stages:
@@ -129,12 +129,9 @@ repos:
# actionlint has a shellcheck integration which extracts shell scripts in `run:` steps from GitHub Actions
# and checks these with shellcheck. This is arguably its most useful feature,
# but the integration only works if shellcheck is installed
- "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.10.0"
- "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.11.1"
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.10.0.1
rev: v0.11.0.1
hooks:
- id: shellcheck
ci:
skip: [cargo-fmt, dev-generate-all]

View File

@@ -1,5 +1,54 @@
# Changelog
## 0.14.10
Released on 2025-12-18.
### Preview features
- [formatter] Fluent formatting of method chains ([#21369](https://github.com/astral-sh/ruff/pull/21369))
- [formatter] Keep lambda parameters on one line and parenthesize the body if it expands ([#21385](https://github.com/astral-sh/ruff/pull/21385))
- \[`flake8-implicit-str-concat`\] New rule to prevent implicit string concatenation in collections (`ISC004`) ([#21972](https://github.com/astral-sh/ruff/pull/21972))
- \[`flake8-use-pathlib`\] Make fixes unsafe when types change in compound statements (`PTH104`, `PTH105`, `PTH109`, `PTH115`) ([#22009](https://github.com/astral-sh/ruff/pull/22009))
- \[`refurb`\] Extend support for `Path.open` (`FURB101`, `FURB103`) ([#21080](https://github.com/astral-sh/ruff/pull/21080))
### Bug fixes
- \[`pyupgrade`\] Fix parsing named Unicode escape sequences (`UP032`) ([#21901](https://github.com/astral-sh/ruff/pull/21901))
### Rule changes
- \[`eradicate`\] Ignore `ruff:disable` and `ruff:enable` comments in `ERA001` ([#22038](https://github.com/astral-sh/ruff/pull/22038))
- \[`flake8-pytest-style`\] Allow `match` and `check` keyword arguments without an expected exception type (`PT010`) ([#21964](https://github.com/astral-sh/ruff/pull/21964))
- [syntax-errors] Annotated name cannot be global ([#20868](https://github.com/astral-sh/ruff/pull/20868))
### Documentation
- Add `uv` and `ty` to the Ruff README ([#21996](https://github.com/astral-sh/ruff/pull/21996))
- Document known lambda formatting deviations from Black ([#21954](https://github.com/astral-sh/ruff/pull/21954))
- Update `setup.md` ([#22024](https://github.com/astral-sh/ruff/pull/22024))
- \[`flake8-bandit`\] Fix broken link (`S704`) ([#22039](https://github.com/astral-sh/ruff/pull/22039))
### Other changes
- Fix playground Share button showing "Copied!" before clipboard copy completes ([#21942](https://github.com/astral-sh/ruff/pull/21942))
### Contributors
- [@dylwil3](https://github.com/dylwil3)
- [@charliecloudberry](https://github.com/charliecloudberry)
- [@charliermarsh](https://github.com/charliermarsh)
- [@chirizxc](https://github.com/chirizxc)
- [@ntBre](https://github.com/ntBre)
- [@zanieb](https://github.com/zanieb)
- [@amyreese](https://github.com/amyreese)
- [@hauntsaninja](https://github.com/hauntsaninja)
- [@11happy](https://github.com/11happy)
- [@mahiro72](https://github.com/mahiro72)
- [@MichaReiser](https://github.com/MichaReiser)
- [@phongddo](https://github.com/phongddo)
- [@PeterJCLaw](https://github.com/PeterJCLaw)
## 0.14.9
Released on 2025-12-11.

64
CLAUDE.md Normal file
View File

@@ -0,0 +1,64 @@
# 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
```
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.

295
Cargo.lock generated
View File

@@ -17,6 +17,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "alloca"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
dependencies = [
"cc",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
@@ -137,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"
@@ -208,12 +220,6 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "bincode"
version = "2.0.1"
@@ -280,6 +286,9 @@ name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
dependencies = [
"serde_core",
]
[[package]]
name = "bitvec"
@@ -351,9 +360,9 @@ dependencies = [
[[package]]
name = "camino"
version = "1.2.1"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609"
checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48"
dependencies = [
"serde_core",
]
@@ -657,7 +666,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -666,7 +675,7 @@ version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -767,18 +776,20 @@ dependencies = [
[[package]]
name = "criterion"
version = "0.7.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928"
checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf"
dependencies = [
"alloca",
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot 0.6.0",
"criterion-plot 0.8.1",
"itertools 0.13.0",
"num-traits",
"oorandom",
"page_size",
"regex",
"serde",
"serde_json",
@@ -798,9 +809,9 @@ dependencies = [
[[package]]
name = "criterion-plot"
version = "0.6.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338"
checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4"
dependencies = [
"cast",
"itertools 0.13.0",
@@ -1004,27 +1015,6 @@ dependencies = [
"crypto-common",
]
[[package]]
name = "dir-test"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62c013fe825864f3e4593f36426c1fa7a74f5603f13ca8d1af7a990c1cd94a79"
dependencies = [
"dir-test-macros",
]
[[package]]
name = "dir-test-macros"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d42f54d7b4a6bc2400fe5b338e35d1a335787585375322f49c5d5fe7b243da7e"
dependencies = [
"glob",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dirs"
version = "6.0.0"
@@ -1043,7 +1033,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.61.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -1135,7 +1125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -1658,9 +1648,9 @@ dependencies = [
[[package]]
name = "insta"
version = "1.43.2"
version = "1.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0"
checksum = "983e3b24350c84ab8a65151f537d67afbbf7153bb9f1110e03e9fa9b07f67a5c"
dependencies = [
"console 0.15.11",
"once_cell",
@@ -1670,6 +1660,7 @@ dependencies = [
"ron",
"serde",
"similar",
"tempfile",
]
[[package]]
@@ -1736,7 +1727,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -1790,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",
@@ -1800,14 +1791,14 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde_core",
"windows-sys 0.61.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",
@@ -1883,9 +1874,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.177"
version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "libcst"
@@ -1991,9 +1982,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.28"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "lsp-server"
@@ -2069,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"
@@ -2310,6 +2301,16 @@ dependencies = [
"memchr",
]
[[package]]
name = "page_size"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "parking_lot"
version = "0.12.4"
@@ -2633,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",
]
@@ -2897,18 +2898,21 @@ checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
[[package]]
name = "ron"
version = "0.7.1"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a"
checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32"
dependencies = [
"base64",
"bitflags 1.3.2",
"bitflags 2.10.0",
"once_cell",
"serde",
"serde_derive",
"typeid",
"unicode-ident",
]
[[package]]
name = "ruff"
version = "0.14.9"
version = "0.14.10"
dependencies = [
"anyhow",
"argfile",
@@ -3150,6 +3154,7 @@ dependencies = [
"salsa",
"schemars",
"serde",
"ty_module_resolver",
"ty_python_semantic",
"zip",
]
@@ -3166,7 +3171,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.14.9"
version = "0.14.10"
dependencies = [
"aho-corasick",
"anyhow",
@@ -3244,7 +3249,6 @@ name = "ruff_memory_usage"
version = "0.0.0"
dependencies = [
"get-size2",
"ordermap",
]
[[package]]
@@ -3525,7 +3529,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.14.9"
version = "0.14.10"
dependencies = [
"console_error_panic_hook",
"console_log",
@@ -3615,15 +3619,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.61.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -3640,8 +3644,8 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "salsa"
version = "0.24.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
version = "0.25.2"
source = "git+https://github.com/salsa-rs/salsa.git?rev=309c249088fdeef0129606fa34ec2eefc74736ff#309c249088fdeef0129606fa34ec2eefc74736ff"
dependencies = [
"boxcar",
"compact_str",
@@ -3665,13 +3669,13 @@ dependencies = [
[[package]]
name = "salsa-macro-rules"
version = "0.24.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
version = "0.25.2"
source = "git+https://github.com/salsa-rs/salsa.git?rev=309c249088fdeef0129606fa34ec2eefc74736ff#309c249088fdeef0129606fa34ec2eefc74736ff"
[[package]]
name = "salsa-macros"
version = "0.24.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
version = "0.25.2"
source = "git+https://github.com/salsa-rs/salsa.git?rev=309c249088fdeef0129606fa34ec2eefc74736ff#309c249088fdeef0129606fa34ec2eefc74736ff"
dependencies = [
"proc-macro2",
"quote",
@@ -3690,9 +3694,9 @@ dependencies = [
[[package]]
name = "schemars"
version = "1.0.5"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1317c3bf3e7df961da95b0a56a172a02abead31276215a0497241a7624b487ce"
checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2"
dependencies = [
"dyn-clone",
"ref-cast",
@@ -3703,9 +3707,9 @@ dependencies = [
[[package]]
name = "schemars_derive"
version = "1.0.5"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f760a6150d45dd66ec044983c124595ae76912e77ed0b44124cb3e415cce5d9"
checksum = "4908ad288c5035a8eb12cfdf0d49270def0a268ee162b75eeee0f85d155a7c45"
dependencies = [
"proc-macro2",
"quote",
@@ -3779,15 +3783,15 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.145"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
"zmij",
]
[[package]]
@@ -3803,9 +3807,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
dependencies = [
"serde_core",
]
@@ -3821,9 +3825,9 @@ dependencies = [
[[package]]
name = "serde_with"
version = "3.15.1"
version = "3.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04"
checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7"
dependencies = [
"serde_core",
"serde_with_macros",
@@ -3831,9 +3835,9 @@ dependencies = [
[[package]]
name = "serde_with_macros"
version = "3.15.1"
version = "3.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955"
checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
dependencies = [
"darling",
"proc-macro2",
@@ -3983,9 +3987,9 @@ dependencies = [
[[package]]
name = "supports-hyperlinks"
version = "3.1.0"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b"
checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91"
[[package]]
name = "syn"
@@ -4017,15 +4021,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.61.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -4219,9 +4223,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml"
version = "0.9.8"
version = "0.9.10+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48"
dependencies = [
"indexmap",
"serde_core",
@@ -4234,9 +4238,9 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.7.3"
version = "0.7.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
dependencies = [
"serde_core",
]
@@ -4255,24 +4259,24 @@ dependencies = [
[[package]]
name = "toml_parser"
version = "1.0.4"
version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.0.4"
version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
[[package]]
name = "tracing"
version = "0.1.43"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"log",
"pin-project-lite",
@@ -4293,9 +4297,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.35"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
"valuable",
@@ -4314,9 +4318,9 @@ dependencies = [
[[package]]
name = "tracing-indicatif"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04d4e11e0e27acef25a47f27e9435355fecdc488867fa2bc90e75b0700d2823d"
checksum = "e1ef6990e0438749f0080573248e96631171a0b5ddfddde119aa5ba8c3a9c47e"
dependencies = [
"indicatif",
"tracing",
@@ -4396,6 +4400,7 @@ dependencies = [
"tracing-flame",
"tracing-subscriber",
"ty_combine",
"ty_module_resolver",
"ty_project",
"ty_python_semantic",
"ty_server",
@@ -4410,7 +4415,6 @@ dependencies = [
"ordermap",
"ruff_db",
"ruff_python_ast",
"ty_python_semantic",
]
[[package]]
@@ -4428,8 +4432,8 @@ dependencies = [
"tempfile",
"toml",
"ty_ide",
"ty_module_resolver",
"ty_project",
"ty_python_semantic",
"walkdir",
]
@@ -4459,11 +4463,35 @@ dependencies = [
"salsa",
"smallvec",
"tracing",
"ty_module_resolver",
"ty_project",
"ty_python_semantic",
"ty_vendored",
]
[[package]]
name = "ty_module_resolver"
version = "0.0.0"
dependencies = [
"anyhow",
"camino",
"compact_str",
"get-size2",
"insta",
"ruff_db",
"ruff_memory_usage",
"ruff_python_ast",
"ruff_python_stdlib",
"rustc-hash",
"salsa",
"strum",
"strum_macros",
"tempfile",
"thiserror 2.0.17",
"tracing",
"ty_vendored",
]
[[package]]
name = "ty_project"
version = "0.0.0"
@@ -4498,6 +4526,7 @@ dependencies = [
"toml",
"tracing",
"ty_combine",
"ty_module_resolver",
"ty_python_semantic",
"ty_static",
"ty_vendored",
@@ -4513,7 +4542,7 @@ dependencies = [
"camino",
"colored 3.0.0",
"compact_str",
"dir-test",
"datatest-stable",
"drop_bomb",
"get-size2",
"glob",
@@ -4550,11 +4579,11 @@ dependencies = [
"strsim",
"strum",
"strum_macros",
"tempfile",
"test-case",
"thiserror 2.0.17",
"tracing",
"ty_python_semantic",
"ty_combine",
"ty_module_resolver",
"ty_static",
"ty_test",
"ty_vendored",
@@ -4593,6 +4622,7 @@ dependencies = [
"tracing-subscriber",
"ty_combine",
"ty_ide",
"ty_module_resolver",
"ty_project",
"ty_python_semantic",
]
@@ -4633,6 +4663,7 @@ dependencies = [
"thiserror 2.0.17",
"toml",
"tracing",
"ty_module_resolver",
"ty_python_semantic",
"ty_static",
"ty_vendored",
@@ -4680,6 +4711,12 @@ version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
version = "1.18.0"
@@ -4839,9 +4876,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.18.1"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
dependencies = [
"getrandom 0.3.4",
"js-sys",
@@ -4852,9 +4889,9 @@ dependencies = [
[[package]]
name = "uuid-macro-internal"
version = "1.18.1"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9384a660318abfbd7f8932c34d67e4d1ec511095f95972ddc01e19d7ba8413f"
checksum = "39d11901c36b3650df7acb0f9ebe624f35b5ac4e1922ecd3c57f444648429594"
dependencies = [
"proc-macro2",
"quote",
@@ -5074,15 +5111,37 @@ dependencies = [
"glob",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.0",
"windows-sys 0.52.0",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.62.0"
@@ -5466,6 +5525,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

@@ -45,6 +45,7 @@ ty = { path = "crates/ty" }
ty_combine = { path = "crates/ty_combine" }
ty_completion_eval = { path = "crates/ty_completion_eval" }
ty_ide = { path = "crates/ty_ide" }
ty_module_resolver = { path = "crates/ty_module_resolver" }
ty_project = { path = "crates/ty_project", default-features = false }
ty_python_semantic = { path = "crates/ty_python_semantic" }
ty_server = { path = "crates/ty_server" }
@@ -57,8 +58,8 @@ anstream = { version = "0.6.18" }
anstyle = { version = "1.0.10" }
anyhow = { version = "1.0.80" }
arc-swap = { version = "1.7.1" }
assert_fs = { version = "1.1.0" }
argfile = { version = "0.2.0" }
assert_fs = { version = "1.1.0" }
bincode = { version = "2.0.0" }
bitflags = { version = "2.5.0" }
bitvec = { version = "1.0.1", default-features = false, features = [
@@ -70,31 +71,31 @@ camino = { version = "1.1.7" }
clap = { version = "4.5.3", features = ["derive"] }
clap_complete_command = { version = "0.6.0" }
clearscreen = { version = "4.0.0" }
csv = { version = "1.3.1" }
divan = { package = "codspeed-divan-compat", version = "4.0.4" }
codspeed-criterion-compat = { version = "4.0.4", default-features = false }
colored = { version = "3.0.0" }
compact_str = "0.9.0"
console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "1.0.0" }
countme = { version = "3.0.1" }
compact_str = "0.9.0"
criterion = { version = "0.7.0", default-features = false }
criterion = { version = "0.8.0", default-features = false }
crossbeam = { version = "0.8.4" }
csv = { version = "1.3.1" }
dashmap = { version = "6.0.1" }
datatest-stable = { version = "0.3.3" }
dir-test = { version = "0.4.0" }
dunce = { version = "1.0.5" }
divan = { package = "codspeed-divan-compat", version = "4.0.4" }
drop_bomb = { version = "0.1.5" }
dunce = { version = "1.0.5" }
etcetera = { version = "0.11.0" }
fern = { version = "0.7.0" }
filetime = { version = "0.2.23" }
getrandom = { version = "0.3.1" }
get-size2 = { version = "0.7.3", features = [
"derive",
"smallvec",
"hashbrown",
"compact-str",
"ordermap"
] }
getrandom = { version = "0.3.1" }
glob = { version = "0.3.1" }
globset = { version = "0.4.14" }
globwalk = { version = "0.9.1" }
@@ -116,8 +117,8 @@ is-macro = { version = "0.3.5" }
is-wsl = { version = "0.4.0" }
itertools = { version = "0.14.0" }
jiff = { version = "0.2.0" }
js-sys = { version = "0.3.69" }
jod-thread = { version = "1.0.0" }
js-sys = { version = "0.3.69" }
libc = { version = "0.2.153" }
libcst = { version = "1.8.4", default-features = false }
log = { version = "0.4.17" }
@@ -139,6 +140,8 @@ pretty_assertions = "1.3.0"
proc-macro2 = { version = "1.0.79" }
pyproject-toml = { version = "0.13.4" }
quick-junit = { version = "0.5.0" }
quickcheck = { version = "1.0.3", default-features = false }
quickcheck_macros = { version = "1.0.0" }
quote = { version = "1.0.23" }
rand = { version = "0.9.0" }
rayon = { version = "1.10.0" }
@@ -147,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 = "55e5e7d32fa3fc189276f35bb04c9438f9aedbd1", default-features = false, features = [
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "309c249088fdeef0129606fa34ec2eefc74736ff", default-features = false, features = [
"compact_str",
"macros",
"salsa_unstable",
@@ -195,9 +198,9 @@ tryfn = { version = "0.2.1" }
typed-arena = { version = "2.0.2" }
unic-ucd-category = { version = "0.9" }
unicode-ident = { version = "1.0.12" }
unicode-normalization = { version = "0.1.23" }
unicode-width = { version = "0.2.0" }
unicode_names2 = { version = "1.2.2" }
unicode-normalization = { version = "0.1.23" }
url = { version = "2.5.0" }
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics"] }
walkdir = { version = "2.3.2" }
@@ -207,8 +210,13 @@ wild = { version = "2" }
zip = { version = "0.6.6", default-features = false }
[workspace.metadata.cargo-shear]
ignored = ["getrandom", "ruff_options_metadata", "uuid", "get-size2", "ty_completion_eval"]
ignored = [
"getrandom",
"ruff_options_metadata",
"uuid",
"get-size2",
"ty_completion_eval",
]
[workspace.lints.rust]
unsafe_code = "warn"
@@ -268,17 +276,10 @@ if_not_else = "allow"
# Diagnostics are not actionable: Enable once https://github.com/rust-lang/rust-clippy/issues/13774 is resolved.
large_stack_arrays = "allow"
[profile.release]
lto = "fat"
codegen-units = 16
# Profile to build a minimally sized binary for ruff/ty
[profile.minimal-size]
inherits = "release"
opt-level = "z"
codegen-units = 1
# Some crates don't change as much but benefit more from
# more expensive optimization passes, so we selectively
# decrease codegen-units in some cases.
@@ -289,6 +290,12 @@ codegen-units = 1
[profile.release.package.salsa]
codegen-units = 1
# Profile to build a minimally sized binary for ruff/ty
[profile.minimal-size]
inherits = "release"
opt-level = "z"
codegen-units = 1
[profile.dev.package.insta]
opt-level = 3

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.9/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.14.9/install.ps1 | iex"
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"
```
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.9
rev: v0.14.10
hooks:
# Run the linter.
- id: ruff-check

View File

@@ -4,6 +4,7 @@ extend-exclude = [
"crates/ty_vendored/vendor/**/*",
"**/resources/**/*",
"**/snapshots/**/*",
"crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/collection_literal.rs",
# Completion tests tend to have a lot of incomplete
# words naturally. It's annoying to have to make all
# of them actually words. So just ignore typos here.

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.14.9"
version = "0.14.10"
publish = true
authors = { workspace = true }
edition = { workspace = true }
@@ -12,6 +12,13 @@ license = { workspace = true }
readme = "../../README.md"
default-run = "ruff"
[package.metadata.cargo-shear]
# Used via macro expansion.
ignored = ["jiff"]
[package.metadata.dist]
dist = true
[dependencies]
ruff_cache = { workspace = true }
ruff_db = { workspace = true, default-features = false, features = ["os"] }
@@ -61,6 +68,12 @@ tracing = { workspace = true, features = ["log"] }
walkdir = { workspace = true }
wild = { workspace = true }
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
tikv-jemallocator = { workspace = true }
[target.'cfg(target_os = "windows")'.dependencies]
mimalloc = { workspace = true }
[dev-dependencies]
# Enable test rules during development
ruff_linter = { workspace = true, features = ["clap", "test-rules"] }
@@ -76,18 +89,5 @@ ruff_python_trivia = { workspace = true }
tempfile = { workspace = true }
test-case = { workspace = true }
[package.metadata.cargo-shear]
# Used via macro expansion.
ignored = ["jiff"]
[package.metadata.dist]
dist = true
[target.'cfg(target_os = "windows")'.dependencies]
mimalloc = { workspace = true }
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
tikv-jemallocator = { workspace = true }
[lints]
workspace = true

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,7 @@ fn notebook_basic() -> Result<()> {
}
----- stderr -----
"###);
"#);
});
Ok(())
@@ -765,7 +765,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 +778,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 -----

View File

@@ -39,7 +39,7 @@ inline-quotes = "single"
.arg("ruff.toml")
.args(["--stdin-filename", "test.py"])
.arg("-")
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r"
.pass_stdin(r#"a = "abcba".strip("aba")"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -76,18 +76,18 @@ inline-quotes = "single"
.arg("--config")
.arg("ruff.toml")
.arg("-")
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r"
success: false
exit_code: 1
----- stdout -----
-:1:5: Q000 [*] Double quotes found but single quotes preferred
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
-:1:19: Q000 [*] Double quotes found but single quotes preferred
Found 3 errors.
[*] 2 fixable with the `--fix` option.
.pass_stdin(r#"a = "abcba".strip("aba")"#), @"
success: false
exit_code: 1
----- stdout -----
-:1:5: Q000 [*] Double quotes found but single quotes preferred
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
-:1:19: Q000 [*] Double quotes found but single quotes preferred
Found 3 errors.
[*] 2 fixable with the `--fix` option.
----- stderr -----
");
----- stderr -----
");
Ok(())
}
@@ -110,7 +110,7 @@ inline-quotes = "single"
.arg("--config")
.arg("ruff.toml")
.arg("-")
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r"
.pass_stdin(r#"a = "abcba".strip("aba")"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -150,7 +150,7 @@ inline-quotes = "single"
.arg("--config")
.arg("ruff.toml")
.arg("-")
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r"
.pass_stdin(r#"a = "abcba".strip("aba")"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -220,7 +220,7 @@ OTHER = "OTHER"
// Explicitly pass test.py, should be linted regardless of it being excluded by lint.exclude
.arg("test.py")
// Lint all other files in the directory, should respect the `exclude` and `lint.exclude` options
.arg("."), @r"
.arg("."), @"
success: false
exit_code: 1
----- stdout -----
@@ -259,16 +259,16 @@ exclude = ["main.py"]
.arg(".")
// Explicitly pass main.py, should be linted regardless of it being excluded by lint.exclude
.arg("main.py"),
@r"
success: false
exit_code: 1
----- stdout -----
main.py:1:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.
@"
success: false
exit_code: 1
----- stdout -----
main.py:1:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
"
----- stderr -----
"
);
Ok(())
@@ -299,7 +299,7 @@ from test import say_hy
if __name__ == "__main__":
say_hy("dear Ruff contributor")
"#), @r"
"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -340,7 +340,7 @@ max-line-length = 100
_ = "---------------------------------------------------------------------------亜亜亜亜亜亜"
# longer than 100
_ = "---------------------------------------------------------------------------亜亜亜亜亜亜亜亜亜亜亜亜亜亜"
"#), @r"
"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -381,7 +381,7 @@ from test import say_hy
if __name__ == "__main__":
say_hy("dear Ruff contributor")
"#), @r"
"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -422,7 +422,7 @@ from test import say_hy
if __name__ == "__main__":
say_hy("dear Ruff contributor")
"#), @r"
"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -461,7 +461,7 @@ ignore = ["D203", "D212"]
assert_cmd_snapshot!(fixture
.check_command()
.current_dir(fixture.root().join("subdirectory"))
, @r"
, @"
success: true
exit_code: 0
----- stdout -----
@@ -478,7 +478,7 @@ ignore = ["D203", "D212"]
fn nonexistent_config_file() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(["--config", "foo.toml", "."]), @r"
.args(["--config", "foo.toml", "."]), @"
success: false
exit_code: 2
----- stdout -----
@@ -501,7 +501,7 @@ fn nonexistent_config_file() {
fn config_override_rejected_if_invalid_toml() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(["--config", "foo = bar", "."]), @r"
.args(["--config", "foo = bar", "."]), @"
success: false
exit_code: 2
----- stdout -----
@@ -537,7 +537,7 @@ fn too_many_config_files() -> Result<()> {
.arg("ruff.toml")
.arg("--config")
.arg("ruff2.toml")
.arg("."), @r"
.arg("."), @"
success: false
exit_code: 2
----- stdout -----
@@ -556,7 +556,7 @@ fn too_many_config_files() -> Result<()> {
fn extend_passed_via_config_argument() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(["--config", "extend = 'foo.toml'", "."]), @r"
.args(["--config", "extend = 'foo.toml'", "."]), @"
success: false
exit_code: 2
----- stdout -----
@@ -588,17 +588,17 @@ extend = "ruff3.toml"
)?;
assert_cmd_snapshot!(fixture
.check_command(), @r"
success: false
exit_code: 2
----- stdout -----
.check_command(), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: Failed to load extended configuration `[TMP]/ruff3.toml` (`[TMP]/ruff.toml` extends `[TMP]/ruff2.toml` extends `[TMP]/ruff3.toml`)
Cause: Failed to read [TMP]/ruff3.toml
Cause: No such file or directory (os error 2)
");
----- stderr -----
ruff failed
Cause: Failed to load extended configuration `[TMP]/ruff3.toml` (`[TMP]/ruff.toml` extends `[TMP]/ruff2.toml` extends `[TMP]/ruff3.toml`)
Cause: Failed to read [TMP]/ruff3.toml
Cause: No such file or directory (os error 2)
");
Ok(())
}
@@ -627,7 +627,7 @@ extend = "ruff.toml"
assert_cmd_snapshot!(fixture
.check_command(),
@r"
@"
success: false
exit_code: 2
----- stdout -----
@@ -659,7 +659,7 @@ select = [E501]
assert_cmd_snapshot!(
fixture.check_command(),
@r"
@"
success: false
exit_code: 2
----- stdout -----
@@ -688,7 +688,7 @@ fn config_file_and_isolated() -> Result<()> {
.arg("--config")
.arg("ruff.toml")
.arg("--isolated")
.arg("."), @r"
.arg("."), @"
success: false
exit_code: 2
----- stdout -----
@@ -742,7 +742,7 @@ x = "longer_than_90_charactersssssssssssssssssssssssssssssssssssssssssssssssssss
.args(["--config", "lint.extend-select=['E501', 'F841']"])
.args(["--config", "lint.isort.combine-as-imports = false"])
.arg("-")
.pass_stdin(test_code), @r"
.pass_stdin(test_code), @"
success: false
exit_code: 1
----- stdout -----
@@ -761,7 +761,7 @@ fn valid_toml_but_nonexistent_option_provided_via_config_argument() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args([".", "--config", "extend-select=['F481']"]), // No such code as F481!
@r"
@"
success: false
exit_code: 2
----- stdout -----
@@ -788,7 +788,7 @@ fn each_toml_option_requires_a_new_flag_1() {
// commas can't be used to delimit different config overrides;
// you need a new --config flag for each override
.args([".", "--config", "extend-select=['F841'], line-length=90"]),
@r"
@"
success: false
exit_code: 2
----- stdout -----
@@ -819,7 +819,7 @@ fn each_toml_option_requires_a_new_flag_2() {
// spaces *also* can't be used to delimit different config overrides;
// you need a new --config flag for each override
.args([".", "--config", "extend-select=['F841'] line-length=90"]),
@r"
@"
success: false
exit_code: 2
----- stdout -----
@@ -886,7 +886,7 @@ fn value_given_to_table_key_is_not_inline_table_2() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args([".", "--config", r#"lint=123"#]),
@r"
@"
success: false
exit_code: 2
----- stdout -----
@@ -953,7 +953,7 @@ select=["E501"]
.arg("ruff.toml")
.args(["--config", "line-length=110"])
.arg("-")
.pass_stdin(test_code), @r"
.pass_stdin(test_code), @"
success: false
exit_code: 1
----- stdout -----
@@ -976,7 +976,7 @@ fn complex_config_setting_overridden_via_cli() -> Result<()> {
.args(["--config", "lint.per-file-ignores = {'generated.py' = ['N801']}"])
.args(["--stdin-filename", "generated.py"])
.arg("-")
.pass_stdin(test_code), @r"
.pass_stdin(test_code), @"
success: true
exit_code: 0
----- stdout -----
@@ -993,7 +993,7 @@ fn deprecated_config_option_overridden_via_cli() {
.args(STDIN_BASE_OPTIONS)
.args(["--config", "select=['N801']", "-"])
.pass_stdin("class lowercase: ..."),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1061,7 +1061,7 @@ include = ["*.ipy"]
.check_command()
.args(["--config", "ruff.toml"])
.args(["--extension", "ipy:ipynb"])
.arg("."), @r"
.arg("."), @"
success: false
exit_code: 1
----- stdout -----
@@ -1111,7 +1111,7 @@ external = ["AAA"]
.pass_stdin(r#"
# flake8: noqa: AAA101, BBB102
import os
"#), @r"
"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -1147,7 +1147,7 @@ required-version = "0.1.0"
.arg("-")
.pass_stdin(r#"
import os
"#), @r"
"#), @"
success: false
exit_code: 2
----- stdout -----
@@ -1184,7 +1184,7 @@ required-version = "{version}"
.arg("-")
.pass_stdin(r#"
import os
"#), @r"
"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -1222,7 +1222,7 @@ required-version = ">{version}"
.arg("-")
.pass_stdin(r#"
import os
"#), @r"
"#), @"
success: false
exit_code: 2
----- stdout -----
@@ -1252,7 +1252,7 @@ required-version = ">=0.1.0"
.arg("-")
.pass_stdin(r#"
import os
"#), @r"
"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -1289,7 +1289,7 @@ import os
def func():
x = 1
"#), @r"
"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -1323,7 +1323,7 @@ fn negated_per_file_ignores() -> Result<()> {
.arg("ruff.toml")
.arg("--select")
.arg("RUF901")
, @r"
, @"
success: false
exit_code: 1
----- stdout -----
@@ -1355,7 +1355,7 @@ fn negated_per_file_ignores_absolute() -> Result<()> {
.arg("ruff.toml")
.arg("--select")
.arg("RUF901")
, @r"
, @"
success: false
exit_code: 1
----- stdout -----
@@ -1389,7 +1389,7 @@ fn negated_per_file_ignores_overlap() -> Result<()> {
.arg("ruff.toml")
.arg("--select")
.arg("RUF901")
, @r"
, @"
success: true
exit_code: 0
----- stdout -----
@@ -1423,7 +1423,7 @@ import os # F401
def function():
import os # F811
print(os.name)
"#), @r"
"#), @"
success: true
exit_code: 0
----- stdout -----
@@ -1466,7 +1466,7 @@ import sys
.check_command()
.args(["--config", "ruff.toml"])
.arg("noqa.py"),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1482,7 +1482,7 @@ import sys
.args(["--config", "ruff.toml"])
.arg("noqa.py")
.args(["--preview"]),
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -1497,7 +1497,7 @@ import sys
.args(["--config", "ruff.toml"])
.arg("noqa.py")
.args(["--ignore-noqa", "--preview"]),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -1539,7 +1539,7 @@ def first_square():
.arg("-")
.pass_stdin(r#"
"#), @r"
"#), @"
success: true
exit_code: 0
----- stdout -----
@@ -1551,7 +1551,8 @@ def first_square():
let test_code =
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
insta::assert_snapshot!(test_code, @r"
insta::assert_snapshot!(test_code, @"
def first_square():
return [x * x for x in range(20)][0] # noqa: RUF015
");
@@ -1587,7 +1588,7 @@ def unused(x):
.arg("-")
.pass_stdin(r#"
"#), @r"
"#), @"
success: true
exit_code: 0
----- stdout -----
@@ -1599,7 +1600,8 @@ def unused(x):
let test_code =
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
insta::assert_snapshot!(test_code, @r"
insta::assert_snapshot!(test_code, @"
def unused(x): # noqa: ANN001, ANN201, D103
pass
");
@@ -1635,7 +1637,7 @@ import a
.arg("-")
.pass_stdin(r#"
"#), @r"
"#), @"
success: true
exit_code: 0
----- stdout -----
@@ -1647,7 +1649,8 @@ import a
let test_code =
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
insta::assert_snapshot!(test_code, @r"
insta::assert_snapshot!(test_code, @"
import z # noqa: I001
import c
import a
@@ -1684,7 +1687,7 @@ def unused(x): # noqa: ANN001, ARG001, D103
.arg("-")
.pass_stdin(r#"
"#), @r"
"#), @"
success: true
exit_code: 0
----- stdout -----
@@ -1696,7 +1699,8 @@ def unused(x): # noqa: ANN001, ARG001, D103
let test_code =
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
insta::assert_snapshot!(test_code, @r"
insta::assert_snapshot!(test_code, @"
def unused(x): # noqa: ANN001, ANN201, ARG001, D103
pass
");
@@ -1732,7 +1736,7 @@ import os
.arg("-")
.pass_stdin(r#"
"#), @r"
"#), @"
success: true
exit_code: 0
----- stdout -----
@@ -1743,7 +1747,8 @@ import os
let test_code =
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
insta::assert_snapshot!(test_code, @r"
insta::assert_snapshot!(test_code, @"
# ruff: noqa F401
import os
");
@@ -1779,7 +1784,7 @@ import os
.arg("-")
.pass_stdin(r#"
"#), @r"
"#), @"
success: true
exit_code: 0
----- stdout -----
@@ -1790,7 +1795,8 @@ import os
let test_code =
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
insta::assert_snapshot!(test_code, @r"
insta::assert_snapshot!(test_code, @"
# ruff: disable[F401]
import os
");
@@ -1831,7 +1837,7 @@ print(
.arg("-")
.pass_stdin(r#"
"#), @r"
"#), @"
success: true
exit_code: 0
----- stdout -----
@@ -1844,6 +1850,7 @@ print(
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
insta::assert_snapshot!(test_code, @r#"
print(
"""First line
second line
@@ -1886,7 +1893,7 @@ def first_square():
assert_cmd_snapshot!(fixture
.check_command()
.args(["--add-noqa"]), @r"
.args(["--add-noqa"]), @"
success: true
exit_code: 0
----- stdout -----
@@ -1915,7 +1922,7 @@ from foo import ( # noqa: F401
.check_command()
.arg("--add-noqa")
.arg("--select=F401")
.arg("noqa.py"), @r"
.arg("noqa.py"), @"
success: true
exit_code: 0
----- stdout -----
@@ -1942,7 +1949,7 @@ def foo():
.check_command()
.arg("--add-noqa=TODO: fix")
.arg("--select=F401,F841")
.arg("test.py"), @r"
.arg("test.py"), @"
success: true
exit_code: 0
----- stdout -----
@@ -1952,12 +1959,12 @@ def foo():
");
let content = fs::read_to_string(fixture.root().join("test.py"))?;
insta::assert_snapshot!(content, @r"
import os # noqa: F401 TODO: fix
insta::assert_snapshot!(content, @"
import os # noqa: F401 TODO: fix
def foo():
x = 1 # noqa: F841 TODO: fix
");
def foo():
x = 1 # noqa: F841 TODO: fix
");
Ok(())
}
@@ -1971,7 +1978,7 @@ fn add_noqa_with_newline_in_reason() -> Result<()> {
.check_command()
.arg("--add-noqa=line1\nline2")
.arg("--select=F401")
.arg("test.py"), @r###"
.arg("test.py"), @"
success: false
exit_code: 2
----- stdout -----
@@ -1979,7 +1986,7 @@ fn add_noqa_with_newline_in_reason() -> Result<()> {
----- stderr -----
ruff failed
Cause: --add-noqa <reason> cannot contain newline characters
"###);
");
Ok(())
}
@@ -2003,7 +2010,7 @@ select = ["UP006"]
.arg("pyproject.toml")
.args(["--stdin-filename", "test.py"])
.arg("-")
.pass_stdin(r#"from typing import List; foo: List[int]"#), @r"
.pass_stdin(r#"from typing import List; foo: List[int]"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -2030,7 +2037,7 @@ select = ["UP006"]
.arg("pyproject.toml")
.args(["--stdin-filename", "test.py"])
.arg("-")
.pass_stdin(r#"from typing import List; foo: List[int]"#), @r"
.pass_stdin(r#"from typing import List; foo: List[int]"#), @"
success: true
exit_code: 0
----- stdout -----
@@ -2061,7 +2068,7 @@ select = ["UP006"]
.arg("pyproject.toml")
.args(["--stdin-filename", "test.py"])
.arg("-")
.pass_stdin(r#"from typing import List; foo: List[int]"#), @r"
.pass_stdin(r#"from typing import List; foo: List[int]"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -2094,7 +2101,7 @@ select = ["UP006"]
.arg("pyproject.toml")
.args(["--stdin-filename", "test.py"])
.arg("-")
.pass_stdin(r#"from typing import List; foo: List[int]"#), @r"
.pass_stdin(r#"from typing import List; foo: List[int]"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -2127,7 +2134,7 @@ select = ["UP006"]
.arg("pyproject.toml")
.args(["--stdin-filename", "test.py"])
.arg("-")
.pass_stdin(r#"from typing import List; foo: List[int]"#), @r"
.pass_stdin(r#"from typing import List; foo: List[int]"#), @"
success: false
exit_code: 1
----- stdout -----
@@ -2263,7 +2270,7 @@ requires-python = ">= 3.11"
.check_command()
.args(["--select","UP007"])
.arg(".")
, @r###"
, @"
success: false
exit_code: 1
----- stdout -----
@@ -2272,7 +2279,7 @@ requires-python = ">= 3.11"
[*] 1 fixable with the `--fix` option.
----- stderr -----
"###);
");
Ok(())
}
@@ -2348,7 +2355,7 @@ from typing import Union;foo: Union[int, str] = 1"#,
assert_cmd_snapshot!(fixture
.check_command()
.arg("test.py")
, @r"
, @"
success: false
exit_code: 1
----- stdout -----
@@ -2591,7 +2598,7 @@ fn checks_notebooks_in_stable() -> anyhow::Result<()> {
.check_command()
.arg("--select")
.arg("F401")
, @r"
, @"
success: false
exit_code: 1
----- stdout -----
@@ -2619,7 +2626,7 @@ fn nested_implicit_namespace_package() -> Result<()> {
.check_command()
.arg("--select")
.arg("INP")
, @r"
, @"
success: true
exit_code: 0
----- stdout -----
@@ -2633,7 +2640,7 @@ fn nested_implicit_namespace_package() -> Result<()> {
.arg("--select")
.arg("INP")
.arg("--preview")
, @r"
, @"
success: false
exit_code: 1
----- stdout -----
@@ -2764,7 +2771,7 @@ fn flake8_import_convention_nfkc_normalization() -> Result<()> {
.arg("ruff.toml")
.arg("-")
.pass_stdin("")
, @r###"
, @"
success: false
exit_code: 2
----- stdout -----
@@ -2772,7 +2779,7 @@ fn flake8_import_convention_nfkc_normalization() -> Result<()> {
----- stderr -----
ruff failed
Cause: Invalid alias for module 'test.module': alias normalizes to '__debug__', which is not allowed.
"###);
");
Ok(())
}
@@ -2822,7 +2829,7 @@ fn pyupgrade_up026_respects_isort_required_import_fix() {
.arg("--fix")
.arg("--no-cache")
.pass_stdin("1\n"),
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -2849,7 +2856,7 @@ fn pyupgrade_up026_respects_isort_required_import_from_fix() {
.arg("--fix")
.arg("--no-cache")
.pass_stdin("from mock import mock\n"),
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -2883,7 +2890,7 @@ d: Literal[None,] | Literal[None]
.arg("--preview")
.arg("--diff")
.arg("-")
.pass_stdin(snippet), @r"
.pass_stdin(snippet), @"
success: false
exit_code: 1
----- stdout -----
@@ -2937,7 +2944,7 @@ def func(t: _T) -> _T:
return x
"#
),
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -2976,7 +2983,7 @@ class Foo[_T, __T]:
pass
"#
),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3041,7 +3048,7 @@ fn a005_module_shadowing_strict() -> Result<()> {
.arg("--config")
.arg(r#"lint.flake8-builtins.strict-checking = true"#)
.args(["--select", "A005"]),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3069,7 +3076,7 @@ fn a005_module_shadowing_non_strict() -> Result<()> {
.arg("--config")
.arg(r#"lint.flake8-builtins.strict-checking = false"#)
.args(["--select", "A005"]),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3094,7 +3101,7 @@ fn a005_module_shadowing_strict_default() -> Result<()> {
assert_cmd_snapshot!(fixture.check_command()
.args(["--select", "A005"]),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3127,7 +3134,7 @@ T = TypeVar("T")
class A(Generic[T]):
var: T
"#),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3157,7 +3164,7 @@ T = TypeVar("T")
class A(Generic[T]):
var: T
"#),
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -3177,7 +3184,7 @@ fn walrus_before_py38() {
.arg("--target-version=py38")
.arg("-")
.pass_stdin(r#"(x := 1)"#),
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -3195,7 +3202,7 @@ fn walrus_before_py38() {
.arg("--preview")
.arg("-")
.pass_stdin(r#"(x := 1)"#),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3222,7 +3229,7 @@ match 2:
print("it's one")
"#
),
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -3245,7 +3252,7 @@ match 2:
print("it's one")
"#
),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3270,7 +3277,7 @@ match 2:
print("it's one")
"#
),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3296,7 +3303,7 @@ fn cache_syntax_errors() -> Result<()> {
assert_cmd_snapshot!(
cmd,
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3309,7 +3316,7 @@ fn cache_syntax_errors() -> Result<()> {
// this should *not* be cached, like normal parse errors
assert_cmd_snapshot!(
cmd,
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3345,14 +3352,14 @@ fn cookiecutter_globbing() -> Result<()> {
assert_cmd_snapshot!(fixture
.check_command()
.arg("--select=F811"), @r"
success: true
exit_code: 0
----- stdout -----
All checks passed!
.arg("--select=F811"), @"
success: true
exit_code: 0
----- stdout -----
All checks passed!
----- stderr -----
");
----- stderr -----
");
// after removing the config file with the ignore, F811 applies, so the glob worked above
fs::remove_file(
@@ -3363,7 +3370,7 @@ fn cookiecutter_globbing() -> Result<()> {
assert_cmd_snapshot!(fixture
.check_command()
.arg("--select=F811"), @r"
.arg("--select=F811"), @"
success: false
exit_code: 1
----- stdout -----
@@ -3388,15 +3395,15 @@ fn cookiecutter_globbing_no_project_root() -> Result<()> {
assert_cmd_snapshot!(fixture
.check_command()
.current_dir(fixture.root().join("{{cookiecutter.repo_name}}"))
.args(["--extend-per-file-ignores", "generated.py:Q"]), @r"
success: true
exit_code: 0
----- stdout -----
All checks passed!
.args(["--extend-per-file-ignores", "generated.py:Q"]), @"
success: true
exit_code: 0
----- stdout -----
All checks passed!
----- stderr -----
warning: No Python files found under the given path(s)
");
----- stderr -----
warning: No Python files found under the given path(s)
");
Ok(())
}
@@ -3417,7 +3424,7 @@ fn semantic_syntax_errors() -> Result<()> {
assert_cmd_snapshot!(
cmd,
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3431,7 +3438,7 @@ fn semantic_syntax_errors() -> Result<()> {
// this should *not* be cached, like normal parse errors
assert_cmd_snapshot!(
cmd,
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3449,7 +3456,7 @@ fn semantic_syntax_errors() -> Result<()> {
.arg("--preview")
.arg("-")
.pass_stdin(contents),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3483,7 +3490,7 @@ class Foo:
.arg("--target-version=py39")
.arg("-")
.pass_stdin(contents),
@r"
@"
success: true
exit_code: 0
----- stdout -----
@@ -3581,7 +3588,7 @@ nested_optional: Optional[Optional[Optional[str]]] = None
.args(["--select", "UP045", "--diff", "--target-version", "py312"])
.arg("-")
.pass_stdin(contents),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3606,7 +3613,7 @@ fn show_fixes_in_full_output_with_preview_enabled() {
.arg("--preview")
.arg("-")
.pass_stdin("import math"),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3637,7 +3644,7 @@ fn rule_panic_mixed_results_concise() -> Result<()> {
fixture.check_command()
.args(["--select", "RUF9", "--preview"])
.args(["normal.py", "panic.py"]),
@r"
@"
success: false
exit_code: 2
----- stdout -----
@@ -3672,7 +3679,7 @@ fn rule_panic_mixed_results_full() -> Result<()> {
fixture.command()
.args(["check", "--select", "RUF9", "--preview", "--output-format=full", "--no-cache"])
.args(["normal.py", "panic.py"]),
@r"
@"
success: false
exit_code: 2
----- stdout -----
@@ -3781,7 +3788,7 @@ fn supported_file_extensions() -> Result<()> {
fixture.check_command()
.args(["--select", "F401"])
.arg("src"),
@r"
@"
success: false
exit_code: 1
----- stdout -----
@@ -3861,7 +3868,7 @@ fn supported_file_extensions_preview_enabled() -> Result<()> {
fixture.check_command()
.args(["--select", "F401", "--preview"])
.arg("src"),
@r"
@"
success: false
exit_code: 1
----- stdout -----

View File

@@ -14,6 +14,6 @@ info:
success: false
exit_code: 1
----- stdout -----
::error title=Ruff (unformatted),file=[TMP]/input.py,line=1,col=1,endLine=2,endColumn=1::input.py:1:1: unformatted: File would be reformatted
::error title=Ruff (unformatted),file=[TMP]/input.py,line=1,endLine=2::input.py:1:1: unformatted: File would be reformatted
----- stderr -----

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

@@ -125,7 +125,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.functions_names = [
linter.flake8_gettext.function_names = [
_,
gettext,
ngettext,
@@ -261,6 +261,7 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -127,7 +127,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.functions_names = [
linter.flake8_gettext.function_names = [
_,
gettext,
ngettext,
@@ -263,6 +263,7 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -129,7 +129,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.functions_names = [
linter.flake8_gettext.function_names = [
_,
gettext,
ngettext,
@@ -265,6 +265,7 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -129,7 +129,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.functions_names = [
linter.flake8_gettext.function_names = [
_,
gettext,
ngettext,
@@ -265,6 +265,7 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -126,7 +126,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.functions_names = [
linter.flake8_gettext.function_names = [
_,
gettext,
ngettext,
@@ -262,6 +262,7 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -126,7 +126,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.functions_names = [
linter.flake8_gettext.function_names = [
_,
gettext,
ngettext,
@@ -262,6 +262,7 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -125,7 +125,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.functions_names = [
linter.flake8_gettext.function_names = [
_,
gettext,
ngettext,
@@ -261,6 +261,7 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -125,7 +125,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.functions_names = [
linter.flake8_gettext.function_names = [
_,
gettext,
ngettext,
@@ -261,6 +261,7 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -125,7 +125,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.functions_names = [
linter.flake8_gettext.function_names = [
_,
gettext,
ngettext,
@@ -261,6 +261,7 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

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

@@ -238,7 +238,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.functions_names = [
linter.flake8_gettext.function_names = [
_,
gettext,
ngettext,
@@ -374,6 +374,7 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

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

@@ -12,10 +12,6 @@ license = "MIT OR Apache-2.0"
[lib]
[features]
default = []
testing-colors = []
[dependencies]
anstyle = { workspace = true }
memchr = { workspace = true }
@@ -23,12 +19,17 @@ unicode-width = { workspace = true }
[dev-dependencies]
ruff_annotate_snippets = { workspace = true, features = ["testing-colors"] }
anstream = { workspace = true }
serde = { workspace = true, features = ["derive"] }
snapbox = { workspace = true, features = ["diff", "term-svg", "cmd", "examples"] }
toml = { workspace = true }
tryfn = { workspace = true }
[features]
default = []
testing-colors = []
[[test]]
name = "fixtures"
harness = false

View File

@@ -16,77 +16,80 @@ bench = false
test = false
doctest = false
[[bench]]
name = "linter"
harness = false
required-features = ["instrumented"]
[[bench]]
name = "lexer"
harness = false
required-features = ["instrumented"]
[[bench]]
name = "parser"
harness = false
required-features = ["instrumented"]
[[bench]]
name = "formatter"
harness = false
required-features = ["instrumented"]
[[bench]]
name = "ty"
harness = false
required-features = ["instrumented"]
[[bench]]
name = "ty_walltime"
harness = false
required-features = ["walltime"]
[dependencies]
ruff_db = { workspace = true, features = ["testing"] }
ruff_python_ast = { workspace = true }
ruff_linter = { workspace = true, optional = true }
ruff_python_ast = { workspace = true }
ruff_python_formatter = { workspace = true, optional = true }
ruff_python_parser = { workspace = true, optional = true }
ruff_python_trivia = { workspace = true, optional = true }
ty_project = { workspace = true, optional = true }
divan = { workspace = true, optional = true }
anyhow = { workspace = true }
codspeed-criterion-compat = { workspace = true, default-features = false, optional = true }
criterion = { workspace = true, default-features = false, optional = true }
divan = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true }
tracing = { workspace = true }
[lints]
workspace = true
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
tikv-jemallocator = { workspace = true, optional = true }
[target.'cfg(target_os = "windows")'.dependencies]
mimalloc = { workspace = true, optional = true }
[dev-dependencies]
rayon = { workspace = true }
rustc-hash = { workspace = true }
[features]
default = ["instrumented", "walltime"]
# Enables the benchmark that should only run with codspeed's instrumented runner
instrumented = [
default = ["ty_instrumented", "ty_walltime", "ruff_instrumented"]
# Enables the ruff instrumented benchmarks
ruff_instrumented = [
"criterion",
"ruff_linter",
"ruff_python_formatter",
"ruff_python_parser",
"ruff_python_trivia",
"ty_project",
"mimalloc",
"tikv-jemallocator",
]
# Enables the ty instrumented benchmarks
ty_instrumented = ["criterion", "ty_project", "ruff_python_trivia"]
codspeed = ["codspeed-criterion-compat"]
# Enables benchmark that should only run with codspeed's walltime runner.
walltime = ["ruff_db/os", "ty_project", "divan"]
# Enables the ty_walltime benchmarks
ty_walltime = ["ruff_db/os", "ty_project", "divan"]
[target.'cfg(target_os = "windows")'.dev-dependencies]
mimalloc = { workspace = true }
[[bench]]
name = "linter"
harness = false
required-features = ["ruff_instrumented"]
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dev-dependencies]
tikv-jemallocator = { workspace = true }
[[bench]]
name = "lexer"
harness = false
required-features = ["ruff_instrumented"]
[dev-dependencies]
rustc-hash = { workspace = true }
rayon = { workspace = true }
[[bench]]
name = "parser"
harness = false
required-features = ["ruff_instrumented"]
[[bench]]
name = "formatter"
harness = false
required-features = ["ruff_instrumented"]
[[bench]]
name = "ty"
harness = false
required-features = ["ty_instrumented"]
[[bench]]
name = "ty_walltime"
harness = false
required-features = ["ty_walltime"]
[lints]
workspace = true

View File

@@ -557,6 +557,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,
@@ -717,6 +771,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

@@ -194,7 +194,7 @@ static SYMPY: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
13100,
13116,
);
static TANJUN: Benchmark = Benchmark::new(
@@ -235,30 +235,55 @@ fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
});
}
#[bench(args=[&ALTAIR, &FREQTRADE, &TANJUN], sample_size=2, sample_count=3)]
fn small(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
#[bench(sample_size = 2, sample_count = 3)]
fn altair(bencher: Bencher) {
run_single_threaded(bencher, &ALTAIR);
}
#[bench(args=[&COLOUR_SCIENCE, &PANDAS, &STATIC_FRAME], sample_size=1, sample_count=3)]
fn medium(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
#[bench(sample_size = 2, sample_count = 3)]
fn freqtrade(bencher: Bencher) {
run_single_threaded(bencher, &FREQTRADE);
}
#[bench(args=[&SYMPY, &PYDANTIC], sample_size=1, sample_count=2)]
fn large(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
#[bench(sample_size = 2, sample_count = 3)]
fn tanjun(bencher: Bencher) {
run_single_threaded(bencher, &TANJUN);
}
#[bench(args=[&ALTAIR], sample_size=3, sample_count=8)]
fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
#[bench(sample_size = 2, sample_count = 3)]
fn pydantic(bencher: Bencher) {
run_single_threaded(bencher, &PYDANTIC);
}
#[bench(sample_size = 1, sample_count = 3)]
fn static_frame(bencher: Bencher) {
run_single_threaded(bencher, &STATIC_FRAME);
}
#[bench(sample_size = 1, sample_count = 2)]
fn colour_science(bencher: Bencher) {
run_single_threaded(bencher, &COLOUR_SCIENCE);
}
#[bench(sample_size = 1, sample_count = 2)]
fn pandas(bencher: Bencher) {
run_single_threaded(bencher, &PANDAS);
}
#[bench(sample_size = 1, sample_count = 2)]
fn sympy(bencher: Bencher) {
run_single_threaded(bencher, &SYMPY);
}
#[bench(sample_size = 3, sample_count = 8)]
fn multithreaded(bencher: Bencher) {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
bencher
.with_inputs(|| benchmark.setup_iteration())
.with_inputs(|| ALTAIR.setup_iteration())
.bench_local_values(|db| {
thread_pool.install(|| {
check_project(&db, benchmark.project.name, benchmark.max_diagnostics);
check_project(&db, ALTAIR.project.name, ALTAIR.max_diagnostics);
db
})
});

View File

@@ -1,6 +1,6 @@
use std::path::PathBuf;
#[cfg(feature = "instrumented")]
#[cfg(any(feature = "ty_instrumented", feature = "ruff_instrumented"))]
pub mod criterion;
pub mod real_world_projects;

View File

@@ -11,11 +11,11 @@ repository = { workspace = true }
license = { workspace = true }
[dependencies]
filetime = { workspace = true }
glob = { workspace = true }
globset = { workspace = true }
itertools = { workspace = true }
regex = { workspace = true }
filetime = { workspace = true }
seahash = { workspace = true }
[dev-dependencies]

View File

@@ -48,12 +48,12 @@ tracing = { workspace = true }
tracing-subscriber = { workspace = true, optional = true }
zip = { workspace = true }
[target.'cfg(target_arch="wasm32")'.dependencies]
web-time = { version = "1.1.0" }
[target.'cfg(not(target_arch="wasm32"))'.dependencies]
etcetera = { workspace = true, optional = true }
[target.'cfg(target_arch="wasm32")'.dependencies]
web-time = { version = "1.1.0" }
[dev-dependencies]
insta = { workspace = true, features = ["filters"] }
tempfile = { workspace = true }

View File

@@ -0,0 +1,51 @@
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
/// Signals a [`CancellationToken`] that it should be canceled.
#[derive(Debug, Clone)]
pub struct CancellationTokenSource {
cancelled: Arc<AtomicBool>,
}
impl Default for CancellationTokenSource {
fn default() -> Self {
Self::new()
}
}
impl CancellationTokenSource {
pub fn new() -> Self {
Self {
cancelled: Arc::new(AtomicBool::new(false)),
}
}
pub fn is_cancellation_requested(&self) -> bool {
self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
}
/// Creates a new token that uses this source.
pub fn token(&self) -> CancellationToken {
CancellationToken {
cancelled: self.cancelled.clone(),
}
}
/// Requests cancellation for operations using this token.
pub fn cancel(&self) {
self.cancelled
.store(true, std::sync::atomic::Ordering::Relaxed);
}
}
/// Token signals whether an operation should be canceled.
#[derive(Debug, Clone)]
pub struct CancellationToken {
cancelled: Arc<AtomicBool>,
}
impl CancellationToken {
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
}
}

View File

@@ -1,4 +1,4 @@
use std::{fmt::Formatter, path::Path, sync::Arc};
use std::{borrow::Cow, fmt::Formatter, path::Path, sync::Arc};
use ruff_diagnostics::{Applicability, Fix};
use ruff_source_file::{LineColumn, SourceCode, SourceFile};
@@ -11,6 +11,7 @@ pub use self::render::{
ceil_char_boundary,
github::{DisplayGithubDiagnostics, GithubRenderer},
};
use crate::cancellation::CancellationToken;
use crate::{Db, files::File};
mod render;
@@ -410,11 +411,6 @@ impl Diagnostic {
self.id().is_invalid_syntax()
}
/// Returns the message body to display to the user.
pub fn body(&self) -> &str {
self.primary_message()
}
/// Returns the message of the first sub-diagnostic with a `Help` severity.
///
/// Note that this is used as the fix title/suggestion for some of Ruff's output formats, but in
@@ -1312,6 +1308,8 @@ pub struct DisplayDiagnosticConfig {
show_fix_diff: bool,
/// The lowest applicability that should be shown when reporting diagnostics.
fix_applicability: Applicability,
cancellation_token: Option<CancellationToken>,
}
impl DisplayDiagnosticConfig {
@@ -1385,6 +1383,20 @@ impl DisplayDiagnosticConfig {
pub fn fix_applicability(&self) -> Applicability {
self.fix_applicability
}
pub fn with_cancellation_token(
mut self,
token: Option<CancellationToken>,
) -> DisplayDiagnosticConfig {
self.cancellation_token = token;
self
}
pub fn is_canceled(&self) -> bool {
self.cancellation_token
.as_ref()
.is_some_and(|token| token.is_cancelled())
}
}
impl Default for DisplayDiagnosticConfig {
@@ -1398,6 +1410,7 @@ impl Default for DisplayDiagnosticConfig {
show_fix_status: false,
show_fix_diff: false,
fix_applicability: Applicability::Safe,
cancellation_token: None,
}
}
}
@@ -1474,6 +1487,15 @@ pub enum ConciseMessage<'a> {
Custom(&'a str),
}
impl<'a> ConciseMessage<'a> {
pub fn to_str(&self) -> Cow<'a, str> {
match self {
ConciseMessage::MainDiagnostic(s) | ConciseMessage::Custom(s) => Cow::Borrowed(s),
ConciseMessage::Both { .. } => Cow::Owned(self.to_string()),
}
}
}
impl std::fmt::Display for ConciseMessage<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
@@ -1490,6 +1512,16 @@ impl std::fmt::Display for ConciseMessage<'_> {
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for ConciseMessage<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_str(self)
}
}
/// A diagnostic message string.
///
/// This is, for all intents and purposes, equivalent to a `Box<str>`.

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

@@ -52,7 +52,7 @@ impl AzureRenderer<'_> {
f,
"code={code};]{body}",
code = diag.secondary_code_or_id(),
body = diag.body(),
body = diag.concise_message(),
)?;
}

View File

@@ -28,6 +28,10 @@ impl<'a> ConciseRenderer<'a> {
let sep = fmt_styled(":", stylesheet.separator);
for diag in diagnostics {
if self.config.is_canceled() {
return Ok(());
}
if let Some(span) = diag.primary_span() {
write!(
f,
@@ -133,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`
@@ -146,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`
@@ -160,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`
@@ -173,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
");
@@ -182,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
");
@@ -191,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

@@ -53,6 +53,10 @@ impl<'a> FullRenderer<'a> {
.hyperlink(stylesheet.hyperlink);
for diag in diagnostics {
if self.config.is_canceled() {
return Ok(());
}
let resolved = Resolved::new(self.resolver, diag, self.config);
let renderable = resolved.to_renderable(self.config.context);
for diag in renderable.diagnostics.iter() {

View File

@@ -49,14 +49,26 @@ impl<'a> GithubRenderer<'a> {
}
.unwrap_or_default();
write!(
f,
",line={row},col={column},endLine={end_row},endColumn={end_column}::",
row = start_location.line,
column = start_location.column,
end_row = end_location.line,
end_column = end_location.column,
)?;
// GitHub Actions workflow commands have constraints on error annotations:
// - `col` and `endColumn` cannot be set if `line` and `endLine` are different
// See: https://github.com/astral-sh/ruff/issues/22074
if start_location.line == end_location.line {
write!(
f,
",line={row},col={column},endLine={end_row},endColumn={end_column}::",
row = start_location.line,
column = start_location.column,
end_row = end_location.line,
end_column = end_location.column,
)?;
} else {
write!(
f,
",line={row},endLine={end_row}::",
row = start_location.line,
end_row = end_location.line,
)?;
}
write!(
f,
@@ -75,7 +87,7 @@ impl<'a> GithubRenderer<'a> {
write!(f, "{id}:", id = diagnostic.id())?;
}
writeln!(f, " {}", diagnostic.body())?;
writeln!(f, " {}", diagnostic.concise_message())?;
}
Ok(())

View File

@@ -98,7 +98,7 @@ impl Serialize for SerializedMessages<'_> {
}
fingerprints.insert(message_fingerprint);
let description = diagnostic.body();
let description = diagnostic.concise_message();
let check_name = diagnostic.secondary_code_or_id();
let severity = match diagnostic.severity() {
Severity::Info => "info",

View File

@@ -6,7 +6,7 @@ use ruff_notebook::NotebookIndex;
use ruff_source_file::{LineColumn, OneIndexed};
use ruff_text_size::Ranged;
use crate::diagnostic::{Diagnostic, DiagnosticSource, DisplayDiagnosticConfig};
use crate::diagnostic::{ConciseMessage, Diagnostic, DiagnosticSource, DisplayDiagnosticConfig};
use super::FileResolver;
@@ -101,7 +101,7 @@ pub(super) fn diagnostic_to_json<'a>(
JsonDiagnostic {
code: diagnostic.secondary_code_or_id(),
url: diagnostic.documentation_url(),
message: diagnostic.body(),
message: diagnostic.concise_message(),
fix,
cell: notebook_cell_index,
location: start_location.map(JsonLocation::from),
@@ -113,7 +113,7 @@ pub(super) fn diagnostic_to_json<'a>(
JsonDiagnostic {
code: diagnostic.secondary_code_or_id(),
url: diagnostic.documentation_url(),
message: diagnostic.body(),
message: diagnostic.concise_message(),
fix,
cell: notebook_cell_index,
location: Some(start_location.unwrap_or_default().into()),
@@ -226,7 +226,7 @@ pub(crate) struct JsonDiagnostic<'a> {
filename: Option<&'a str>,
fix: Option<JsonFix<'a>>,
location: Option<JsonLocation>,
message: &'a str,
message: ConciseMessage<'a>,
noqa_row: Option<OneIndexed>,
url: Option<&'a str>,
}

View File

@@ -56,17 +56,17 @@ impl<'a> JunitRenderer<'a> {
start_location: location,
} = diagnostic;
let mut status = TestCaseStatus::non_success(NonSuccessKind::Failure);
status.set_message(diagnostic.body());
status.set_message(diagnostic.concise_message().to_str());
if let Some(location) = location {
status.set_description(format!(
"line {row}, col {col}, {body}",
row = location.line,
col = location.column,
body = diagnostic.body()
body = diagnostic.concise_message()
));
} else {
status.set_description(diagnostic.body());
status.set_description(diagnostic.concise_message().to_str());
}
let code = diagnostic

View File

@@ -55,7 +55,7 @@ impl PylintRenderer<'_> {
f,
"{path}:{row}: [{code}] {body}",
path = filename,
body = diagnostic.body()
body = diagnostic.concise_message()
)?;
}

View File

@@ -5,7 +5,7 @@ use ruff_diagnostics::{Edit, Fix};
use ruff_source_file::{LineColumn, SourceCode};
use ruff_text_size::Ranged;
use crate::diagnostic::Diagnostic;
use crate::diagnostic::{ConciseMessage, Diagnostic};
use super::FileResolver;
@@ -76,7 +76,7 @@ fn diagnostic_to_rdjson<'a>(
let edits = diagnostic.fix().map(Fix::edits).unwrap_or_default();
RdjsonDiagnostic {
message: diagnostic.body(),
message: diagnostic.concise_message(),
location,
code: RdjsonCode {
value: diagnostic
@@ -155,7 +155,7 @@ struct RdjsonDiagnostic<'a> {
code: RdjsonCode<'a>,
#[serde(skip_serializing_if = "Option::is_none")]
location: Option<RdjsonLocation<'a>>,
message: &'a str,
message: ConciseMessage<'a>,
#[serde(skip_serializing_if = "Vec::is_empty")]
suggestions: Vec<RdjsonSuggestion<'a>>,
}

View File

@@ -2,5 +2,5 @@
source: crates/ruff_db/src/diagnostic/render/github.rs
expression: env.render_diagnostics(&diagnostics)
---
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=1,col=15,endLine=2,endColumn=1::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=3,col=12,endLine=4,endColumn=1::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=1,endLine=2::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=3,endLine=4::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline

View File

@@ -12,6 +12,7 @@ use std::hash::BuildHasherDefault;
use std::num::NonZeroUsize;
use ty_static::EnvVars;
pub mod cancellation;
pub mod diagnostic;
pub mod display;
pub mod file_revision;

View File

@@ -275,16 +275,16 @@ impl OsSystem {
/// instead of at least one system call for each component between `path` and `prefix`.
///
/// However, using `canonicalize` to resolve the path's casing doesn't work in two cases:
/// * if `path` is a symlink because `canonicalize` then returns the symlink's target and not the symlink's source path.
/// * on Windows: If `path` is a mapped network drive because `canonicalize` then returns the UNC path
/// (e.g. `Z:\` is mapped to `\\server\share` and `canonicalize` then returns `\\?\UNC\server\share`).
/// * if `path` is a symlink, `canonicalize` returns the symlink's target and not the symlink's source path.
/// * on Windows: If `path` is a mapped network drive, `canonicalize` returns the UNC path
/// (e.g. `Z:\` is mapped to `\\server\share` and `canonicalize` returns `\\?\UNC\server\share`).
///
/// Symlinks and mapped network drives should be rare enough that this fast path is worth trying first,
/// even if it comes at a cost for those rare use cases.
fn path_exists_case_sensitive_fast(&self, path: &SystemPath) -> Option<bool> {
// This is a more forgiving version of `dunce::simplified` that removes all `\\?\` prefixes on Windows.
// We use this more forgiving version because we don't intend on using either path for anything other than comparison
// and the prefix is only relevant when passing the path to other programs and its longer than 200 something
// and the prefix is only relevant when passing the path to other programs and it's longer than 200 something
// characters.
fn simplify_ignore_verbatim(path: &SystemPath) -> &SystemPath {
if cfg!(windows) {
@@ -298,9 +298,7 @@ impl OsSystem {
}
}
let simplified = simplify_ignore_verbatim(path);
let Ok(canonicalized) = simplified.as_std_path().canonicalize() else {
let Ok(canonicalized) = path.as_std_path().canonicalize() else {
// The path doesn't exist or can't be accessed. The path doesn't exist.
return Some(false);
};
@@ -309,12 +307,13 @@ impl OsSystem {
// The original path is valid UTF8 but the canonicalized path isn't. This definitely suggests
// that a symlink is involved. Fall back to the slow path.
tracing::debug!(
"Falling back to the slow case-sensitive path existence check because the canonicalized path of `{simplified}` is not valid UTF-8"
"Falling back to the slow case-sensitive path existence check because the canonicalized path of `{path}` is not valid UTF-8"
);
return None;
};
let simplified_canonicalized = simplify_ignore_verbatim(&canonicalized);
let simplified = simplify_ignore_verbatim(path);
// Test if the paths differ by anything other than casing. If so, that suggests that
// `path` pointed to a symlink (or some other none reversible path normalization happened).

View File

@@ -1,5 +1,6 @@
use glob::PatternError;
use ruff_notebook::{Notebook, NotebookError};
use rustc_hash::FxHashMap;
use std::panic::RefUnwindSafe;
use std::sync::{Arc, Mutex};
@@ -20,18 +21,44 @@ use super::walk_directory::WalkDirectoryBuilder;
///
/// ## Warning
/// Don't use this system for production code. It's intended for testing only.
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct TestSystem {
inner: Arc<dyn WritableSystem + RefUnwindSafe + Send + Sync>,
/// Environment variable overrides. If a key is present here, it takes precedence
/// over the inner system's environment variables.
env_overrides: Arc<Mutex<FxHashMap<String, Option<String>>>>,
}
impl Clone for TestSystem {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
env_overrides: self.env_overrides.clone(),
}
}
}
impl TestSystem {
pub fn new(inner: impl WritableSystem + RefUnwindSafe + Send + Sync + 'static) -> Self {
Self {
inner: Arc::new(inner),
env_overrides: Arc::new(Mutex::new(FxHashMap::default())),
}
}
/// Sets an environment variable override. This takes precedence over the inner system.
pub fn set_env_var(&self, name: impl Into<String>, value: impl Into<String>) {
self.env_overrides
.lock()
.unwrap()
.insert(name.into(), Some(value.into()));
}
/// Removes an environment variable override, making it appear as not set.
pub fn remove_env_var(&self, name: impl Into<String>) {
self.env_overrides.lock().unwrap().insert(name.into(), None);
}
/// Returns the [`InMemorySystem`].
///
/// ## Panics
@@ -147,6 +174,18 @@ impl System for TestSystem {
self.system().case_sensitivity()
}
fn env_var(&self, name: &str) -> std::result::Result<String, std::env::VarError> {
// Check overrides first
if let Some(override_value) = self.env_overrides.lock().unwrap().get(name) {
return match override_value {
Some(value) => Ok(value.clone()),
None => Err(std::env::VarError::NotPresent),
};
}
// Fall back to inner system
self.system().env_var(name)
}
fn dyn_clone(&self) -> Box<dyn System> {
Box::new(self.clone())
}
@@ -156,6 +195,7 @@ impl Default for TestSystem {
fn default() -> Self {
Self {
inner: Arc::new(InMemorySystem::default()),
env_overrides: Arc::new(Mutex::new(FxHashMap::default())),
}
}
}

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

@@ -11,10 +11,6 @@ repository = { workspace = true }
license = { workspace = true }
[dependencies]
ty = { workspace = true }
ty_project = { workspace = true, features = ["schemars"] }
ty_python_semantic = { workspace = true }
ty_static = { workspace = true }
ruff = { workspace = true }
ruff_formatter = { workspace = true }
ruff_linter = { workspace = true, features = ["schemars"] }
@@ -26,6 +22,10 @@ ruff_python_formatter = { workspace = true }
ruff_python_parser = { workspace = true }
ruff_python_trivia = { workspace = true }
ruff_workspace = { workspace = true, features = ["schemars"] }
ty = { workspace = true }
ty_project = { workspace = true, features = ["schemars"] }
ty_python_semantic = { workspace = true }
ty_static = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true, features = ["wrap_help"] }

View File

@@ -1,11 +1,13 @@
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
use std::borrow::Cow;
use std::{fmt::Write, path::PathBuf};
use anyhow::bail;
use itertools::Itertools;
use pretty_assertions::StrComparison;
use std::{fmt::Write, path::PathBuf};
use ruff_options_metadata::{OptionField, OptionSet, OptionsMetadata, Visit};
use ruff_python_trivia::textwrap;
use ty_project::metadata::Options;
use crate::{
@@ -165,62 +167,69 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
let _ = writeln!(output, "**Default value**: `{}`", field.default);
output.push('\n');
let _ = writeln!(output, "**Type**: `{}`", field.value_type);
output.push('\n');
output.push_str("**Example usage**:\n\n");
output.push_str(&format_example(
"pyproject.toml",
&format_header(
field.scope,
field.example,
parents,
ConfigurationFile::PyprojectToml,
),
field.example,
));
output.push('\n');
}
fn format_example(title: &str, header: &str, content: &str) -> String {
if header.is_empty() {
format!("```toml title=\"{title}\"\n{content}\n```\n",)
} else {
format!("```toml title=\"{title}\"\n{header}\n{content}\n```\n",)
for configuration_file in [ConfigurationFile::PyprojectToml, ConfigurationFile::TyToml] {
let (header, example) =
format_snippet(field.scope, field.example, parents, configuration_file);
output.push_str(&format_tab(configuration_file.name(), &header, &example));
output.push('\n');
}
}
fn format_tab(tab_name: &str, header: &str, content: &str) -> String {
let header = if header.is_empty() {
String::new()
} else {
format!("\n {header}")
};
format!(
"=== \"{}\"\n\n ```toml{}\n{}\n ```\n",
tab_name,
header,
textwrap::indent(content, " ")
)
}
/// Format the TOML header for the example usage for a given option.
///
/// For example: `[tool.ruff.format]` or `[tool.ruff.lint.isort]`.
fn format_header(
/// For example: `[tool.ty.rules]`.
fn format_snippet<'a>(
scope: Option<&str>,
example: &str,
example: &'a str,
parents: &[Set],
configuration: ConfigurationFile,
) -> String {
let tool_parent = match configuration {
ConfigurationFile::PyprojectToml => Some("tool.ty"),
ConfigurationFile::TyToml => None,
};
) -> (String, Cow<'a, str>) {
let mut example = Cow::Borrowed(example);
let header = tool_parent
let header = configuration
.parent_table()
.into_iter()
.chain(parents.iter().filter_map(|parent| parent.name()))
.chain(scope)
.join(".");
// Rewrite examples starting with `[tool.ty]` or `[[tool.ty]]` to their `ty.toml` equivalent.
if matches!(configuration, ConfigurationFile::TyToml) {
example = example.replace("[tool.ty.", "[").into();
}
// Ex) `[[tool.ty.xx]]`
if example.starts_with(&format!("[[{header}")) {
return String::new();
return (String::new(), example);
}
// Ex) `[tool.ty.rules]`
if example.starts_with(&format!("[{header}")) {
return String::new();
return (String::new(), example);
}
if header.is_empty() {
String::new()
(String::new(), example)
} else {
format!("[{header}]")
(format!("[{header}]"), example)
}
}
@@ -243,10 +252,25 @@ impl Visit for CollectOptionsVisitor {
#[derive(Debug, Copy, Clone)]
enum ConfigurationFile {
PyprojectToml,
#[expect(dead_code)]
TyToml,
}
impl ConfigurationFile {
const fn name(self) -> &'static str {
match self {
Self::PyprojectToml => "pyproject.toml",
Self::TyToml => "ty.toml",
}
}
const fn parent_table(self) -> Option<&'static str> {
match self {
Self::PyprojectToml => Some("tool.ty"),
Self::TyToml => None,
}
}
}
#[cfg(test)]
mod tests {
use anyhow::Result;

View File

@@ -63,12 +63,7 @@ fn generate_markdown() -> String {
let _ = writeln!(&mut output, "# Rules\n");
let mut lints: Vec<_> = registry.lints().iter().collect();
lints.sort_by(|a, b| {
a.default_level()
.cmp(&b.default_level())
.reverse()
.then_with(|| a.name().cmp(&b.name()))
});
lints.sort_by_key(|a| a.name());
for lint in lints {
let _ = writeln!(&mut output, "## `{rule_name}`\n", rule_name = lint.name());
@@ -119,7 +114,7 @@ fn generate_markdown() -> String {
let _ = writeln!(
&mut output,
r#"<small>
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
{status_text} ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>

View File

@@ -10,6 +10,10 @@ documentation = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
[package.metadata.cargo-shear]
# Used via `CacheKey` macro expansion.
ignored = ["ruff_cache"]
[dependencies]
ruff_cache = { workspace = true }
ruff_macros = { workspace = true }
@@ -25,10 +29,6 @@ unicode-width = { workspace = true }
[dev-dependencies]
[package.metadata.cargo-shear]
# Used via `CacheKey` macro expansion.
ignored = ["ruff_cache"]
[features]
serde = ["dep:serde", "ruff_text_size/serde"]
schemars = ["dep:schemars", "ruff_text_size/schemars"]

View File

@@ -9,6 +9,10 @@ repository.workspace = true
authors.workspace = true
license.workspace = true
[package.metadata.cargo-shear]
# Used via `CacheKey` macro expansion.
ignored = ["ruff_cache"]
[dependencies]
ruff_cache = { workspace = true }
ruff_db = { workspace = true, features = ["os", "serde"] }
@@ -16,6 +20,7 @@ ruff_linter = { workspace = true }
ruff_macros = { workspace = true }
ruff_python_ast = { workspace = true }
ruff_python_parser = { workspace = true }
ty_module_resolver = { workspace = true }
ty_python_semantic = { workspace = true }
anyhow = { workspace = true }
@@ -28,7 +33,3 @@ zip = { workspace = true, features = [] }
[lints]
workspace = true
[package.metadata.cargo-shear]
# Used via `CacheKey` macro expansion.
ignored = ["ruff_cache"]

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