Compare commits

...

50 Commits

Author SHA1 Message Date
Jack O'Connor
c7a241e112 add an I_KNOW_TY_IS_PRE_RELEASE env var to turn off the pre-release warning 2025-07-15 18:15:12 -07:00
Jack O'Connor
e73a8ba571 lint on the global keyword if there's no explicit definition in the global scope 2025-07-15 16:56:54 -07:00
David Peter
a1edb69ea5 [ty] Enum literal types (#19328)
## Summary

Add a new `Type::EnumLiteral(…)` variant and infer this type for member
accesses on enums.

**Example**: No more `@Todo` types here:
```py
from enum import Enum

class Answer(Enum):
    YES = 1
    NO = 2

    def is_yes(self) -> bool:
        return self == Answer.YES

reveal_type(Answer.YES)  # revealed: Literal[Answer.YES]
reveal_type(Answer.YES == Answer.NO)  # revealed: Literal[False]
reveal_type(Answer.YES.is_yes())  # revealed: bool
```

## Test Plan

* Many new Markdown tests for the new type variant
* Added enum literal types to property tests, ran property tests

## Ecosystem analysis

Summary:

Lots of false positives removed. All of the new diagnostics are
either new true positives (the majority) or known problems. Click for
detailed analysis</summary>

Details:

```diff
AutoSplit (https://github.com/Toufool/AutoSplit)
+ error[call-non-callable] src/capture_method/__init__.py:137:9: Method `__getitem__` of type `bound method CaptureMethodDict.__getitem__(key: Never, /) -> type[CaptureMethodBase]` is not callable on object of type `CaptureMethodDict`
+ error[call-non-callable] src/capture_method/__init__.py:147:9: Method `__getitem__` of type `bound method CaptureMethodDict.__getitem__(key: Never, /) -> type[CaptureMethodBase]` is not callable on object of type `CaptureMethodDict`
+ error[call-non-callable] src/capture_method/__init__.py:148:1: Method `__getitem__` of type `bound method CaptureMethodDict.__getitem__(key: Never, /) -> type[CaptureMethodBase]` is not callable on object of type `CaptureMethodDict`
```

New true positives. That `__getitem__` method is apparently annotated
with `Never` to prevent developers from using it.


```diff
dd-trace-py (https://github.com/DataDog/dd-trace-py)
+ error[invalid-assignment] ddtrace/vendor/psutil/_common.py:29:5: Object of type `None` is not assignable to `Literal[AddressFamily.AF_INET6]`
+ error[invalid-assignment] ddtrace/vendor/psutil/_common.py:33:5: Object of type `None` is not assignable to `Literal[AddressFamily.AF_UNIX]`
```

Arguably true positives:
e0a772c28b/ddtrace/vendor/psutil/_common.py (L29)

```diff
ignite (https://github.com/pytorch/ignite)
+ error[invalid-argument-type] tests/ignite/engine/test_custom_events.py:190:34: Argument to bound method `__call__` is incorrect: Expected `((...) -> Unknown) | None`, found `Literal["123"]`
+ error[invalid-argument-type] tests/ignite/engine/test_custom_events.py:220:37: Argument to function `default_event_filter` is incorrect: Expected `Engine`, found `None`
+ error[invalid-argument-type] tests/ignite/engine/test_custom_events.py:220:43: Argument to function `default_event_filter` is incorrect: Expected `int`, found `None`
+ error[call-non-callable] tests/ignite/engine/test_custom_events.py:561:9: Object of type `CustomEvents` is not callable
+ error[invalid-argument-type] tests/ignite/metrics/test_frequency.py:50:38: Argument to bound method `attach` is incorrect: Expected `Events`, found `CallableEventWithFilter`
```

All true positives. Some of them are inside `pytest.raises(TypeError,
…)` blocks 🙃

```diff
meson (https://github.com/mesonbuild/meson)
+ error[invalid-argument-type] unittests/internaltests.py:243:51: Argument to bound method `__init__` is incorrect: Expected `bool`, found `Literal[MachineChoice.HOST]`
+ error[invalid-argument-type] unittests/internaltests.py:271:51: Argument to bound method `__init__` is incorrect: Expected `bool`, found `Literal[MachineChoice.HOST]`
```

New true positives. Enum literals can not be assigned to `bool`, even if
their value types are `0` and `1`.

```diff
poetry (https://github.com/python-poetry/poetry)
+ error[invalid-assignment] src/poetry/console/exceptions.py:101:5: Object of type `Literal[""]` is not assignable to `InitVar[str]`
```

New false positive, missing support for `InitVar`.

```diff
prefect (https://github.com/PrefectHQ/prefect)
+ error[invalid-argument-type] src/integrations/prefect-dask/tests/test_task_runners.py:193:17: Argument is incorrect: Expected `StateType`, found `Literal[StateType.COMPLETED]`
```

This is confusing. There are two definitions
([one](74d8cd93ee/src/prefect/client/schemas/objects.py (L89-L100)),
[two](https://github.com/PrefectHQ/prefect/blob/main/src/prefect/server/schemas/states.py#L40))
of the `StateType` enum. Here, we're trying to assign one to the other.
I don't think that should be allowed, so this is a true positive (?).

```diff
python-htmlgen (https://github.com/srittau/python-htmlgen)
+ error[invalid-assignment] test_htmlgen/form.py:51:9: Object of type `str` is not assignable to attribute `autocomplete` of type `Autocomplete | None`
+ error[invalid-assignment] test_htmlgen/video.py:38:9: Object of type `str` is not assignable to attribute `preload` of type `Preload | None`
```

True positives. [The stubs are
wrong](01e3b911ac/htmlgen/form.pyi (L8-L10)).
These should not contain type annotations, but rather just `OFF = ...`.

```diff
rotki (https://github.com/rotki/rotki)
+ error[invalid-argument-type] rotkehlchen/tests/unit/test_serialization.py:62:30: Argument to bound method `deserialize` is incorrect: Expected `str`, found `Literal[15]`
```

New true positive.

```diff
vision (https://github.com/pytorch/vision)
+ error[unresolved-attribute] test/test_extended_models.py:302:17: Type `type[WeightsEnum]` has no attribute `DEFAULT`
+ error[unresolved-attribute] test/test_extended_models.py:302:58: Type `type[WeightsEnum]` has no attribute `DEFAULT`
```

Also new true positives. No `DEFAULT` member exists on `WeightsEnum`.
2025-07-15 21:31:53 +02:00
github-actions[bot]
a0d4e1f854 [ty] Sync vendored typeshed stubs (#19368)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-07-15 18:14:46 +00:00
Alex Waygood
c0d04f2d56 Fix typeshed-sync workflow (#19367) 2025-07-15 19:07:38 +01:00
Alex Waygood
8d7d02193e Rework typeshed-sync workflow to also add docstrings for Windows- and MacOS-specific APIs (#19360) 2025-07-15 18:14:32 +01:00
Zanie Blue
78dfc8af0f [ty] Allow -qq for silent output mode (#19366)
This matches uv's behavior.

Briefly discussed at
https://github.com/astral-sh/ruff/pull/19233#discussion_r2197930360

I think the most useful case is to avoid piping to `/dev/null` which
hard to do properly in a cross-platform script.
2025-07-15 17:08:19 +00:00
Zanie Blue
0c84652cc5 [ty] Allow -q short alias for --quiet (#19364) 2025-07-15 12:00:07 -05:00
Alex Waygood
560ae04346 Add shellcheck to pre-commit (#19361) 2025-07-15 16:49:13 +00:00
Jack O'Connor
a357a68fc9 distinguish references from definitions in infer_nonlocal
The initial implementation of `infer_nonlocal` landed in
https://github.com/astral-sh/ruff/pull/19112 fails to report an error
for this example:

```py
x = 1
def f():
    # This is only a usage of `x`, not a definition. It shouldn't be
    # enough to make the `nonlocal` statement below allowed.
    print(x)
    def g():
        nonlocal x
```

Fix this by continuing to walk enclosing scopes when the place we've
found isn't bound, declared, or `nonlocal`.
2025-07-15 07:55:40 -07:00
Dylan
00e7d1ffd6 [pycodestyle] Handle brace escapes for t-strings in logical lines (#19358)
Tracks both f and t-strings in the logical line rules for `pycodestyle`.

Progress towards #15506
2025-07-15 14:48:48 +00:00
Douglas Creager
f4d0273532 [ty] Combine CallArguments and CallArgumentTypes (#19337)
We previously had separate `CallArguments` and `CallArgumentTypes` types
in support of our two-phase call binding logic. `CallArguments` would
store only the arity/kind of each argument (positional, keyword,
variadic, etc). We then performed parameter matching using only this
arity/kind information, and then infered the type of each argument,
placing the result of this second phase into a new `CallArgumentTypes`.

In #18996, we will need to infer the types of splatted arguments
_before_ performing parameter matching, since we need to know the
argument type to accurately infer its length, which informs how many
parameters the splatted argument is matched against.

That makes this separation of Rust types no longer useful. This PR
merges everything back into a single `CallArguments`. In the case where
we are performing two-phase call binding, the types will be initialized
to `None`, and updated to the actual argument type during the second
`check_types` phase.

_[This is a refactoring in support of fixing the merge conflicts on
#18996. I've pulled this out into a separate PR to make it easier to
review in isolation.]_
2025-07-15 10:20:58 -04:00
Brent Westbrook
e9cac3684a Move Pylint rendering to ruff_db (#19340)
Summary
--

This is a very simple output format, the only decision is what to do if
the file
is missing from the diagnostic. For now, I opted to `unwrap_or_default`
both the
path and the `OneIndexed` row number, giving `:1: main diagnostic
message` in
the test without a file.

Another quirk here is that the path is relativized. I just pasted in the
`relativize_path` and `get_cwd` implementations from `ruff_linter::fs`
for now,
but maybe there's a better place for them.

I didn't see any details about why this needs to be relativized in the
original
[issue](https://github.com/astral-sh/ruff/issues/1953),
[PR](https://github.com/astral-sh/ruff/pull/1995), or in the pylint

[docs](https://flake8.pycqa.org/en/latest/internal/formatters.html#pylint-formatter),
but it did change the results of the CLI integration test when I tried
deleting
it. I haven't been able to reproduce that in the CLI, though, so it may
only
happen with `Command::current_dir`.

Test Plan
--

Tests ported from `ruff_linter` and a new test for the case with no file

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-15 10:14:49 -04:00
Dylan
92a302e291 [pylint] Extend invalid string character rules to include t-strings (#19355)
Handle t-strings in PLE2510-15

Progress towards #15506
2025-07-15 07:59:51 -05:00
Alex Waygood
7b8161e80d Make TC010 docs example more realistic (#19356) 2025-07-15 13:52:21 +01:00
Brent Westbrook
e9b0c33703 Move RDJSON rendering to ruff_db (#19293)
## Summary

Another output format like #19133. This is the
[reviewdog](https://github.com/reviewdog/reviewdog) output format, which
is somewhat similar to regular JSON. Like #19270, in the first commit I
converted from using `json!` to `Serialize` structs, then in the second
commit I moved the module to `ruff_db`.

The reviewdog
[schema](320a8e73a9/proto/rdf/jsonschema/DiagnosticResult.json)
seems a bit more flexible than our JSON schema, so I'm not sure if we
need any preview checks here. I'll flag the places I wasn't sure about
as review comments.

## Test Plan

New tests in `rdjson.rs`, ported from the old `rjdson.rs` module, as
well as the new CLI output tests.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-15 12:39:21 +00:00
Dylan
82391b5675 [flake8-use-pathlib] Skip single dots for invalid-pathlib-with-suffix (PTH210) on versions >= 3.14 (#19331)
Skips [invalid-pathlib-with-suffix
(PTH210)](https://docs.astral.sh/ruff/rules/invalid-pathlib-with-suffix/#invalid-pathlib-with-suffix-pth210)
for `.with_suffix(".")` on Python versions 3.14 and greater, as per [the
docs](https://docs.python.org/3.14/library/pathlib.html#pathlib.PurePath.with_suffix).

Progress towards #15506
2025-07-15 07:05:00 -05:00
Dylan
464144f1c6 [ruff] Allow strict kwarg when checking for starmap-zip (RUF058) in Python 3.14+ (#19333)
In Python 3.14 the keyword-argument `strict` was [added to
`map`](https://docs.python.org/3.14/library/functions.html#map). This PR
adds support for this when replacing a starmap-zip call with map in
[starmap-zip
(RUF058)](https://docs.astral.sh/ruff/rules/starmap-zip/#starmap-zip-ruf058).

Progress towards #15506
2025-07-15 07:04:23 -05:00
Alex Waygood
002f9057db [ty] Reduce false positives for TypedDict types (#19354) 2025-07-15 12:47:19 +01:00
Dhruv Manilawala
f3a27406c9 [ty] Remove ConnectionInitializer (#19353)
## Summary

This PR removes the `ConnectionInitializer` and inlines the
`initialize_start` and `initialize_finish` calls.

The main benefit of this is that it will allow us to use
[`Connection::memory`](https://docs.rs/lsp-server/latest/lsp_server/struct.Connection.html#method.memory)
in the mock server. That method returns two `Connection` where one of
them will represent the client side connection and the other will be
sent to the `Server::new` call to be used by the server. This way the
mock client can send notifications and requests to mimic the editor.

## Test Plan

I tested out the initialization process and checked that the initialized
result contains the server capabilities and server info.
2025-07-15 17:02:44 +05:30
Alex Waygood
2c9da80985 [ty] Use Type::string_literal() more (#19352) 2025-07-15 11:09:07 +00:00
David Peter
8e61da740a [ty] Add ecosystem-report workflow (#19349)
## Summary

Adds a new workflow that generates an ecosystem report of all
diagnostics and publishes it to Cloudflare pages.

## Test Plan

Not yet tested.
2025-07-15 12:29:44 +02:00
Micha Reiser
e506296cec [ty] Make use of salsa Lookup when interning values (#19347) 2025-07-15 09:54:43 +02:00
github-actions[bot]
966cc9d6e9 [ty] Sync vendored typeshed stubs (#19345)
Close and reopen this PR to trigger CI

Co-authored-by: typeshedbot <>
2025-07-15 11:46:59 +05:30
GiGaGon
7b27fe966e [pylint] Make example error out-of-the-box (PLE2502) (#19272)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972
Fixes #14346

This PR makes [bidirectional-unicode
(PLE2502)](https://docs.astral.sh/ruff/rules/bidirectional-unicode/#bidirectional-unicode-ple2502)'s
example error out-of-the-box, by converting it to use one of the test
cases. The documentation in general is also updated to replace
"bidirectional unicode character" with "bidirectional formatting
character", as those are the only ones checked for, and the "unicode"
suffix is redundant. The new example section looks like this:
<img width="1074" height="264" alt="image"
src="https://github.com/user-attachments/assets/cc1d2cb4-b590-4f20-a4d2-15b744872cdd"
/>

The "References" section link is also updated to reflect the rule's
actual behavior.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-14 14:46:23 -04:00
GiGaGon
966fd6f57a [pydoclint] Fix SyntaxError from fixes with line continuations (D201, D202) (#19246)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

This PR fixes #7172 by suppressing the fixes for
[docstring-missing-returns
(DOC201)](https://docs.astral.sh/ruff/rules/docstring-missing-returns/#docstring-missing-returns-doc201)
/ [docstring-extraneous-returns
(DOC202)](https://docs.astral.sh/ruff/rules/docstring-extraneous-returns/#docstring-extraneous-returns-doc202)
if there is a surrounding line continuation character `\` that would
make the fix cause a syntax error.

To do this, the lints are changed from `AlwaysFixableViolation` to
`Violation` with `FixAvailability::Sometimes`.

In the case of `DOC201`, the fix is not given if the non-break line ends
in a line continuation character `\`. Note that lines are iterated in
reverse from the docstring to the function definition.

In the case of `DOC202`, the fix is not given if the docstring ends with
a line continuation character `\`.

## Test Plan

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

Added a test case.
2025-07-14 13:31:36 -04:00
github-actions[bot]
4f60f0e925 [ty] Sync vendored typeshed stubs (#19334)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-07-14 17:34:09 +01:00
GiGaGon
059e90a98f [refurb] Make example error out-of-the-box (FURB122) (#19297)
## Summary

Part of #18972

This PR makes [for-loop-writes
(FURB122)](https://docs.astral.sh/ruff/rules/for-loop-writes/#for-loop-writes-furb122)'s
example error out-of-the-box. I also had to re-name the second case's
variables to get both to raise at the same time, I suspect because of
limitations in ruff's current semantic model. New names subject to
bikeshedding, I just went with the least effort `_b` for binary suffix.

[Old example](https://play.ruff.rs/19e8e47a-8058-4013-aef5-e9b5eab65962)
```py
with Path("file").open("w") as f:
    for line in lines:
        f.write(line)

with Path("file").open("wb") as f:
    for line in lines:
        f.write(line.encode())
```

[New example](https://play.ruff.rs/e96b00e5-3c63-47c3-996d-dace420dd711)
```py
from pathlib import Path

with Path("file").open("w") as f:
    for line in lines:
        f.write(line)

with Path("file").open("wb") as f_b:
    for line_b in lines_b:
        f_b.write(line_b.encode())
```

The "Use instead" section was also modified similarly.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-14 11:24:16 -05:00
Juriah
a4562ac673 [refurb] Make example error out-of-the-box (FURB177) (#19309)
<!--
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

Part of #18972
This PR makes
[implicit-cwd(FURB177)](https://docs.astral.sh/ruff/rules/implicit-cwd/)'s
example error out-of-the-box.

[Old example](https://play.ruff.rs/a0bef229-9626-426f-867f-55cb95ee64d8)
```python
cwd = Path().resolve()
```
[New example](https://play.ruff.rs/bdbea4af-e276-4603-a1b6-88757dfaa399)
```python
from pathlib import Path

cwd = Path().resolve()
```
<!-- What's the purpose of the change? What does it do, and why? -->


## Test Plan

<!-- How was it tested? -->
N/A, no functionality/tests affected
2025-07-14 11:23:02 -05:00
Alex Waygood
021a70d30c [ty] ignore errors when reformatting codemodded typeshed (#19332) 2025-07-14 16:14:01 +00:00
Alex Waygood
fddf2f33d2 [ty] Provide docstrings for stdlib APIs when hovering over them in an IDE (#19311) 2025-07-14 17:00:45 +01:00
Dhruv Manilawala
b4c42eb83b [ty] Add virtual files to the only project database (#19322)
## Summary

Previously, the virtual files were being added to the default database
that's present on the session. This is wrong because the default
database is for any files that don't belong to any project i.e., they're
outside of any projects managed by the server. Virtual files are neither
part of the project nor it is outside the projects. This was not the
intention as in the initial version, virtual files were being added to
the only project database managed by the server.

This PR fixes this by reverting back to the original behavior where
virtual files will be added to the only project database present. When
support for multiple workspace and project is added, this will require
updating (https://github.com/astral-sh/ty/issues/794).

This is required for #19264 because workspace diagnostics doesn't check
the default project database yet. Ideally, the default db should be
checked as well.

The implementation of this PR means that virtual files are now being
included for workspace diagnostics but it doesn't work completely e.g.,
if I save an untitled file the diagnostics disappears but it doesn't
appear back for the (now) saved file on disk as shown in the following
video demonstration:



https://github.com/user-attachments/assets/123e8d20-1e95-4c7d-b7eb-eb65be8c476e
2025-07-14 20:17:51 +05:30
Dylan
2a2cc37158 Add t-string fixtures for rules that do not need to be modified (#19146)
I used a script to attempt to identify those rules with the following
property: changing f-strings to t-strings in the corresponding fixture
altered the number of lint errors emitted. In other words, those rules
for which f-strings and t-strings are not treated the same in the
current implementation.

This PR documents the subset of such rules where this is fine and no
changes need to be made to the implementation of the rule. Mostly these
are the rules where it is relevant that an f-string evaluates to type
`str` at runtime whereas t-strings do not.

In theory many of these fixtures are not super necessary - it's unlikely
t-strings would be used for most of these. However, the internal
handling of t-strings is tightly coupled with that of f-strings, and may
become even more so as we implement the upcoming changes due to
https://github.com/python/cpython/pull/135996 . So I'd like to keep
these around as regression tests.

Note: The `flake8-bandit` fixtures were already added during the
original t-string implementation.

| Rule(s) | Reason |
| --- | --- |
| [`unused-method-argument`
(`ARG002`)](https://docs.astral.sh/ruff/rules/unused-method-argument/#unused-method-argument-arg002)
| f-strings exempted for msg in `NotImplementedError` not relevant for
t-strings |
| [`logging-f-string`
(`G004`)](https://docs.astral.sh/ruff/rules/logging-f-string/#logging-f-string-g004)
| t-strings cannot be used here |
| [`f-string-in-get-text-func-call`
(`INT001`)](https://docs.astral.sh/ruff/rules/f-string-in-get-text-func-call/#f-string-in-get-text-func-call-int001)
| rule justified by eager evaluation of interpolations |
| [`flake8-bandit`](https://docs.astral.sh/ruff/rules/#flake8-bandit-s)|
rules justified by eager evaluation of interpolations |
| [`single-string-slots`
(`PLC0205`)](https://docs.astral.sh/ruff/rules/single-string-slots/#single-string-slots-plc0205)
| t-strings cannot be slots in general |
| [`unnecessary-encode-utf8`
(`UP012`)](https://docs.astral.sh/ruff/rules/unnecessary-encode-utf8/#unnecessary-encode-utf8-up012)
| cannot encode t-strings |
| [`no-self-use`
(`PLR6301`)](https://docs.astral.sh/ruff/rules/no-self-use/#no-self-use-plr6301)
| f-strings exempted for msg in NotImplementedError not relevant for
t-strings |
| [`pytest-raises-too-broad`
(`PT011`)](https://docs.astral.sh/ruff/rules/pytest-raises-too-broad/) /
[`pytest-fail-without-message`
(`PT016`)](https://docs.astral.sh/ruff/rules/pytest-fail-without-message/#pytest-fail-without-message-pt016)
/ [`pytest-warns-too-broad`
(`PT030`)](https://docs.astral.sh/ruff/rules/pytest-warns-too-broad/#pytest-warns-too-broad-pt030)
| t-strings cannot be empty or used as messages |
| [`assert-on-string-literal`
(`PLW0129`)](https://docs.astral.sh/ruff/rules/assert-on-string-literal/#assert-on-string-literal-plw0129)
| t-strings are not strings and cannot be empty |
| [`native-literals`
(`UP018`)](https://docs.astral.sh/ruff/rules/native-literals/#native-literals-up018)
| t-strings are not native literals |
2025-07-14 09:46:31 -05:00
Dhruv Manilawala
8a217e5920 [ty] Remove FileLookupError (#19323)
## Summary

This PR removes the `FileLookupError` as it's not really required. The
original intention was that this would be returned from the `.file`
lookup to the different handlers but we've since moved the logic of
"lookup file and add trace message if file unavailable with the reason"
under the `file_ok` method which all of the handlers use.
2025-07-14 13:35:14 +00:00
Andrew Gallant
f7973ac870 [ty] Fix handling of metaclasses in object.<CURSOR> completions
Basically, we weren't quite using `Type::member` in every case
correctly. Specifically, this example from @sharkdp:

```
class Meta(type):
    @property
    def meta_attr(self) -> int:
        return 0

class C(metaclass=Meta): ...

C.<CURSOR>
```

While we would return `C.meta_attr` here, we were claiming its type was
`property`. But its type should be `int`.

Ref https://github.com/astral-sh/ruff/pull/19216#discussion_r2197065241
2025-07-14 08:24:23 -04:00
Micha Reiser
3560f86450 [ty] Use an interval map for scopes by expression (#19025) 2025-07-14 13:50:58 +02:00
David Peter
f22da352db [ty] List all enum members (#19283)
## Summary

Adds a way to list all members of an `Enum` and implements almost all of
the mechanisms by which members are distinguished from non-members
([spec](https://typing.python.org/en/latest/spec/enums.html#defining-members)).
This has no effect on actual enums, so far.

## Test Plan

New Markdown tests using `ty_extensions.enum_members`.
2025-07-14 13:18:17 +02:00
Micha Reiser
cb530a0216 [ty] Handle configuration errors in LSP more gracefully (#19262) 2025-07-14 12:27:52 +02:00
Micha Reiser
90026047f9 [ty] Use python version and path from Python extension (#19012) 2025-07-14 09:47:27 +00:00
w0nder1ng
26f736bc46 [pep8_naming] Avoid false positives on standard library functions with uppercase names (N802) (#18907)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-14 08:26:57 +00:00
renovate[bot]
c9f95e8714 Update Rust crate toml to 0.9.0 (#19320)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [toml](https://redirect.github.com/toml-rs/toml) |
workspace.dependencies | minor | `0.8.11` -> `0.9.0` |

---

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

---

### Release Notes

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

###
[`v0.9.2`](https://redirect.github.com/toml-rs/toml/compare/toml-v0.9.1...toml-v0.9.2)

[Compare
Source](https://redirect.github.com/toml-rs/toml/compare/toml-v0.9.1...toml-v0.9.2)

###
[`v0.9.1`](https://redirect.github.com/toml-rs/toml/compare/toml-v0.9.0...toml-v0.9.1)

[Compare
Source](https://redirect.github.com/toml-rs/toml/compare/toml-v0.9.0...toml-v0.9.1)

###
[`v0.9.0`](https://redirect.github.com/toml-rs/toml/compare/toml-v0.8.23...toml-v0.9.0)

[Compare
Source](https://redirect.github.com/toml-rs/toml/compare/toml-v0.8.23...toml-v0.9.0)

</details>

---

### Configuration

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

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

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

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

---

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

---

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

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

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2025-07-14 13:11:10 +05:30
Micha Reiser
3da8b51dc1 [ty] Fix server version (#19284) 2025-07-14 09:06:34 +02:00
renovate[bot]
3cbf2fe82e Update NPM Development dependencies (#19319)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 08:45:59 +02:00
renovate[bot]
221f3258d4 Update taiki-e/install-action action to v2.56.13 (#19317)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 08:40:43 +02:00
renovate[bot]
3fae3f248c Update Rust crate clap to v4.5.41 (#19315)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 08:40:26 +02:00
renovate[bot]
6c73837bc4 Update Rust crate get-size2 to v0.5.2 (#19316)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 08:40:03 +02:00
renovate[bot]
6f8f38cb68 Update CodSpeedHQ/action action to v3.7.0 (#19318)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 08:35:06 +02:00
renovate[bot]
4e2d6f5e45 Update pre-commit hook astral-sh/ruff-pre-commit to v0.12.3 (#19314)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[astral-sh/ruff-pre-commit](https://redirect.github.com/astral-sh/ruff-pre-commit)
| repository | patch | `v0.12.2` -> `v0.12.3` |

---

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

Note: The `pre-commit` manager in Renovate is not supported by the
`pre-commit` maintainers or community. Please do not report any problems
there, instead [create a Discussion in the Renovate
repository](https://redirect.github.com/renovatebot/renovate/discussions/new)
if you have any questions.

---

### Release Notes

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

###
[`v0.12.3`](https://redirect.github.com/astral-sh/ruff-pre-commit/releases/tag/v0.12.3)

[Compare
Source](https://redirect.github.com/astral-sh/ruff-pre-commit/compare/v0.12.2...v0.12.3)

See: https://github.com/astral-sh/ruff/releases/tag/0.12.3

</details>

---

### Configuration

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

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

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

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

---

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

---

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

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 11:52:42 +05:30
renovate[bot]
3ed3852c38 Update dependency ruff to v0.12.3 (#19313)
This PR contains the following updates:

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

---

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

---

### Release Notes

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

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

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

##### Preview features

- \[`flake8-bugbear`] Support non-context-manager calls in `B017`
([#&#8203;19063](https://redirect.github.com/astral-sh/ruff/pull/19063))
- \[`flake8-use-pathlib`] Add autofixes for `PTH100`, `PTH106`,
`PTH107`, `PTH108`, `PTH110`, `PTH111`, `PTH112`, `PTH113`, `PTH114`,
`PTH115`, `PTH117`, `PTH119`, `PTH120`
([#&#8203;19213](https://redirect.github.com/astral-sh/ruff/pull/19213))
- \[`flake8-use-pathlib`] Add autofixes for `PTH203`, `PTH204`, `PTH205`
([#&#8203;18922](https://redirect.github.com/astral-sh/ruff/pull/18922))

##### Bug fixes

- \[`flake8-return`] Fix false-positive for variables used inside nested
functions in `RET504`
([#&#8203;18433](https://redirect.github.com/astral-sh/ruff/pull/18433))
- Treat form feed as valid whitespace before a line continuation
([#&#8203;19220](https://redirect.github.com/astral-sh/ruff/pull/19220))
- \[`flake8-type-checking`] Fix syntax error introduced by fix (`TC008`)
([#&#8203;19150](https://redirect.github.com/astral-sh/ruff/pull/19150))
- \[`pyupgrade`] Keyword arguments in `super` should suppress the
`UP008` fix
([#&#8203;19131](https://redirect.github.com/astral-sh/ruff/pull/19131))

##### Documentation

- \[`flake8-pyi`] Make example error out-of-the-box (`PYI007`, `PYI008`)
([#&#8203;19103](https://redirect.github.com/astral-sh/ruff/pull/19103))
- \[`flake8-simplify`] Make example error out-of-the-box (`SIM116`)
([#&#8203;19111](https://redirect.github.com/astral-sh/ruff/pull/19111))
- \[`flake8-type-checking`] Make example error out-of-the-box (`TC001`)
([#&#8203;19151](https://redirect.github.com/astral-sh/ruff/pull/19151))
- \[`flake8-use-pathlib`] Make example error out-of-the-box (`PTH210`)
([#&#8203;19189](https://redirect.github.com/astral-sh/ruff/pull/19189))
- \[`pycodestyle`] Make example error out-of-the-box (`E272`)
([#&#8203;19191](https://redirect.github.com/astral-sh/ruff/pull/19191))
- \[`pycodestyle`] Make example not raise unnecessary `SyntaxError`
(`E114`)
([#&#8203;19190](https://redirect.github.com/astral-sh/ruff/pull/19190))
- \[`pydoclint`] Make example error out-of-the-box (`DOC501`)
([#&#8203;19218](https://redirect.github.com/astral-sh/ruff/pull/19218))
- \[`pylint`, `pyupgrade`] Fix syntax errors in examples (`PLW1501`,
`UP028`)
([#&#8203;19127](https://redirect.github.com/astral-sh/ruff/pull/19127))
- \[`pylint`] Update `missing-maxsplit-arg` docs and error to suggest
proper usage (`PLC0207`)
([#&#8203;18949](https://redirect.github.com/astral-sh/ruff/pull/18949))
- \[`flake8-bandit`] Make example error out-of-the-box (`S412`)
([#&#8203;19241](https://redirect.github.com/astral-sh/ruff/pull/19241))

</details>

---

### Configuration

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

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

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

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

---

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

---

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

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 11:52:25 +05:30
GiGaGon
dca594f89f [pyupgrade] Make example error out-of-the-box (UP040) (#19296)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [non-pep695-type-alias
(UP040)](https://docs.astral.sh/ruff/rules/non-pep695-type-alias/#non-pep695-type-alias-up040)'s
example error out-of-the-box.

[Old example](https://play.ruff.rs/6beca1be-45cd-4e5a-aafa-6a0584c10d64)
```py
ListOfInt: TypeAlias = list[int]
PositiveInt = TypeAliasType("PositiveInt", Annotated[int, Gt(0)])
```

[New example](https://play.ruff.rs/bbad34da-bf07-44e6-9f34-53337e8f57d4)
```py
from typing import Annotated, TypeAlias, TypeAliasType
from annotated_types import Gt

ListOfInt: TypeAlias = list[int]
PositiveInt = TypeAliasType("PositiveInt", Annotated[int, Gt(0)])
```

Imports were also added to the "Use instead" section.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-12 18:39:25 +01:00
853 changed files with 78705 additions and 10092 deletions

View File

@@ -240,11 +240,11 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
with:
tool: cargo-insta
- name: ty mdtests (GitHub annotations)
@@ -298,11 +298,11 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
with:
tool: cargo-insta
- name: "Run tests"
@@ -325,7 +325,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo nextest"
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
with:
tool: cargo-nextest
- name: "Run tests"
@@ -903,7 +903,7 @@ jobs:
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
with:
tool: cargo-codspeed
@@ -911,7 +911,7 @@ jobs:
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark
- name: "Run benchmarks"
uses: CodSpeedHQ/action@0010eb0ca6e89b80c88e8edaaa07cfe5f3e6664d # v3.5.0
uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.0
with:
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}
@@ -936,7 +936,7 @@ jobs:
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
with:
tool: cargo-codspeed
@@ -944,7 +944,7 @@ jobs:
run: cargo codspeed build --features "codspeed,walltime" --no-default-features -p ruff_benchmark
- name: "Run benchmarks"
uses: CodSpeedHQ/action@0010eb0ca6e89b80c88e8edaaa07cfe5f3e6664d # v3.5.0
uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.0
with:
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}

View File

@@ -1,5 +1,25 @@
name: Sync typeshed
# How this works:
#
# 1. A Linux worker:
# a. Checks out Ruff and typeshed
# b. Deletes the vendored typeshed stdlib stubs from Ruff
# c. Copies the latest versions of the stubs from typeshed
# d. Uses docstring-adder to sync all docstrings available on Linux
# e. Creates a new branch on the upstream astral-sh/ruff repository
# f. Commits the changes it's made and pushes them to the new upstream branch
# 2. Once the Linux worker is done, a Windows worker:
# a. Checks out the branch created by the Linux worker
# b. Syncs all docstrings available on Windows that are not available on Linux
# c. Commits the changes and pushes them to the same upstream branch
# 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. Commits the changes and pushes them to the same upstream branch
# d. 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
on:
workflow_dispatch:
schedule:
@@ -10,7 +30,13 @@ env:
FORCE_COLOR: 1
GH_TOKEN: ${{ github.token }}
# The name of the upstream branch that the first worker creates,
# and which all three workers push to.
UPSTREAM_BRANCH: typeshedbot/sync-typeshed
jobs:
# Sync typeshed stubs, and sync all docstrings available on Linux.
# Push the changes to a new branch on the upstream repository.
sync:
name: Sync typeshed
runs-on: ubuntu-latest
@@ -19,7 +45,6 @@ jobs:
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
name: Checkout Ruff
@@ -36,37 +61,130 @@ jobs:
run: |
git config --global user.name typeshedbot
git config --global user.email '<>'
- name: Sync typeshed
id: sync
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- name: Sync typeshed stubs
run: |
rm -rf ruff/crates/ty_vendored/vendor/typeshed
mkdir ruff/crates/ty_vendored/vendor/typeshed
cp typeshed/README.md ruff/crates/ty_vendored/vendor/typeshed
cp typeshed/LICENSE ruff/crates/ty_vendored/vendor/typeshed
# The pyproject.toml file is needed by a later job for the black configuration.
# It's deleted before creating the PR.
cp typeshed/pyproject.toml ruff/crates/ty_vendored/vendor/typeshed
cp -r typeshed/stdlib ruff/crates/ty_vendored/vendor/typeshed/stdlib
rm -rf ruff/crates/ty_vendored/vendor/typeshed/stdlib/@tests
git -C typeshed rev-parse HEAD > ruff/crates/ty_vendored/vendor/typeshed/source_commit.txt
- name: Commit the changes
id: commit
if: ${{ steps.sync.outcome == 'success' }}
run: |
cd ruff
git checkout -b typeshedbot/sync-typeshed
git add .
git diff --staged --quiet || git commit -m "Sync typeshed. Source commit: https://github.com/python/typeshed/commit/$(git -C ../typeshed rev-parse HEAD)"
- name: Create a PR
if: ${{ steps.sync.outcome == 'success' && steps.commit.outcome == 'success' }}
git commit -m "Sync typeshed. Source commit: https://github.com/python/typeshed/commit/$(git -C ../typeshed rev-parse HEAD)" --allow-empty
- name: Sync Linux docstrings
if: ${{ success() }}
run: |
cd ruff
git push --force origin typeshedbot/sync-typeshed
./scripts/codemod_docstrings.sh
git commit -am "Sync Linux docstrings" --allow-empty
- name: Push the changes
id: commit
if: ${{ success() }}
run: git -C ruff push --force --set-upstream origin "${UPSTREAM_BRANCH}"
# Checkout the branch created by the sync job,
# and sync all docstrings available on Windows that are not available on Linux.
# Commit the changes and push them to the same branch.
docstrings-windows:
runs-on: windows-latest
timeout-minutes: 20
needs: [sync]
# Don't run the cron job on forks.
# The job will also be skipped if the sync job failed, because it's specified in `needs` above,
# and we haven't used `always()` in the `if` condition here
# (https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-requiring-successful-dependent-jobs)
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
permissions:
contents: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
name: Checkout Ruff
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- name: Setup git
run: |
git config --global user.name typeshedbot
git config --global user.email '<>'
- name: Sync Windows docstrings
id: docstrings
shell: bash
run: ./scripts/codemod_docstrings.sh
- name: Commit the changes
if: ${{ steps.docstrings.outcome == 'success' }}
run: |
git commit -am "Sync Windows docstrings" --allow-empty
git push
# Checkout the branch created by the sync job,
# and sync all docstrings available on macOS that are not available on Linux or Windows.
# Push the changes to the same branch and create a PR against the `main` branch using that branch.
docstrings-macos-and-pr:
runs-on: macos-latest
timeout-minutes: 20
needs: [sync, docstrings-windows]
# Don't run the cron job on forks.
# The job will also be skipped if the sync or docstrings-windows jobs failed,
# because they're specified in `needs` above and we haven't used an `always()` condition in the `if` here
# (https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-requiring-successful-dependent-jobs)
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
name: Checkout Ruff
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- name: Setup git
run: |
git config --global user.name typeshedbot
git config --global user.email '<>'
- name: Sync macOS docstrings
run: ./scripts/codemod_docstrings.sh
- name: Commit and push the changes
if: ${{ success() }}
run: |
git commit -am "Sync macOS docstrings" --allow-empty
# Here we just reformat the codemodded stubs so that they are
# consistent with the other typeshed stubs around them.
# Typeshed formats code using black in their CI, so we just invoke
# black on the stubs the same way that typeshed does.
uvx black crates/ty_vendored/vendor/typeshed/stdlib --config crates/ty_vendored/vendor/typeshed/pyproject.toml || true
git commit -am "Format codemodded docstrings" --allow-empty
rm crates/ty_vendored/vendor/typeshed/pyproject.toml
git commit -am "Remove pyproject.toml file"
git push
- name: Create a PR
if: ${{ success() }}
run: |
gh pr list --repo "$GITHUB_REPOSITORY" --head typeshedbot/sync-typeshed --json id --jq length | grep 1 && exit 0 # exit if there is existing pr
gh pr create --title "[ty] Sync vendored typeshed stubs" --body "Close and reopen this PR to trigger CI" --label "ty"
create-issue-on-failure:
name: Create an issue if the typeshed sync failed
runs-on: ubuntu-latest
needs: [sync]
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.sync.result == 'failure' }}
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') }}
permissions:
issues: write
steps:

View File

@@ -0,0 +1,76 @@
name: ty ecosystem-report
permissions: {}
on:
workflow_dispatch:
schedule:
# Run every Wednesday at 5:00 UTC:
- cron: 0 5 * * 3
env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: 1
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
jobs:
ty-ecosystem-report:
name: Create ecosystem report
runs-on: depot-ubuntu-22.04-32
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:
workspaces: "ruff"
- name: Install Rust toolchain
run: rustup show
- name: Create report
shell: bash
run: |
cd ruff
echo "Enabling configuration overloads (see .github/mypy-primer-ty.toml)"
mkdir -p ~/.config/ty
cp .github/mypy-primer-ty.toml ~/.config/ty/ty.toml
cd ..
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@f0eec0e549684d8e1d7b8bc3e351202124b63bda"
ecosystem-analyzer \
--verbose \
--repository ruff \
analyze \
--projects ruff/crates/ty_python_semantic/resources/primer/good.txt \
--output ecosystem-diagnostics.json
mkdir dist
ecosystem-analyzer \
generate-report \
--max-diagnostics-per-project=1200 \
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
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}

1
.github/zizmor.yml vendored
View File

@@ -11,6 +11,7 @@ rules:
- build-docker.yml
- publish-playground.yml
- ty-ecosystem-analyzer.yaml
- ty-ecosystem-report.yaml
excessive-permissions:
# it's hard to test what the impact of removing these ignores would be
# without actually running the release workflow...

View File

@@ -81,7 +81,7 @@ repos:
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.2
rev: v0.12.3
hooks:
- id: ruff-format
- id: ruff
@@ -128,5 +128,10 @@ repos:
# but the integration only works if shellcheck is installed
- "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.10.0"
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.10.0.1
hooks:
- id: shellcheck
ci:
skip: [cargo-fmt, dev-generate-all]

116
Cargo.lock generated
View File

@@ -396,9 +396,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.40"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
dependencies = [
"clap_builder",
"clap_derive",
@@ -406,9 +406,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.40"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
dependencies = [
"anstream",
"anstyle",
@@ -449,9 +449,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.40"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
dependencies = [
"heck",
"proc-macro2",
@@ -591,7 +591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -600,7 +600,7 @@ version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -933,7 +933,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -1013,7 +1013,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -1133,9 +1133,9 @@ dependencies = [
[[package]]
name = "get-size-derive2"
version = "0.5.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aac2af9f9a6a50e31b1e541d05b7925add83d3982c2793193fe9d4ee584323c"
checksum = "028f3cfad7c3e3b1d8d04ef0a1c03576f2d62800803fe1301a4cd262849f2dea"
dependencies = [
"attribute-derive",
"quote",
@@ -1144,9 +1144,9 @@ dependencies = [
[[package]]
name = "get-size2"
version = "0.5.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a0312efd19e1c45922dfcc2d6806d3ffc4bca261f89f31fcc4f63f438d885"
checksum = "3a09c2043819a3def7bfbb4927e7df96aab0da4cfd8824484b22d0c94e84458e"
dependencies = [
"compact_str",
"get-size-derive2",
@@ -1586,7 +1586,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi 0.5.1",
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -1650,7 +1650,7 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -2465,7 +2465,7 @@ dependencies = [
"pep508_rs",
"serde",
"thiserror 2.0.12",
"toml",
"toml 0.8.23",
]
[[package]]
@@ -2763,7 +2763,7 @@ dependencies = [
"test-case",
"thiserror 2.0.12",
"tikv-jemallocator",
"toml",
"toml 0.9.2",
"tracing",
"walkdir",
"wild",
@@ -2779,7 +2779,7 @@ dependencies = [
"ruff_annotate_snippets",
"serde",
"snapbox",
"toml",
"toml 0.9.2",
"tryfn",
"unicode-width 0.2.1",
]
@@ -2895,7 +2895,7 @@ dependencies = [
"similar",
"strum",
"tempfile",
"toml",
"toml 0.9.2",
"tracing",
"tracing-indicatif",
"tracing-subscriber",
@@ -3016,7 +3016,7 @@ dependencies = [
"tempfile",
"test-case",
"thiserror 2.0.12",
"toml",
"toml 0.9.2",
"typed-arena",
"unicode-normalization",
"unicode-width 0.2.1",
@@ -3266,7 +3266,7 @@ dependencies = [
"serde_json",
"shellexpand",
"thiserror 2.0.12",
"toml",
"toml 0.9.2",
"tracing",
"tracing-log",
"tracing-subscriber",
@@ -3356,7 +3356,7 @@ dependencies = [
"shellexpand",
"strum",
"tempfile",
"toml",
"toml 0.9.2",
]
[[package]]
@@ -3391,7 +3391,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -3576,6 +3576,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
dependencies = [
"serde",
]
[[package]]
name = "serde_test"
version = "1.0.177"
@@ -3781,7 +3790,7 @@ dependencies = [
"getrandom 0.3.3",
"once_cell",
"rustix",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -3981,11 +3990,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"serde_spanned 0.6.9",
"toml_datetime 0.6.11",
"toml_edit",
]
[[package]]
name = "toml"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac"
dependencies = [
"indexmap",
"serde",
"serde_spanned 1.0.0",
"toml_datetime 0.7.0",
"toml_parser",
"toml_writer",
"winnow",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
@@ -3995,6 +4019,15 @@ dependencies = [
"serde",
]
[[package]]
name = "toml_datetime"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
@@ -4003,17 +4036,25 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"toml_write",
"serde_spanned 0.6.9",
"toml_datetime 0.6.11",
"winnow",
]
[[package]]
name = "toml_write"
version = "0.1.2"
name = "toml_parser"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
[[package]]
name = "tracing"
@@ -4136,7 +4177,7 @@ dependencies = [
"ruff_python_trivia",
"salsa",
"tempfile",
"toml",
"toml 0.9.2",
"tracing",
"tracing-flame",
"tracing-subscriber",
@@ -4197,7 +4238,7 @@ dependencies = [
"schemars",
"serde",
"thiserror 2.0.12",
"toml",
"toml 0.9.2",
"tracing",
"ty_ide",
"ty_python_semantic",
@@ -4266,6 +4307,7 @@ dependencies = [
"lsp-types",
"ruff_db",
"ruff_notebook",
"ruff_python_ast",
"ruff_source_file",
"ruff_text_size",
"rustc-hash",
@@ -4314,7 +4356,7 @@ dependencies = [
"smallvec",
"tempfile",
"thiserror 2.0.12",
"toml",
"toml 0.9.2",
"tracing",
"ty_python_semantic",
"ty_static",
@@ -4808,7 +4850,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]

View File

@@ -165,7 +165,7 @@ tempfile = { version = "3.9.0" }
test-case = { version = "3.3.1" }
thiserror = { version = "2.0.0" }
tikv-jemallocator = { version = "0.6.0" }
toml = { version = "0.8.11" }
toml = { version = "0.9.0" }
tracing = { version = "0.1.40" }
tracing-flame = { version = "0.2.0" }
tracing-indicatif = { version = "0.3.11" }

View File

@@ -131,6 +131,7 @@ pub fn run(
}: Args,
) -> Result<ExitStatus> {
{
ruff_db::set_program_version(crate::version::version().to_string()).unwrap();
let default_panic_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
#[expect(clippy::print_stderr)]

View File

@@ -16,7 +16,7 @@ use ruff_linter::fs::relativize_path;
use ruff_linter::logging::LogLevel;
use ruff_linter::message::{
Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter, JunitEmitter,
PylintEmitter, RdjsonEmitter, SarifEmitter, TextEmitter,
SarifEmitter, TextEmitter,
};
use ruff_linter::notify_user;
use ruff_linter::settings::flags::{self};
@@ -238,7 +238,11 @@ impl Printer {
write!(writer, "{value}")?;
}
OutputFormat::Rdjson => {
RdjsonEmitter.emit(writer, &diagnostics.inner, &context)?;
let config = DisplayDiagnosticConfig::default()
.format(DiagnosticFormat::Rdjson)
.preview(preview);
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
write!(writer, "{value}")?;
}
OutputFormat::JsonLines => {
let config = DisplayDiagnosticConfig::default()
@@ -290,7 +294,11 @@ impl Printer {
GitlabEmitter::default().emit(writer, &diagnostics.inner, &context)?;
}
OutputFormat::Pylint => {
PylintEmitter.emit(writer, &diagnostics.inner, &context)?;
let config = DisplayDiagnosticConfig::default()
.format(DiagnosticFormat::Pylint)
.preview(preview);
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
write!(writer, "{value}")?;
}
OutputFormat::Azure => {
let config = DisplayDiagnosticConfig::default()

View File

@@ -120,7 +120,7 @@ fn nonexistent_config_file() {
#[test]
fn config_override_rejected_if_invalid_toml() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["format", "--config", "foo = bar", "."]), @r#"
.args(["format", "--config", "foo = bar", "."]), @r"
success: false
exit_code: 2
----- stdout -----
@@ -137,12 +137,11 @@ fn config_override_rejected_if_invalid_toml() {
TOML parse error at line 1, column 7
|
1 | foo = bar
| ^
invalid string
expected `"`, `'`
| ^^^
string values must be quoted, expected literal string
For more information, try '--help'.
"#);
");
}
#[test]

View File

@@ -2246,8 +2246,7 @@ fn pyproject_toml_stdin_syntax_error() {
success: false
exit_code: 1
----- stdout -----
pyproject.toml:1:9: RUF200 Failed to parse pyproject.toml: invalid table header
expected `.`, `]`
pyproject.toml:1:9: RUF200 Failed to parse pyproject.toml: unclosed table, expected `]`
|
1 | [project
| ^ RUF200

View File

@@ -534,7 +534,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", "."]), @r"
success: false
exit_code: 2
----- stdout -----
@@ -551,12 +551,11 @@ fn config_override_rejected_if_invalid_toml() {
TOML parse error at line 1, column 7
|
1 | foo = bar
| ^
invalid string
expected `"`, `'`
| ^^^
string values must be quoted, expected literal string
For more information, try '--help'.
"#);
");
}
#[test]
@@ -733,9 +732,8 @@ select = [E501]
Cause: TOML parse error at line 3, column 11
|
3 | select = [E501]
| ^
invalid array
expected `]`
| ^^^^
string values must be quoted, expected literal string
");
});
@@ -876,7 +874,7 @@ fn each_toml_option_requires_a_new_flag_1() {
|
1 | extend-select=['F841'], line-length=90
| ^
expected newline, `#`
unexpected key or value, expected newline, `#`
For more information, try '--help'.
");
@@ -907,7 +905,7 @@ fn each_toml_option_requires_a_new_flag_2() {
|
1 | extend-select=['F841'] line-length=90
| ^
expected newline, `#`
unexpected key or value, expected newline, `#`
For more information, try '--help'.
");

View File

@@ -18,6 +18,6 @@ exit_code: 1
----- stdout -----
input.py:1: [F401] `os` imported but unused
input.py:2: [F821] Undefined name `y`
input.py:3: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
input.py:3: [invalid-syntax] SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
----- stderr -----

View File

@@ -75,8 +75,7 @@ exit_code: 1
},
{
"code": {
"url": null,
"value": null
"value": "invalid-syntax"
},
"location": {
"path": "[TMP]/input.py",
@@ -94,7 +93,7 @@ exit_code: 1
"message": "SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)"
}
],
"severity": "warning",
"severity": "WARNING",
"source": {
"name": "ruff",
"url": "https://docs.astral.sh/ruff"

View File

@@ -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"]), @r"
success: false
exit_code: 2
----- stdout -----
@@ -77,12 +77,11 @@ fn config_option_ignored_but_validated() {
TOML parse error at line 1, column 7
|
1 | foo = bar
| ^
invalid string
expected `"`, `'`
| ^^^
string values must be quoted, expected literal string
For more information, try '--help'.
"#
"
);
});
}

View File

@@ -2,6 +2,7 @@
use ruff_benchmark::criterion;
use ruff_benchmark::real_world_projects::{InstalledProject, RealWorldProject};
use std::fmt::Write;
use std::ops::Range;
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
@@ -441,6 +442,37 @@ fn benchmark_complex_constrained_attributes_2(criterion: &mut Criterion) {
});
}
fn benchmark_many_enum_members(criterion: &mut Criterion) {
const NUM_ENUM_MEMBERS: usize = 512;
setup_rayon();
let mut code = String::new();
writeln!(&mut code, "from enum import Enum").ok();
writeln!(&mut code, "class E(Enum):").ok();
for i in 0..NUM_ENUM_MEMBERS {
writeln!(&mut code, " m{i} = {i}").ok();
}
writeln!(&mut code).ok();
for i in 0..NUM_ENUM_MEMBERS {
writeln!(&mut code, "print(E.m{i})").ok();
}
criterion.bench_function("ty_micro[many_enum_members]", |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,
@@ -591,6 +623,7 @@ criterion_group!(
benchmark_many_tuple_assignments,
benchmark_complex_constrained_attributes_1,
benchmark_complex_constrained_attributes_2,
benchmark_many_enum_members,
);
criterion_group!(project, anyio, attrs, hydra, datetype);
criterion_main!(check_file, micro, project);

View File

@@ -1,4 +1,4 @@
use std::{fmt::Formatter, sync::Arc};
use std::{fmt::Formatter, path::Path, sync::Arc};
use ruff_diagnostics::Fix;
use ruff_source_file::{LineColumn, SourceCode, SourceFile};
@@ -308,6 +308,10 @@ impl Diagnostic {
/// Set the fix for this diagnostic.
pub fn set_fix(&mut self, fix: Fix) {
debug_assert!(
self.primary_span().is_some(),
"Expected a source file for a diagnostic with a fix"
);
Arc::make_mut(&mut self.inner).fix = Some(fix);
}
@@ -1008,6 +1012,18 @@ impl UnifiedFile {
}
}
/// Return the file's path relative to the current working directory.
pub fn relative_path<'a>(&'a self, resolver: &'a dyn FileResolver) -> &'a Path {
let cwd = resolver.current_directory();
let path = Path::new(self.path(resolver));
if let Ok(path) = path.strip_prefix(cwd) {
return path;
}
path
}
fn diagnostic_source(&self, resolver: &dyn FileResolver) -> DiagnosticSource {
match self {
UnifiedFile::Ty(file) => DiagnosticSource::Ty(resolver.input(*file)),
@@ -1259,6 +1275,13 @@ pub enum DiagnosticFormat {
/// format for an array of all diagnostics. See <https://jsonlines.org/> for more details.
#[cfg(feature = "serde")]
JsonLines,
/// Print diagnostics in the JSON format expected by [reviewdog].
///
/// [reviewdog]: https://github.com/reviewdog/reviewdog
#[cfg(feature = "serde")]
Rdjson,
/// Print diagnostics in the format emitted by Pylint.
Pylint,
}
/// A representation of the kinds of messages inside a diagnostic.

View File

@@ -1,4 +1,5 @@
use std::collections::BTreeMap;
use std::path::Path;
use ruff_annotate_snippets::{
Annotation as AnnotateAnnotation, Level as AnnotateLevel, Message as AnnotateMessage,
@@ -22,12 +23,16 @@ use super::{
};
use azure::AzureRenderer;
use pylint::PylintRenderer;
mod azure;
#[cfg(feature = "serde")]
mod json;
#[cfg(feature = "serde")]
mod json_lines;
mod pylint;
#[cfg(feature = "serde")]
mod rdjson;
/// A type that implements `std::fmt::Display` for diagnostic rendering.
///
@@ -184,6 +189,13 @@ impl std::fmt::Display for DisplayDiagnostics<'_> {
json_lines::JsonLinesRenderer::new(self.resolver, self.config)
.render(f, self.diagnostics)?;
}
#[cfg(feature = "serde")]
DiagnosticFormat::Rdjson => {
rdjson::RdjsonRenderer::new(self.resolver).render(f, self.diagnostics)?;
}
DiagnosticFormat::Pylint => {
PylintRenderer::new(self.resolver).render(f, self.diagnostics)?;
}
}
Ok(())
@@ -705,6 +717,9 @@ pub trait FileResolver {
/// Returns whether the file given is a Jupyter notebook.
fn is_notebook(&self, file: &UnifiedFile) -> bool;
/// Returns the current working directory.
fn current_directory(&self) -> &Path;
}
impl<T> FileResolver for T
@@ -740,6 +755,10 @@ where
UnifiedFile::Ruff(_) => unimplemented!("Expected an interned ty file"),
}
}
fn current_directory(&self) -> &Path {
self.system().current_directory().as_std_path()
}
}
impl FileResolver for &dyn Db {
@@ -772,6 +791,10 @@ impl FileResolver for &dyn Db {
UnifiedFile::Ruff(_) => unimplemented!("Expected an interned ty file"),
}
}
fn current_directory(&self) -> &Path {
self.system().current_directory().as_std_path()
}
}
/// An abstraction over a unit of user input.

View File

@@ -262,9 +262,6 @@ struct JsonEdit<'a> {
#[cfg(test)]
mod tests {
use ruff_diagnostics::{Edit, Fix};
use ruff_text_size::TextSize;
use crate::diagnostic::{
DiagnosticFormat,
render::tests::{
@@ -297,13 +294,7 @@ mod tests {
env.format(DiagnosticFormat::Json);
env.preview(false);
let diag = env
.err()
.fix(Fix::safe_edit(Edit::insertion(
"edit".to_string(),
TextSize::from(0),
)))
.build();
let diag = env.err().build();
insta::assert_snapshot!(
env.render(&diag),
@@ -317,23 +308,7 @@ mod tests {
"row": 1
},
"filename": "",
"fix": {
"applicability": "safe",
"edits": [
{
"content": "edit",
"end_location": {
"column": 1,
"row": 1
},
"location": {
"column": 1,
"row": 1
}
}
],
"message": null
},
"fix": null,
"location": {
"column": 1,
"row": 1
@@ -353,13 +328,7 @@ mod tests {
env.format(DiagnosticFormat::Json);
env.preview(true);
let diag = env
.err()
.fix(Fix::safe_edit(Edit::insertion(
"edit".to_string(),
TextSize::from(0),
)))
.build();
let diag = env.err().build();
insta::assert_snapshot!(
env.render(&diag),
@@ -370,17 +339,7 @@ mod tests {
"code": null,
"end_location": null,
"filename": null,
"fix": {
"applicability": "safe",
"edits": [
{
"content": "edit",
"end_location": null,
"location": null
}
],
"message": null
},
"fix": null,
"location": null,
"message": "main diagnostic message",
"noqa_row": null,

View File

@@ -0,0 +1,97 @@
use crate::diagnostic::{Diagnostic, SecondaryCode, render::FileResolver};
/// Generate violations in Pylint format.
///
/// The format is given by this string:
///
/// ```python
/// "%(path)s:%(row)d: [%(code)s] %(text)s"
/// ```
///
/// See: [Flake8 documentation](https://flake8.pycqa.org/en/latest/internal/formatters.html#pylint-formatter)
pub(super) struct PylintRenderer<'a> {
resolver: &'a dyn FileResolver,
}
impl<'a> PylintRenderer<'a> {
pub(super) fn new(resolver: &'a dyn FileResolver) -> Self {
Self { resolver }
}
}
impl PylintRenderer<'_> {
pub(super) fn render(
&self,
f: &mut std::fmt::Formatter,
diagnostics: &[Diagnostic],
) -> std::fmt::Result {
for diagnostic in diagnostics {
let (filename, row) = diagnostic
.primary_span_ref()
.map(|span| {
let file = span.file();
let row = span
.range()
.filter(|_| !self.resolver.is_notebook(file))
.map(|range| {
file.diagnostic_source(self.resolver)
.as_source_code()
.line_column(range.start())
.line
});
(file.relative_path(self.resolver).to_string_lossy(), row)
})
.unwrap_or_default();
let code = diagnostic
.secondary_code()
.map_or_else(|| diagnostic.name(), SecondaryCode::as_str);
let row = row.unwrap_or_default();
writeln!(
f,
"{path}:{row}: [{code}] {body}",
path = filename,
body = diagnostic.body()
)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::diagnostic::{
DiagnosticFormat,
render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics},
};
#[test]
fn output() {
let (env, diagnostics) = create_diagnostics(DiagnosticFormat::Pylint);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
}
#[test]
fn syntax_errors() {
let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Pylint);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
}
#[test]
fn missing_file() {
let mut env = TestEnvironment::new();
env.format(DiagnosticFormat::Pylint);
let diag = env.err().build();
insta::assert_snapshot!(
env.render(&diag),
@":1: [test-diagnostic] main diagnostic message",
);
}
}

View File

@@ -0,0 +1,235 @@
use serde::ser::SerializeSeq;
use serde::{Serialize, Serializer};
use ruff_diagnostics::{Edit, Fix};
use ruff_source_file::{LineColumn, SourceCode};
use ruff_text_size::Ranged;
use crate::diagnostic::Diagnostic;
use super::FileResolver;
pub struct RdjsonRenderer<'a> {
resolver: &'a dyn FileResolver,
}
impl<'a> RdjsonRenderer<'a> {
pub(super) fn new(resolver: &'a dyn FileResolver) -> Self {
Self { resolver }
}
pub(super) fn render(
&self,
f: &mut std::fmt::Formatter,
diagnostics: &[Diagnostic],
) -> std::fmt::Result {
write!(
f,
"{:#}",
serde_json::json!(RdjsonDiagnostics::new(diagnostics, self.resolver))
)
}
}
struct ExpandedDiagnostics<'a> {
resolver: &'a dyn FileResolver,
diagnostics: &'a [Diagnostic],
}
impl Serialize for ExpandedDiagnostics<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_seq(Some(self.diagnostics.len()))?;
for diagnostic in self.diagnostics {
let value = diagnostic_to_rdjson(diagnostic, self.resolver);
s.serialize_element(&value)?;
}
s.end()
}
}
fn diagnostic_to_rdjson<'a>(
diagnostic: &'a Diagnostic,
resolver: &'a dyn FileResolver,
) -> RdjsonDiagnostic<'a> {
let span = diagnostic.primary_span_ref();
let source_file = span.map(|span| {
let file = span.file();
(file.path(resolver), file.diagnostic_source(resolver))
});
let location = source_file.as_ref().map(|(path, source)| {
let range = diagnostic.range().map(|range| {
let source_code = source.as_source_code();
let start = source_code.line_column(range.start());
let end = source_code.line_column(range.end());
RdjsonRange::new(start, end)
});
RdjsonLocation { path, range }
});
let edits = diagnostic.fix().map(Fix::edits).unwrap_or_default();
RdjsonDiagnostic {
message: diagnostic.body(),
location,
code: RdjsonCode {
value: diagnostic
.secondary_code()
.map_or_else(|| diagnostic.name(), |code| code.as_str()),
url: diagnostic.to_ruff_url(),
},
suggestions: rdjson_suggestions(
edits,
source_file
.as_ref()
.map(|(_, source)| source.as_source_code()),
),
}
}
fn rdjson_suggestions<'a>(
edits: &'a [Edit],
source_code: Option<SourceCode>,
) -> Vec<RdjsonSuggestion<'a>> {
if edits.is_empty() {
return Vec::new();
}
let Some(source_code) = source_code else {
debug_assert!(false, "Expected a source file for a diagnostic with a fix");
return Vec::new();
};
edits
.iter()
.map(|edit| {
let start = source_code.line_column(edit.start());
let end = source_code.line_column(edit.end());
let range = RdjsonRange::new(start, end);
RdjsonSuggestion {
range,
text: edit.content().unwrap_or_default(),
}
})
.collect()
}
#[derive(Serialize)]
struct RdjsonDiagnostics<'a> {
diagnostics: ExpandedDiagnostics<'a>,
severity: &'static str,
source: RdjsonSource,
}
impl<'a> RdjsonDiagnostics<'a> {
fn new(diagnostics: &'a [Diagnostic], resolver: &'a dyn FileResolver) -> Self {
Self {
source: RdjsonSource {
name: "ruff",
url: env!("CARGO_PKG_HOMEPAGE"),
},
severity: "WARNING",
diagnostics: ExpandedDiagnostics {
diagnostics,
resolver,
},
}
}
}
#[derive(Serialize)]
struct RdjsonSource {
name: &'static str,
url: &'static str,
}
#[derive(Serialize)]
struct RdjsonDiagnostic<'a> {
code: RdjsonCode<'a>,
#[serde(skip_serializing_if = "Option::is_none")]
location: Option<RdjsonLocation<'a>>,
message: &'a str,
#[serde(skip_serializing_if = "Vec::is_empty")]
suggestions: Vec<RdjsonSuggestion<'a>>,
}
#[derive(Serialize)]
struct RdjsonLocation<'a> {
path: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
range: Option<RdjsonRange>,
}
#[derive(Default, Serialize)]
struct RdjsonRange {
end: LineColumn,
start: LineColumn,
}
impl RdjsonRange {
fn new(start: LineColumn, end: LineColumn) -> Self {
Self { start, end }
}
}
#[derive(Serialize)]
struct RdjsonCode<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
url: Option<String>,
value: &'a str,
}
#[derive(Serialize)]
struct RdjsonSuggestion<'a> {
range: RdjsonRange,
text: &'a str,
}
#[cfg(test)]
mod tests {
use crate::diagnostic::{
DiagnosticFormat,
render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics},
};
#[test]
fn output() {
let (env, diagnostics) = create_diagnostics(DiagnosticFormat::Rdjson);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
}
#[test]
fn syntax_errors() {
let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Rdjson);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
}
#[test]
fn missing_file_stable() {
let mut env = TestEnvironment::new();
env.format(DiagnosticFormat::Rdjson);
env.preview(false);
let diag = env.err().build();
insta::assert_snapshot!(env.render(&diag));
}
#[test]
fn missing_file_preview() {
let mut env = TestEnvironment::new();
env.format(DiagnosticFormat::Rdjson);
env.preview(true);
let diag = env.err().build();
insta::assert_snapshot!(env.render(&diag));
}
}

View File

@@ -1,7 +1,6 @@
---
source: crates/ruff_linter/src/message/pylint.rs
expression: content
snapshot_kind: text
source: crates/ruff_db/src/diagnostic/render/pylint.rs
expression: env.render_diagnostics(&diagnostics)
---
fib.py:1: [F401] `os` imported but unused
fib.py:6: [F841] Local variable `x` is assigned to but never used

View File

@@ -0,0 +1,6 @@
---
source: crates/ruff_db/src/diagnostic/render/pylint.rs
expression: env.render_diagnostics(&diagnostics)
---
syntax_errors.py:1: [invalid-syntax] SyntaxError: Expected one or more symbol names after import
syntax_errors.py:3: [invalid-syntax] SyntaxError: Expected ')', found newline

View File

@@ -0,0 +1,20 @@
---
source: crates/ruff_db/src/diagnostic/render/rdjson.rs
expression: env.render(&diag)
---
{
"diagnostics": [
{
"code": {
"url": "https://docs.astral.sh/ruff/rules/test-diagnostic",
"value": "test-diagnostic"
},
"message": "main diagnostic message"
}
],
"severity": "WARNING",
"source": {
"name": "ruff",
"url": "https://docs.astral.sh/ruff"
}
}

View File

@@ -0,0 +1,20 @@
---
source: crates/ruff_db/src/diagnostic/render/rdjson.rs
expression: env.render(&diag)
---
{
"diagnostics": [
{
"code": {
"url": "https://docs.astral.sh/ruff/rules/test-diagnostic",
"value": "test-diagnostic"
},
"message": "main diagnostic message"
}
],
"severity": "WARNING",
"source": {
"name": "ruff",
"url": "https://docs.astral.sh/ruff"
}
}

View File

@@ -1,7 +1,6 @@
---
source: crates/ruff_linter/src/message/rdjson.rs
expression: content
snapshot_kind: text
source: crates/ruff_db/src/diagnostic/render/rdjson.rs
expression: env.render_diagnostics(&diagnostics)
---
{
"diagnostics": [
@@ -96,7 +95,7 @@ snapshot_kind: text
"message": "Undefined name `a`"
}
],
"severity": "warning",
"severity": "WARNING",
"source": {
"name": "ruff",
"url": "https://docs.astral.sh/ruff"

View File

@@ -1,14 +1,12 @@
---
source: crates/ruff_linter/src/message/rdjson.rs
expression: content
snapshot_kind: text
source: crates/ruff_db/src/diagnostic/render/rdjson.rs
expression: env.render_diagnostics(&diagnostics)
---
{
"diagnostics": [
{
"code": {
"url": null,
"value": null
"value": "invalid-syntax"
},
"location": {
"path": "syntax_errors.py",
@@ -27,8 +25,7 @@ snapshot_kind: text
},
{
"code": {
"url": null,
"value": null
"value": "invalid-syntax"
},
"location": {
"path": "syntax_errors.py",
@@ -46,7 +43,7 @@ snapshot_kind: text
"message": "SyntaxError: Expected ')', found newline"
}
],
"severity": "warning",
"severity": "WARNING",
"source": {
"name": "ruff",
"url": "https://docs.astral.sh/ruff"

View File

@@ -28,6 +28,21 @@ pub use web_time::{Instant, SystemTime, SystemTimeError};
pub type FxDashMap<K, V> = dashmap::DashMap<K, V, BuildHasherDefault<FxHasher>>;
pub type FxDashSet<K> = dashmap::DashSet<K, BuildHasherDefault<FxHasher>>;
static VERSION: std::sync::OnceLock<String> = std::sync::OnceLock::new();
/// Returns the version of the executing program if set.
pub fn program_version() -> Option<&'static str> {
VERSION.get().map(|version| version.as_str())
}
/// Sets the version of the executing program.
///
/// ## Errors
/// If the version has already been initialized (can only be set once).
pub fn set_program_version(version: String) -> Result<(), String> {
VERSION.set(version)
}
/// Most basic database that gives access to files, the host system, source code, and parsed AST.
#[salsa::db]
pub trait Db: salsa::Database {

View File

@@ -1 +1,4 @@
_(f"{'value'}")
# Don't trigger for t-strings
_(t"{'value'}")

View File

@@ -13,3 +13,7 @@ from logging import info
info(f"{name}")
info(f"{__name__}")
# Don't trigger for t-strings
info(t"{name}")
info(t"{__name__}")

View File

@@ -47,3 +47,7 @@ def test_error_match_is_empty():
with pytest.raises(ValueError, match=f""):
raise ValueError("Can't divide 1 by 0")
def test_ok_t_string_match():
with pytest.raises(ValueError, match=t""):
raise ValueError("Can't divide 1 by 0")

View File

@@ -23,3 +23,9 @@ def f():
pytest.fail(msg=f"")
pytest.fail(reason="")
pytest.fail(reason=f"")
# Skip for t-strings
def g():
pytest.fail(t"")
pytest.fail(msg=t"")
pytest.fail(reason=t"")

View File

@@ -32,3 +32,7 @@ def test_error_match_is_empty():
with pytest.warns(UserWarning, match=f""):
pass
def test_ok_match_t_string():
with pytest.warns(UserWarning, match=t""):
pass

View File

@@ -245,3 +245,14 @@ def f(bar: str):
class C:
def __init__(self, x) -> None:
print(locals())
###
# Should trigger for t-string here
# even though the corresponding f-string
# does not trigger (since it is common in stubs)
###
class C:
def f(self, x, y):
"""Docstring."""
msg = t"{x}..."
raise NotImplementedError(msg)

View File

@@ -0,0 +1,26 @@
from pathlib import (
Path,
PosixPath,
PurePath,
PurePosixPath,
PureWindowsPath,
WindowsPath,
)
import pathlib
path = Path()
posix_path: pathlib.PosixPath = PosixPath()
pure_path: PurePath = PurePath()
pure_posix_path = pathlib.PurePosixPath()
pure_windows_path: PureWindowsPath = pathlib.PureWindowsPath()
windows_path: pathlib.WindowsPath = pathlib.WindowsPath()
### No Errors
path.with_suffix(".")
posix_path.with_suffix(".")
pure_path.with_suffix(".")
pure_posix_path.with_suffix(".")
pure_windows_path.with_suffix(".")
windows_path.with_suffix(".")

View File

@@ -48,6 +48,39 @@ from typing import override, overload
def BAD_FUNC():
pass
@overload
def BAD_FUNC():
pass
import ast
from ast import NodeTransformer
class Visitor(ast.NodeVisitor):
def visit_Constant(self, node):
pass
def bad_Name(self):
pass
class ExtendsVisitor(Visitor):
def visit_Constant(self, node):
pass
class Transformer(NodeTransformer):
def visit_Constant(self, node):
pass
from http.server import BaseHTTPRequestHandler
class MyRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
pass
def dont_GET(self):
pass

View File

@@ -189,3 +189,18 @@ f"{ham[lower + 1 :, "columnname"]}"
#: Okay: https://github.com/astral-sh/ruff/issues/12023
f"{x = :.2f}"
f"{(x) = :.2f}"
# t-strings
t"{ {'a': 1} }"
t"{[ { {'a': 1} } ]}"
t"normal { {t"{ { [1, 2] } }" } } normal"
t"{x = :.2f}"
t"{(x) = :.2f}"
#: Okay
t"{ham[lower +1 :, "columnname"]}"
#: E203:1:13
t"{ham[lower + 1 :, "columnname"]}"

View File

@@ -142,3 +142,20 @@ class PEP696GoodWithEmptyBases[A: object="foo"[::-1], B: object =[[["foo", "bar"
class PEP696GoodWithNonEmptyBases[A: object="foo"[::-1], B: object =[[["foo", "bar"]]], C: object= bytes](object, something_dynamic[x::-1]):
pass
# E231
t"{(a,b)}"
# Okay because it's hard to differentiate between the usages of a colon in a t-string
t"{a:=1}"
t"{ {'a':1} }"
t"{a:.3f}"
t"{(a:=1)}"
t"{(lambda x:x)}"
t"normal{t"{a:.3f}"}normal"
#: Okay
snapshot.file_uri[len(t's3://{self.s3_bucket_name}/'):]
#: E231
{len(t's3://{self.s3_bucket_name}/'):1}

View File

@@ -722,3 +722,10 @@ def inconsistent_indent_byte_size():
    Returns:
"""
def line_continuation_chars():\
"""No fix should be offered for D201/D202 because of the line continuation chars."""\
...

View File

@@ -22,3 +22,10 @@ assert b"hello" # [assert-on-string-literal]
assert "", b"hi" # [assert-on-string-literal]
assert "WhyNotHere?", "HereIsOk" # [assert-on-string-literal]
assert 12, "ok here"
# t-strings are always True even when "empty"
# skip lint in this case
assert t""
assert t"hey"
assert t"{a}"

View File

@@ -140,3 +140,15 @@ class Foo:
def unused_message_2(self, x):
msg = ""
raise NotImplementedError(x)
class TPerson:
def developer_greeting(self, name): # [no-self-use]
print(t"Greetings {name}!")
def greeting_1(self):
print(t"Hello from {self.name} !")
def tstring(self, x):
msg = t"{x}"
raise NotImplementedError(msg)

View File

@@ -33,3 +33,11 @@ class Foo:
def __init__(self, bar):
self.bar = bar
# This is a type error, out of scope for the rule
class Foo:
__slots__ = t"bar{baz}"
def __init__(self, bar):
self.bar = bar

View File

@@ -84,3 +84,7 @@ def _match_ignore(line):
# Not a valid type annotation but this test shouldn't result in a panic.
# Refer: https://github.com/astral-sh/ruff/issues/11736
x: '"foo".encode("utf-8")'
# AttributeError for t-strings so skip lint
(t"foo{bar}").encode("utf-8")
(t"foo{bar}").encode(encoding="utf-8")

View File

@@ -90,3 +90,7 @@ bool(True)and None
int(1)and None
float(1.)and None
bool(True)and()
# t-strings are not native literals
str(t"hey")

View File

@@ -0,0 +1,16 @@
from itertools import starmap
import itertools
# Errors in Python 3.14+
starmap(func, zip(a, b, c, strict=True))
starmap(func, zip(a, b, c, strict=False))
starmap(func, zip(a, b, c, strict=strict))
# No errors
starmap(func)
starmap(func, zip(a, b, c, **kwargs))
starmap(func, zip(a, b, c), foo)
starmap(func, zip(a, b, c, lorem=ipsum))
starmap(func, zip(a, b, c), lorem=ipsum)

View File

@@ -15,8 +15,6 @@ pub use github::GithubEmitter;
pub use gitlab::GitlabEmitter;
pub use grouped::GroupedEmitter;
pub use junit::JunitEmitter;
pub use pylint::PylintEmitter;
pub use rdjson::RdjsonEmitter;
use ruff_notebook::NotebookIndex;
use ruff_source_file::{LineColumn, SourceFile};
use ruff_text_size::{Ranged, TextRange, TextSize};
@@ -31,8 +29,6 @@ mod github;
mod gitlab;
mod grouped;
mod junit;
mod pylint;
mod rdjson;
mod sarif;
mod text;
@@ -80,6 +76,13 @@ where
body,
);
let span = Span::from(file).with_range(range);
let mut annotation = Annotation::primary(span);
if let Some(suggestion) = suggestion {
annotation = annotation.message(suggestion);
}
diagnostic.annotate(annotation);
if let Some(fix) = fix {
diagnostic.set_fix(fix);
}
@@ -92,13 +95,6 @@ where
diagnostic.set_noqa_offset(noqa_offset);
}
let span = Span::from(file).with_range(range);
let mut annotation = Annotation::primary(span);
if let Some(suggestion) = suggestion {
annotation = annotation.message(suggestion);
}
diagnostic.annotate(annotation);
diagnostic.set_secondary_code(SecondaryCode::new(rule.noqa_code().to_string()));
diagnostic
@@ -130,6 +126,10 @@ impl FileResolver for EmitterContext<'_> {
UnifiedFile::Ruff(file) => self.notebook_indexes.get(file.name()).is_some(),
}
}
fn current_directory(&self) -> &std::path::Path {
crate::fs::get_cwd()
}
}
struct MessageWithLocation<'a> {

View File

@@ -1,72 +0,0 @@
use std::io::Write;
use ruff_db::diagnostic::Diagnostic;
use ruff_source_file::OneIndexed;
use crate::fs::relativize_path;
use crate::message::{Emitter, EmitterContext};
/// Generate violations in Pylint format.
/// See: [Flake8 documentation](https://flake8.pycqa.org/en/latest/internal/formatters.html#pylint-formatter)
#[derive(Default)]
pub struct PylintEmitter;
impl Emitter for PylintEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
diagnostics: &[Diagnostic],
context: &EmitterContext,
) -> anyhow::Result<()> {
for diagnostic in diagnostics {
let filename = diagnostic.expect_ruff_filename();
let row = if context.is_notebook(&filename) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
OneIndexed::from_zero_indexed(0)
} else {
diagnostic.expect_ruff_start_location().line
};
let body = if let Some(code) = diagnostic.secondary_code() {
format!("[{code}] {body}", body = diagnostic.body())
} else {
diagnostic.body().to_string()
};
writeln!(
writer,
"{path}:{row}: {body}",
path = relativize_path(&filename),
)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use insta::assert_snapshot;
use crate::message::PylintEmitter;
use crate::message::tests::{
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
};
#[test]
fn output() {
let mut emitter = PylintEmitter;
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
assert_snapshot!(content);
}
#[test]
fn syntax_errors() {
let mut emitter = PylintEmitter;
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
assert_snapshot!(content);
}
}

View File

@@ -1,143 +0,0 @@
use std::io::Write;
use serde::ser::SerializeSeq;
use serde::{Serialize, Serializer};
use serde_json::{Value, json};
use ruff_db::diagnostic::Diagnostic;
use ruff_source_file::SourceCode;
use ruff_text_size::Ranged;
use crate::Edit;
use crate::message::{Emitter, EmitterContext, LineColumn};
#[derive(Default)]
pub struct RdjsonEmitter;
impl Emitter for RdjsonEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
diagnostics: &[Diagnostic],
_context: &EmitterContext,
) -> anyhow::Result<()> {
serde_json::to_writer_pretty(
writer,
&json!({
"source": {
"name": "ruff",
"url": "https://docs.astral.sh/ruff",
},
"severity": "warning",
"diagnostics": &ExpandedMessages{ diagnostics }
}),
)?;
Ok(())
}
}
struct ExpandedMessages<'a> {
diagnostics: &'a [Diagnostic],
}
impl Serialize for ExpandedMessages<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_seq(Some(self.diagnostics.len()))?;
for message in self.diagnostics {
let value = message_to_rdjson_value(message);
s.serialize_element(&value)?;
}
s.end()
}
}
fn message_to_rdjson_value(message: &Diagnostic) -> Value {
let source_file = message.expect_ruff_source_file();
let source_code = source_file.to_source_code();
let start_location = source_code.line_column(message.expect_range().start());
let end_location = source_code.line_column(message.expect_range().end());
if let Some(fix) = message.fix() {
json!({
"message": message.body(),
"location": {
"path": message.expect_ruff_filename(),
"range": rdjson_range(start_location, end_location),
},
"code": {
"value": message.secondary_code(),
"url": message.to_ruff_url(),
},
"suggestions": rdjson_suggestions(fix.edits(), &source_code),
})
} else {
json!({
"message": message.body(),
"location": {
"path": message.expect_ruff_filename(),
"range": rdjson_range(start_location, end_location),
},
"code": {
"value": message.secondary_code(),
"url": message.to_ruff_url(),
},
})
}
}
fn rdjson_suggestions(edits: &[Edit], source_code: &SourceCode) -> Value {
Value::Array(
edits
.iter()
.map(|edit| {
let location = source_code.line_column(edit.start());
let end_location = source_code.line_column(edit.end());
json!({
"range": rdjson_range(location, end_location),
"text": edit.content().unwrap_or_default(),
})
})
.collect(),
)
}
fn rdjson_range(start: LineColumn, end: LineColumn) -> Value {
json!({
"start": start,
"end": end,
})
}
#[cfg(test)]
mod tests {
use insta::assert_snapshot;
use crate::message::RdjsonEmitter;
use crate::message::tests::{
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
};
#[test]
fn output() {
let mut emitter = RdjsonEmitter;
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
assert_snapshot!(content);
}
#[test]
fn syntax_errors() {
let mut emitter = RdjsonEmitter;
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
assert_snapshot!(content);
}
}

View File

@@ -1,7 +0,0 @@
---
source: crates/ruff_linter/src/message/pylint.rs
expression: content
snapshot_kind: text
---
syntax_errors.py:1: SyntaxError: Expected one or more symbol names after import
syntax_errors.py:3: SyntaxError: Expected ')', found newline

View File

@@ -1,6 +1,5 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
snapshot_kind: text
---
B018.py:11:5: B018 Found useless expression. Either assign it to a variable or remove it.
|

View File

@@ -1,9 +1,10 @@
---
source: crates/ruff_linter/src/rules/flake8_gettext/mod.rs
snapshot_kind: text
---
INT001.py:1:3: INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
1 | _(f"{'value'}")
| ^^^^^^^^^^^^ INT001
2 |
3 | # Don't trigger for t-strings
|

View File

@@ -52,4 +52,6 @@ G004.py:15:6: G004 Logging statement uses f-string
14 | info(f"{name}")
15 | info(f"{__name__}")
| ^^^^^^^^^^^^^ G004
16 |
17 | # Don't trigger for t-strings
|

View File

@@ -1,6 +1,5 @@
---
source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs
snapshot_kind: text
---
PT016.py:19:5: PT016 No message passed to `pytest.fail()`
|
@@ -67,4 +66,6 @@ PT016.py:25:5: PT016 No message passed to `pytest.fail()`
24 | pytest.fail(reason="")
25 | pytest.fail(reason=f"")
| ^^^^^^^^^^^ PT016
26 |
27 | # Skip for t-strings
|

View File

@@ -23,17 +23,28 @@ use crate::checkers::ast::Checker;
///
/// ## Example
/// ```python
/// var: str | "int"
/// var: "Foo" | None
///
///
/// class Foo: ...
/// ```
///
/// Use instead:
/// ```python
/// var: str | int
/// from __future__ import annotations
///
/// var: Foo | None
///
///
/// class Foo: ...
/// ```
///
/// Or, extend the quotes to include the entire union:
/// ```python
/// var: "str | int"
/// var: "Foo | None"
///
///
/// class Foo: ...
/// ```
///
/// ## References

View File

@@ -66,3 +66,13 @@ ARG.py:216:24: ARG002 Unused method argument: `x`
| ^ ARG002
217 | print("Hello, world!")
|
ARG.py:255:20: ARG002 Unused method argument: `y`
|
253 | ###
254 | class C:
255 | def f(self, x, y):
| ^ ARG002
256 | """Docstring."""
257 | msg = t"{x}..."
|

View File

@@ -8,6 +8,7 @@ mod tests {
use std::path::Path;
use anyhow::Result;
use ruff_python_ast::PythonVersion;
use test_case::test_case;
use crate::assert_diagnostics;
@@ -143,4 +144,22 @@ mod tests {
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::InvalidPathlibWithSuffix, Path::new("PTH210_2.py"))]
fn pathlib_with_suffix_py314(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"py314__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("flake8_use_pathlib").join(path).as_path(),
&settings::LinterSettings {
unresolved_target_version: PythonVersion::PY314.into(),
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -1,7 +1,7 @@
use crate::checkers::ast::Checker;
use crate::{Edit, Fix, FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{self as ast, StringFlags};
use ruff_python_ast::{self as ast, PythonVersion, StringFlags};
use ruff_python_semantic::SemanticModel;
use ruff_python_semantic::analyze::typing;
use ruff_text_size::Ranged;
@@ -9,12 +9,13 @@ use ruff_text_size::Ranged;
/// ## What it does
/// Checks for `pathlib.Path.with_suffix()` calls where
/// the given suffix does not have a leading dot
/// or the given suffix is a single dot `"."`.
/// or the given suffix is a single dot `"."` and the
/// Python version is less than 3.14.
///
/// ## Why is this bad?
/// `Path.with_suffix()` will raise an error at runtime
/// if the given suffix is not prefixed with a dot
/// or it is a single dot `"."`.
/// or, in versions prior to Python 3.14, if it is a single dot `"."`.
///
/// ## Example
///
@@ -57,9 +58,6 @@ use ruff_text_size::Ranged;
/// No fix is offered if the suffix `"."` is given, since the intent is unclear.
#[derive(ViolationMetadata)]
pub(crate) struct InvalidPathlibWithSuffix {
// TODO: Since "." is a correct suffix in Python 3.14,
// we will need to update this rule and documentation
// once Ruff supports Python 3.14.
single_dot: bool,
}
@@ -116,6 +114,13 @@ pub(crate) fn invalid_pathlib_with_suffix(checker: &Checker, call: &ast::ExprCal
};
let single_dot = string_value == ".";
// As of Python 3.14, a single dot is considered a valid suffix.
// https://docs.python.org/3.14/library/pathlib.html#pathlib.PurePath.with_suffix
if single_dot && checker.target_version() >= PythonVersion::PY314 {
return;
}
let mut diagnostic =
checker.report_diagnostic(InvalidPathlibWithSuffix { single_dot }, call.range);
if !single_dot {

View File

@@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
---

View File

@@ -1,9 +1,7 @@
use ruff_python_ast::{Decorator, Stmt};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::{Decorator, Stmt, identifier::Identifier};
use ruff_python_semantic::SemanticModel;
use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::analyze::{class::any_base_class, visibility};
use ruff_python_stdlib::str;
use crate::Violation;
@@ -84,6 +82,41 @@ pub(crate) fn invalid_function_name(
return;
}
let parent_class = semantic
.current_statement_parent()
.and_then(|parent| parent.as_class_def_stmt());
// Ignore the visit_* methods of the ast.NodeVisitor and ast.NodeTransformer classes.
if name.starts_with("visit_")
&& parent_class.is_some_and(|class| {
any_base_class(class, semantic, &mut |superclass| {
let qualified = semantic.resolve_qualified_name(superclass);
qualified.is_some_and(|name| {
matches!(name.segments(), ["ast", "NodeVisitor" | "NodeTransformer"])
})
})
})
{
return;
}
// Ignore the do_* methods of the http.server.BaseHTTPRequestHandler class
if name.starts_with("do_")
&& parent_class.is_some_and(|class| {
any_base_class(class, semantic, &mut |superclass| {
let qualified = semantic.resolve_qualified_name(superclass);
qualified.is_some_and(|name| {
matches!(
name.segments(),
["http", "server", "BaseHTTPRequestHandler"]
)
})
})
})
{
return;
}
checker.report_diagnostic(
InvalidFunctionName {
name: name.to_string(),

View File

@@ -37,3 +37,21 @@ N802.py:40:9: N802 Function name `testTest` should be lowercase
| ^^^^^^^^ N802
41 | assert True
|
N802.py:65:9: N802 Function name `bad_Name` should be lowercase
|
63 | pass
64 |
65 | def bad_Name(self):
| ^^^^^^^^ N802
66 | pass
|
N802.py:84:9: N802 Function name `dont_GET` should be lowercase
|
82 | pass
83 |
84 | def dont_GET(self):
| ^^^^^^^^ N802
85 | pass
|

View File

@@ -126,7 +126,7 @@ impl AlwaysFixableViolation for WhitespaceBeforePunctuation {
/// E201, E202, E203
pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
let mut fstrings = 0u32;
let mut interpolated_strings = 0u32;
let mut brackets = vec![];
let mut prev_token = None;
let mut iter = line.tokens().iter().peekable();
@@ -134,8 +134,10 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
while let Some(token) = iter.next() {
let kind = token.kind();
match kind {
TokenKind::FStringStart => fstrings += 1,
TokenKind::FStringEnd => fstrings = fstrings.saturating_sub(1),
TokenKind::FStringStart | TokenKind::TStringStart => interpolated_strings += 1,
TokenKind::FStringEnd | TokenKind::TStringEnd => {
interpolated_strings = interpolated_strings.saturating_sub(1);
}
TokenKind::Lsqb => {
brackets.push(kind);
}
@@ -161,7 +163,9 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
// Here, `{{` / `}} would be interpreted as a single raw `{` / `}`
// character.
match symbol {
BracketOrPunctuation::OpenBracket(symbol) if symbol != '{' || fstrings == 0 => {
BracketOrPunctuation::OpenBracket(symbol)
if symbol != '{' || interpolated_strings == 0 =>
{
let (trailing, trailing_len) = line.trailing_whitespace(token);
if !matches!(trailing, Whitespace::None) {
if let Some(mut diagnostic) = context.report_diagnostic_if_enabled(
@@ -173,7 +177,9 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
}
}
}
BracketOrPunctuation::CloseBracket(symbol) if symbol != '}' || fstrings == 0 => {
BracketOrPunctuation::CloseBracket(symbol)
if symbol != '}' || interpolated_strings == 0 =>
{
if !matches!(prev_token, Some(TokenKind::Comma)) {
if let (Whitespace::Single | Whitespace::Many | Whitespace::Tab, offset) =
line.leading_whitespace(token)
@@ -286,7 +292,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
}
}
} else {
if fstrings > 0
if interpolated_strings > 0
&& symbol == ':'
&& matches!(prev_token, Some(TokenKind::Equal))
{

View File

@@ -41,7 +41,7 @@ impl AlwaysFixableViolation for MissingWhitespace {
/// E231
pub(crate) fn missing_whitespace(line: &LogicalLine, context: &LintContext) {
let mut fstrings = 0u32;
let mut interpolated_strings = 0u32;
let mut definition_state = DefinitionState::from_tokens(line.tokens());
let mut brackets = Vec::new();
let mut iter = line.tokens().iter().peekable();
@@ -50,21 +50,23 @@ pub(crate) fn missing_whitespace(line: &LogicalLine, context: &LintContext) {
let kind = token.kind();
definition_state.visit_token_kind(kind);
match kind {
TokenKind::FStringStart => fstrings += 1,
TokenKind::FStringEnd => fstrings = fstrings.saturating_sub(1),
TokenKind::Lsqb if fstrings == 0 => {
TokenKind::FStringStart | TokenKind::TStringStart => interpolated_strings += 1,
TokenKind::FStringEnd | TokenKind::TStringEnd => {
interpolated_strings = interpolated_strings.saturating_sub(1);
}
TokenKind::Lsqb if interpolated_strings == 0 => {
brackets.push(kind);
}
TokenKind::Rsqb if fstrings == 0 => {
TokenKind::Rsqb if interpolated_strings == 0 => {
brackets.pop();
}
TokenKind::Lbrace if fstrings == 0 => {
TokenKind::Lbrace if interpolated_strings == 0 => {
brackets.push(kind);
}
TokenKind::Rbrace if fstrings == 0 => {
TokenKind::Rbrace if interpolated_strings == 0 => {
brackets.pop();
}
TokenKind::Colon if fstrings > 0 => {
TokenKind::Colon if interpolated_strings > 0 => {
// Colon in f-string, no space required. This will yield false
// negatives for cases like the following as it's hard to
// differentiate between the usage of a colon in a f-string.

View File

@@ -183,3 +183,23 @@ E20.py:145:5: E201 [*] Whitespace after '['
146 146 |
147 147 | #: Okay
148 148 | ham[lower + offset :: upper + offset]
E20.py:195:5: E201 [*] Whitespace after '['
|
193 | # t-strings
194 | t"{ {'a': 1} }"
195 | t"{[ { {'a': 1} } ]}"
| ^ E201
196 | t"normal { {t"{ { [1, 2] } }" } } normal"
|
= help: Remove whitespace before '['
Safe fix
192 192 |
193 193 | # t-strings
194 194 | t"{ {'a': 1} }"
195 |-t"{[ { {'a': 1} } ]}"
195 |+t"{[{ {'a': 1} } ]}"
196 196 | t"normal { {t"{ { [1, 2] } }" } } normal"
197 197 |
198 198 | t"{x = :.2f}"

View File

@@ -165,3 +165,23 @@ E20.py:172:12: E202 [*] Whitespace before ']'
173 173 |
174 174 | #: E203:1:10
175 175 | ham[upper :]
E20.py:195:18: E202 [*] Whitespace before ']'
|
193 | # t-strings
194 | t"{ {'a': 1} }"
195 | t"{[ { {'a': 1} } ]}"
| ^ E202
196 | t"normal { {t"{ { [1, 2] } }" } } normal"
|
= help: Remove whitespace before ']'
Safe fix
192 192 |
193 193 | # t-strings
194 194 | t"{ {'a': 1} }"
195 |-t"{[ { {'a': 1} } ]}"
195 |+t"{[ { {'a': 1} }]}"
196 196 | t"normal { {t"{ { [1, 2] } }" } } normal"
197 197 |
198 198 | t"{x = :.2f}"

View File

@@ -345,3 +345,19 @@ E20.py:187:17: E203 [*] Whitespace before ':'
188 188 |
189 189 | #: Okay: https://github.com/astral-sh/ruff/issues/12023
190 190 | f"{x = :.2f}"
E20.py:205:17: E203 [*] Whitespace before ':'
|
204 | #: E203:1:13
205 | t"{ham[lower + 1 :, "columnname"]}"
| ^^ E203
|
= help: Remove whitespace before ':'
Safe fix
202 202 | t"{ham[lower +1 :, "columnname"]}"
203 203 |
204 204 | #: E203:1:13
205 |-t"{ham[lower + 1 :, "columnname"]}"
205 |+t"{ham[lower + 1:, "columnname"]}"
206 206 |

View File

@@ -905,3 +905,38 @@ E23.py:126:99: E231 [*] Missing whitespace after ':'
127 127 | pass
128 128 |
129 129 | # Should be no E231 errors on any of these:
E23.py:147:6: E231 [*] Missing whitespace after ','
|
146 | # E231
147 | t"{(a,b)}"
| ^ E231
148 |
149 | # Okay because it's hard to differentiate between the usages of a colon in a t-string
|
= help: Add missing whitespace
Safe fix
144 144 | pass
145 145 |
146 146 | # E231
147 |-t"{(a,b)}"
147 |+t"{(a, b)}"
148 148 |
149 149 | # Okay because it's hard to differentiate between the usages of a colon in a t-string
150 150 | t"{a:=1}"
E23.py:161:37: E231 [*] Missing whitespace after ':'
|
160 | #: E231
161 | {len(t's3://{self.s3_bucket_name}/'):1}
| ^ E231
|
= help: Add missing whitespace
Safe fix
158 158 | snapshot.file_uri[len(t's3://{self.s3_bucket_name}/'):]
159 159 |
160 160 | #: E231
161 |-{len(t's3://{self.s3_bucket_name}/'):1}
161 |+{len(t's3://{self.s3_bucket_name}/'): 1}

View File

@@ -10,7 +10,7 @@ use ruff_text_size::TextRange;
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
use crate::registry::Rule;
use crate::{AlwaysFixableViolation, Edit, Fix};
use crate::{Edit, Fix, FixAvailability, Violation};
/// ## What it does
/// Checks for docstrings on functions that are separated by one or more blank
@@ -42,15 +42,17 @@ pub(crate) struct BlankLineBeforeFunction {
num_lines: usize,
}
impl AlwaysFixableViolation for BlankLineBeforeFunction {
impl Violation for BlankLineBeforeFunction {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let BlankLineBeforeFunction { num_lines } = self;
format!("No blank lines allowed before function docstring (found {num_lines})")
}
fn fix_title(&self) -> String {
"Remove blank line(s) before function docstring".to_string()
fn fix_title(&self) -> Option<String> {
Some("Remove blank line(s) before function docstring".to_string())
}
}
@@ -86,15 +88,17 @@ pub(crate) struct BlankLineAfterFunction {
num_lines: usize,
}
impl AlwaysFixableViolation for BlankLineAfterFunction {
impl Violation for BlankLineAfterFunction {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let BlankLineAfterFunction { num_lines } = self;
format!("No blank lines allowed after function docstring (found {num_lines})")
}
fn fix_title(&self) -> String {
"Remove blank line(s) after function docstring".to_string()
fn fix_title(&self) -> Option<String> {
Some("Remove blank line(s) after function docstring".to_string())
}
}
@@ -115,12 +119,14 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri
let mut lines = UniversalNewlineIterator::with_offset(before, function.start()).rev();
let mut blank_lines_before = 0usize;
let mut blank_lines_start = lines.next().map(|l| l.end()).unwrap_or_default();
let mut start_is_line_continuation = false;
for line in lines {
if line.trim().is_empty() {
blank_lines_before += 1;
blank_lines_start = line.start();
} else {
start_is_line_continuation = line.ends_with('\\');
break;
}
}
@@ -132,11 +138,14 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri
},
docstring.range(),
);
// Delete the blank line before the docstring.
diagnostic.set_fix(Fix::safe_edit(Edit::deletion(
blank_lines_start,
docstring.line_start(),
)));
// Do not offer fix if a \ would cause it to be a syntax error
if !start_is_line_continuation {
// Delete the blank line before the docstring.
diagnostic.set_fix(Fix::safe_edit(Edit::deletion(
blank_lines_start,
docstring.line_start(),
)));
}
}
}
@@ -156,7 +165,9 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri
// Count the number of blank lines after the docstring.
let mut blank_lines_after = 0usize;
let mut lines = UniversalNewlineIterator::with_offset(after, docstring.end()).peekable();
let first_line_end = lines.next().map(|l| l.end()).unwrap_or_default();
let first_line = lines.next();
let first_line_line_continuation = first_line.as_ref().is_some_and(|l| l.ends_with('\\'));
let first_line_end = first_line.map(|l| l.end()).unwrap_or_default();
let mut blank_lines_end = first_line_end;
while let Some(line) = lines.peek() {
@@ -185,11 +196,14 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri
},
docstring.range(),
);
// Delete the blank line after the docstring.
diagnostic.set_fix(Fix::safe_edit(Edit::deletion(
first_line_end,
blank_lines_end,
)));
// Do not offer fix if a \ would cause it to be a syntax error
if !first_line_line_continuation {
// Delete the blank line after the docstring.
diagnostic.set_fix(Fix::safe_edit(Edit::deletion(
first_line_end,
blank_lines_end,
)));
}
}
}
}

View File

@@ -82,3 +82,14 @@ D.py:568:5: D201 [*] No blank lines allowed before function docstring (found 1)
568 567 | """Trailing and leading space.
569 568 |
570 569 | More content.
D.py:729:5: D201 No blank lines allowed before function docstring (found 1)
|
727 | def line_continuation_chars():\
728 |
729 | """No fix should be offered for D201/D202 because of the line continuation chars."""\
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D201
730 |
731 | ...
|
= help: Remove blank line(s) before function docstring

View File

@@ -85,4 +85,15 @@ D.py:568:5: D202 [*] No blank lines allowed after function docstring (found 1)
572 |-
573 572 | pass
574 573 |
575 574 |
575 574 |
D.py:729:5: D202 No blank lines allowed after function docstring (found 1)
|
727 | def line_continuation_chars():\
728 |
729 | """No fix should be offered for D201/D202 because of the line continuation chars."""\
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D202
730 |
731 | ...
|
= help: Remove blank line(s) after function docstring

View File

@@ -428,3 +428,5 @@ D.py:723:1: D208 [*] Docstring is over-indented
723 |-     Returns:
723 |+ Returns:
724 724 | """
725 725 |
726 726 |

View File

@@ -24,24 +24,30 @@ const BIDI_UNICODE: [char; 10] = [
];
/// ## What it does
/// Checks for bidirectional unicode characters.
/// Checks for bidirectional formatting characters.
///
/// ## Why is this bad?
/// The interaction between bidirectional unicode characters and the
/// The interaction between bidirectional formatting characters and the
/// surrounding code can be surprising to those that are unfamiliar
/// with right-to-left writing systems.
///
/// In some cases, bidirectional unicode characters can also be used to
/// In some cases, bidirectional formatting characters can also be used to
/// obfuscate code and introduce or mask security vulnerabilities.
///
/// ## Example
/// ```python
/// s = "א" * 100 # " is assigned
/// print(s) # prints a 100-character string
/// example = "x" * 100 # "x" is assigned
/// ```
///
/// The example uses two `RIGHT-TO-LEFT MARK`s to make the `100 * ` appear inside the comment.
/// Without the `RIGHT-TO-LEFT MARK`s, the code looks like this:
///
/// ```py
/// example = "x" * 100 # "x" is assigned
/// ```
///
/// ## References
/// - [PEP 672: Bidirectional Text](https://peps.python.org/pep-0672/#bidirectional-text)
/// - [PEP 672: Bidirectional Marks, Embeddings, Overrides and Isolates](https://peps.python.org/pep-0672/#bidirectional-marks-embeddings-overrides-and-isolates)
#[derive(ViolationMetadata)]
pub(crate) struct BidirectionalUnicode;

View File

@@ -185,7 +185,9 @@ pub(crate) fn invalid_string_characters(context: &LintContext, token: &Token, lo
let text = match token.kind() {
// We can't use the `value` field since it's decoded and e.g. for f-strings removed a curly
// brace that escaped another curly brace, which would gives us wrong column information.
TokenKind::String | TokenKind::FStringMiddle => locator.slice(token),
TokenKind::String | TokenKind::FStringMiddle | TokenKind::TStringMiddle => {
locator.slice(token)
}
_ => return,
};

View File

@@ -75,3 +75,21 @@ no_self_use.py:140:9: PLR6301 Method `unused_message_2` could be a function, cla
141 | msg = ""
142 | raise NotImplementedError(x)
|
no_self_use.py:145:9: PLR6301 Method `developer_greeting` could be a function, class method, or static method
|
144 | class TPerson:
145 | def developer_greeting(self, name): # [no-self-use]
| ^^^^^^^^^^^^^^^^^^ PLR6301
146 | print(t"Greetings {name}!")
|
no_self_use.py:151:9: PLR6301 Method `tstring` could be a function, class method, or static method
|
149 | print(t"Hello from {self.name} !")
150 |
151 | def tstring(self, x):
| ^^^^^^^ PLR6301
152 | msg = t"{x}"
153 | raise NotImplementedError(msg)
|

View File

@@ -40,12 +40,18 @@ use super::{
///
/// ## Example
/// ```python
/// from typing import Annotated, TypeAlias, TypeAliasType
/// from annotated_types import Gt
///
/// ListOfInt: TypeAlias = list[int]
/// PositiveInt = TypeAliasType("PositiveInt", Annotated[int, Gt(0)])
/// ```
///
/// Use instead:
/// ```python
/// from typing import Annotated
/// from annotated_types import Gt
///
/// type ListOfInt = list[int]
/// type PositiveInt = Annotated[int, Gt(0)]
/// ```

View File

@@ -576,6 +576,8 @@ UP012.py:86:5: UP012 [*] Unnecessary call to `encode` as UTF-8
85 | # Refer: https://github.com/astral-sh/ruff/issues/11736
86 | x: '"foo".encode("utf-8")'
| ^^^^^^^^^^^^^^^^^^^^^ UP012
87 |
88 | # AttributeError for t-strings so skip lint
|
= help: Rewrite as bytes literal
@@ -585,3 +587,6 @@ UP012.py:86:5: UP012 [*] Unnecessary call to `encode` as UTF-8
85 85 | # Refer: https://github.com/astral-sh/ruff/issues/11736
86 |-x: '"foo".encode("utf-8")'
86 |+x: 'b"foo"'
87 87 |
88 88 | # AttributeError for t-strings so skip lint
89 89 | (t"foo{bar}").encode("utf-8")

View File

@@ -660,6 +660,7 @@ UP018.py:90:1: UP018 [*] Unnecessary `int` call (rewrite as a literal)
90 |+1 and None
91 91 | float(1.)and None
92 92 | bool(True)and()
93 93 |
UP018.py:91:1: UP018 [*] Unnecessary `float` call (rewrite as a literal)
|
@@ -678,6 +679,8 @@ UP018.py:91:1: UP018 [*] Unnecessary `float` call (rewrite as a literal)
91 |-float(1.)and None
91 |+1. and None
92 92 | bool(True)and()
93 93 |
94 94 |
UP018.py:92:1: UP018 [*] Unnecessary `bool` call (rewrite as a literal)
|
@@ -694,3 +697,6 @@ UP018.py:92:1: UP018 [*] Unnecessary `bool` call (rewrite as a literal)
91 91 | float(1.)and None
92 |-bool(True)and()
92 |+True and()
93 93 |
94 94 |
95 95 | # t-strings are not native literals

View File

@@ -19,22 +19,26 @@ use crate::rules::refurb::helpers::parenthesize_loop_iter_if_necessary;
///
/// ## Example
/// ```python
/// from pathlib import Path
///
/// with Path("file").open("w") as f:
/// for line in lines:
/// f.write(line)
///
/// with Path("file").open("wb") as f:
/// for line in lines:
/// f.write(line.encode())
/// with Path("file").open("wb") as f_b:
/// for line_b in lines_b:
/// f_b.write(line_b.encode())
/// ```
///
/// Use instead:
/// ```python
/// from pathlib import Path
///
/// with Path("file").open("w") as f:
/// f.writelines(lines)
///
/// with Path("file").open("wb") as f:
/// f.writelines(line.encode() for line in lines)
/// with Path("file").open("wb") as f_b:
/// f_b.writelines(line_b.encode() for line_b in lines_b)
/// ```
///
/// ## Fix safety

View File

@@ -14,11 +14,15 @@ use crate::{checkers::ast::Checker, importer::ImportRequest};
///
/// ## Example
/// ```python
/// from pathlib import Path
///
/// cwd = Path().resolve()
/// ```
///
/// Use instead:
/// ```python
/// from pathlib import Path
///
/// cwd = Path.cwd()
/// ```
///

View File

@@ -581,4 +581,22 @@ mod tests {
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::StarmapZip, Path::new("RUF058_2.py"))]
fn map_strict_py314(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"py314__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("ruff").join(path).as_path(),
&settings::LinterSettings {
unresolved_target_version: PythonVersion::PY314.into(),
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -1,4 +1,5 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::PythonVersion;
use ruff_python_ast::{Expr, ExprCall, parenthesize::parenthesized_range};
use ruff_python_parser::TokenKind;
use ruff_text_size::{Ranged, TextRange};
@@ -67,9 +68,24 @@ pub(crate) fn starmap_zip(checker: &Checker, call: &ExprCall) {
return;
};
if !iterable_call.arguments.keywords.is_empty() {
// TODO: Pass `strict=` to `map` too when 3.14 is supported.
return;
let keywords = &iterable_call.arguments.keywords;
match checker.target_version().cmp(&PythonVersion::PY314) {
// Keyword arguments not supported for `map` before Python 3.14
std::cmp::Ordering::Less => {
if !keywords.is_empty() {
return;
}
}
// Only supported keyword argument is `strict` starting in 3.14
std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {
if keywords.len() > 1 {
return;
}
if keywords.len() == 1 && iterable_call.arguments.find_keyword("strict").is_none() {
return;
}
}
}
let positionals = &iterable_call.arguments.args;

View File

@@ -0,0 +1,61 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---
RUF058_2.py:5:1: RUF058 [*] `itertools.starmap` called on `zip` iterable
|
4 | # Errors in Python 3.14+
5 | starmap(func, zip(a, b, c, strict=True))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF058
6 | starmap(func, zip(a, b, c, strict=False))
7 | starmap(func, zip(a, b, c, strict=strict))
|
= help: Use `map` instead
Safe fix
2 2 | import itertools
3 3 |
4 4 | # Errors in Python 3.14+
5 |-starmap(func, zip(a, b, c, strict=True))
5 |+map(func, a, b, c, strict=True)
6 6 | starmap(func, zip(a, b, c, strict=False))
7 7 | starmap(func, zip(a, b, c, strict=strict))
8 8 |
RUF058_2.py:6:1: RUF058 [*] `itertools.starmap` called on `zip` iterable
|
4 | # Errors in Python 3.14+
5 | starmap(func, zip(a, b, c, strict=True))
6 | starmap(func, zip(a, b, c, strict=False))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF058
7 | starmap(func, zip(a, b, c, strict=strict))
|
= help: Use `map` instead
Safe fix
3 3 |
4 4 | # Errors in Python 3.14+
5 5 | starmap(func, zip(a, b, c, strict=True))
6 |-starmap(func, zip(a, b, c, strict=False))
6 |+map(func, a, b, c, strict=False)
7 7 | starmap(func, zip(a, b, c, strict=strict))
8 8 |
9 9 |
RUF058_2.py:7:1: RUF058 [*] `itertools.starmap` called on `zip` iterable
|
5 | starmap(func, zip(a, b, c, strict=True))
6 | starmap(func, zip(a, b, c, strict=False))
7 | starmap(func, zip(a, b, c, strict=strict))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF058
|
= help: Use `map` instead
Safe fix
4 4 | # Errors in Python 3.14+
5 5 | starmap(func, zip(a, b, c, strict=True))
6 6 | starmap(func, zip(a, b, c, strict=False))
7 |-starmap(func, zip(a, b, c, strict=strict))
7 |+map(func, a, b, c, strict=strict)
8 8 |
9 9 |
10 10 | # No errors

View File

@@ -49,6 +49,16 @@ impl NodeIndex {
pub fn as_usize(self) -> usize {
self.0 as _
}
pub fn as_u32(self) -> u32 {
self.0
}
}
impl From<u32> for NodeIndex {
fn from(value: u32) -> Self {
NodeIndex(value)
}
}
impl From<u32> for AtomicNodeIndex {

View File

@@ -624,6 +624,12 @@ impl OneIndexed {
}
}
impl Default for OneIndexed {
fn default() -> Self {
Self::MIN
}
}
impl fmt::Display for OneIndexed {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
std::fmt::Debug::fmt(&self.0.get(), f)

View File

@@ -7,13 +7,25 @@ use std::{
fn main() {
// The workspace root directory is not available without walking up the tree
// https://github.com/rust-lang/cargo/issues/3946
let workspace_root = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("..")
let ruff_workspace_root = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("..")
.join("..");
let ty_workspace_root = ruff_workspace_root.join("..");
version_info(&workspace_root);
commit_info(&workspace_root);
version_info(&ty_workspace_root);
// If not in a git repository, do not attempt to retrieve commit information
let git_dir = ty_workspace_root.join(".git");
if git_dir.exists() {
commit_info(&git_dir, &ty_workspace_root, false);
} else {
// Try if we're inside the ruff repository and, if so, use that commit hash.
let git_dir = ruff_workspace_root.join(".git");
if git_dir.exists() {
commit_info(&git_dir, &ruff_workspace_root, true);
}
}
let target = std::env::var("TARGET").unwrap();
println!("cargo::rustc-env=RUST_HOST_TARGET={target}");
@@ -45,14 +57,8 @@ fn version_info(workspace_root: &Path) {
}
/// Retrieve commit information from the Git repository.
fn commit_info(workspace_root: &Path) {
// If not in a git repository, do not attempt to retrieve commit information
let git_dir = workspace_root.join(".git");
if !git_dir.exists() {
return;
}
if let Some(git_head_path) = git_head(&git_dir) {
fn commit_info(git_dir: &Path, workspace_root: &Path, is_ruff: bool) {
if let Some(git_head_path) = git_head(git_dir) {
println!("cargo:rerun-if-changed={}", git_head_path.display());
let git_head_contents = fs::read_to_string(git_head_path);
@@ -96,7 +102,10 @@ fn commit_info(workspace_root: &Path) {
let mut describe_parts = describe.split('-');
let last_tag = describe_parts.next().unwrap();
println!("cargo::rustc-env=TY_LAST_TAG={last_tag}");
println!(
"cargo::rustc-env=TY_LAST_TAG={ruff}{last_tag}",
ruff = if is_ruff { "ruff/" } else { "" }
);
// If this is the tagged commit, this component will be missing
println!(

2
crates/ty/docs/cli.md generated
View File

@@ -84,7 +84,7 @@ over all configuration files.</p>
<li><code>3.11</code></li>
<li><code>3.12</code></li>
<li><code>3.13</code></li>
</ul></dd><dt id="ty-check--quiet"><a href="#ty-check--quiet"><code>--quiet</code></a></dt><dd><p>Use quiet output</p>
</ul></dd><dt id="ty-check--quiet"><a href="#ty-check--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output (or <code>-qq</code> for silent output)</p>
</dd><dt id="ty-check--respect-ignore-files"><a href="#ty-check--respect-ignore-files"><code>--respect-ignore-files</code></a></dt><dd><p>Respect file exclusions via <code>.gitignore</code> and other standard ignore files. Use <code>--no-respect-gitignore</code> to disable</p>
</dd><dt id="ty-check--typeshed"><a href="#ty-check--typeshed"><code>--typeshed</code></a>, <code>--custom-typeshed-dir</code> <i>path</i></dt><dd><p>Custom directory to use for stdlib typeshed stubs</p>
</dd><dt id="ty-check--verbose"><a href="#ty-check--verbose"><code>--verbose</code></a>, <code>-v</code></dt><dd><p>Use verbose output (or <code>-vv</code> and <code>-vvv</code> for more verbose output)</p>

168
crates/ty/docs/rules.md generated
View File

@@ -36,7 +36,7 @@ def test(): -> "int":
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L98)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L99)
</small>
**What it does**
@@ -58,7 +58,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L142)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L143)
</small>
**What it does**
@@ -88,7 +88,7 @@ f(int) # error
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L168)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L169)
</small>
**What it does**
@@ -117,7 +117,7 @@ a = 1
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L193)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L194)
</small>
**What it does**
@@ -147,7 +147,7 @@ class C(A, B): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L219)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L220)
</small>
**What it does**
@@ -177,7 +177,7 @@ class B(A): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L263)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L264)
</small>
**What it does**
@@ -202,7 +202,7 @@ class B(A, A): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L284)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L285)
</small>
**What it does**
@@ -306,7 +306,7 @@ def test(): -> "Literal[5]":
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L426)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L427)
</small>
**What it does**
@@ -334,7 +334,7 @@ class C(A, B): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L450)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L451)
</small>
**What it does**
@@ -358,7 +358,7 @@ t[3] # IndexError: tuple index out of range
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L316)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L317)
</small>
**What it does**
@@ -445,7 +445,7 @@ an atypical memory layout.
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L470)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L471)
</small>
**What it does**
@@ -470,7 +470,7 @@ func("foo") # error: [invalid-argument-type]
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L510)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L511)
</small>
**What it does**
@@ -496,7 +496,7 @@ a: int = ''
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1514)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1515)
</small>
**What it does**
@@ -528,7 +528,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L532)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L533)
</small>
**What it does**
@@ -550,7 +550,7 @@ class A(42): ... # error: [invalid-base]
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L583)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L584)
</small>
**What it does**
@@ -575,7 +575,7 @@ with 1:
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L604)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L605)
</small>
**What it does**
@@ -602,7 +602,7 @@ a: str
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L627)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L628)
</small>
**What it does**
@@ -644,7 +644,7 @@ except ZeroDivisionError:
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L663)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L664)
</small>
**What it does**
@@ -675,7 +675,7 @@ class C[U](Generic[T]): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L689)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L690)
</small>
**What it does**
@@ -708,7 +708,7 @@ def f(t: TypeVar("U")): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L738)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L739)
</small>
**What it does**
@@ -740,7 +740,7 @@ class B(metaclass=f): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L766)
</small>
**What it does**
@@ -788,7 +788,7 @@ def foo(x: int) -> int: ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L808)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L809)
</small>
**What it does**
@@ -812,7 +812,7 @@ def f(a: int = ''): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L398)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L399)
</small>
**What it does**
@@ -844,7 +844,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L828)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L829)
</small>
Checks for `raise` statements that raise non-exceptions or use invalid
@@ -891,7 +891,7 @@ def g():
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L491)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L492)
</small>
**What it does**
@@ -914,7 +914,7 @@ def func() -> int:
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L871)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L872)
</small>
**What it does**
@@ -968,7 +968,7 @@ TODO #14889
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L717)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L718)
</small>
**What it does**
@@ -993,7 +993,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L910)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L911)
</small>
**What it does**
@@ -1021,7 +1021,7 @@ TYPE_CHECKING = ''
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L934)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L935)
</small>
**What it does**
@@ -1049,7 +1049,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L986)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L987)
</small>
**What it does**
@@ -1081,7 +1081,7 @@ f(10) # Error
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L958)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L959)
</small>
**What it does**
@@ -1113,7 +1113,7 @@ class C:
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1014)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1015)
</small>
**What it does**
@@ -1146,7 +1146,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1043)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1044)
</small>
**What it does**
@@ -1169,7 +1169,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1062)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1063)
</small>
**What it does**
@@ -1196,7 +1196,7 @@ func("string") # error: [no-matching-overload]
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1085)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1086)
</small>
**What it does**
@@ -1218,7 +1218,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1103)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1104)
</small>
**What it does**
@@ -1242,7 +1242,7 @@ for i in 34: # TypeError: 'int' object is not iterable
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1154)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1155)
</small>
**What it does**
@@ -1296,7 +1296,7 @@ def test(): -> "int":
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1490)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1491)
</small>
**What it does**
@@ -1324,7 +1324,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1245)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1246)
</small>
**What it does**
@@ -1351,7 +1351,7 @@ class B(A): ... # Error raised here
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1290)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1291)
</small>
**What it does**
@@ -1376,7 +1376,7 @@ f("foo") # Error raised here
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1268)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1269)
</small>
**What it does**
@@ -1402,7 +1402,7 @@ def _(x: int):
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1311)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312)
</small>
**What it does**
@@ -1446,7 +1446,7 @@ class A:
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1368)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1369)
</small>
**What it does**
@@ -1471,7 +1471,7 @@ f(x=1, y=2) # Error raised here
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1389)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1390)
</small>
**What it does**
@@ -1497,7 +1497,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1411)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1412)
</small>
**What it does**
@@ -1520,7 +1520,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1430)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1431)
</small>
**What it does**
@@ -1543,7 +1543,7 @@ print(x) # NameError: name 'x' is not defined
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1123)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1124)
</small>
**What it does**
@@ -1578,7 +1578,7 @@ b1 < b2 < b1 # exception raised here
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1449)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1450)
</small>
**What it does**
@@ -1604,7 +1604,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1471)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1472)
</small>
**What it does**
@@ -1655,7 +1655,7 @@ a = 20 / 0 # type: ignore
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1175)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1176)
</small>
**What it does**
@@ -1681,7 +1681,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L116)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L117)
</small>
**What it does**
@@ -1711,7 +1711,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1197)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1198)
</small>
**What it does**
@@ -1741,7 +1741,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1542)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1543)
</small>
**What it does**
@@ -1766,7 +1766,7 @@ cast(int, f()) # Redundant
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1350)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1351)
</small>
**What it does**
@@ -1812,12 +1812,66 @@ Use instead:
a = 20 / 0 # ty: ignore[division-by-zero]
```
## `unresolved-global`
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1564)
</small>
**What it does**
Detects variables declared as `global` in an inner scope that have no explicit
bindings or declarations in the global scope.
**Why is this bad?**
Function bodies with `global` statements can run in any order (or not at all), which makes
it hard for static analysis tools to infer the types of globals without
explicit definitions or declarations.
**Example**
```python
def f():
global x # unresolved global
x = 42
def g():
print(x) # unresolved reference
```
Use instead:
```python
x: int
def f():
global x
x = 42
def g():
print(x)
```
Or:
```python
x: int | None = None
def f():
global x
x = 42
def g():
print(x)
```
## `unsupported-base`
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L550)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L551)
</small>
**What it does**
@@ -1854,7 +1908,7 @@ class D(C): ... # error: [unsupported-base]
<small>
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L245)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L246)
</small>
**What it does**
@@ -1876,7 +1930,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
<small>
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1223)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1224)
</small>
**What it does**

View File

@@ -33,6 +33,7 @@ use ty_server::run_server;
pub fn run() -> anyhow::Result<ExitStatus> {
setup_rayon();
ruff_db::set_program_version(crate::version::version().to_string()).unwrap();
let args = wild::args_os();
let args = argfile::expand_args_from(args, argfile::parse_fromfile, argfile::PREFIX)
@@ -67,10 +68,12 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
let printer = Printer::default().with_verbosity(verbosity);
tracing::warn!(
"ty is pre-release software and not ready for production use. \
if std::env::var_os("I_KNOW_TY_IS_PRE_RELEASE").is_none() {
tracing::warn!(
"ty is pre-release software and not ready for production use. \
Expect to encounter bugs, missing features, and fatal errors.",
);
);
}
tracing::debug!("Version: {}", version::version());
@@ -120,17 +123,17 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
None => ProjectMetadata::discover(&project_path, &system)?,
};
let options = args.into_options();
project_metadata.apply_options(options.clone());
project_metadata.apply_configuration_files(&system)?;
let project_options_overrides = ProjectOptionsOverrides::new(config_file, args.into_options());
project_metadata.apply_overrides(&project_options_overrides);
let mut db = ProjectDatabase::new(project_metadata, system)?;
if !check_paths.is_empty() {
db.project().set_included_paths(&mut db, check_paths);
}
let project_options_overrides = ProjectOptionsOverrides::new(config_file, options);
let (main_loop, main_loop_cancellation_token) =
MainLoop::new(project_options_overrides, printer);

View File

@@ -30,7 +30,8 @@ pub(crate) struct Verbosity {
#[arg(
long,
help = "Use quiet output",
short,
help = "Use quiet output (or `-qq` for silent output)",
action = clap::ArgAction::Count,
global = true,
overrides_with = "verbose",
@@ -46,8 +47,8 @@ impl Verbosity {
// `--quiet` and `--verbose` are mutually exclusive in Clap, so we can just check one first.
match self.quiet {
0 => {}
_ => return VerbosityLevel::Quiet,
// TODO(zanieb): Add support for `-qq` with a "silent" mode
1 => return VerbosityLevel::Quiet,
_ => return VerbosityLevel::Silent,
}
match self.verbose {
@@ -61,6 +62,9 @@ impl Verbosity {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default)]
pub(crate) enum VerbosityLevel {
/// Silent output. Does not show any logging output or summary information.
Silent,
/// Quiet output. Only shows Ruff and ty events up to the [`ERROR`](tracing::Level::ERROR).
/// Silences output except for summary information.
Quiet,
@@ -84,6 +88,7 @@ pub(crate) enum VerbosityLevel {
impl VerbosityLevel {
const fn level_filter(self) -> LevelFilter {
match self {
VerbosityLevel::Silent => LevelFilter::OFF,
VerbosityLevel::Quiet => LevelFilter::ERROR,
VerbosityLevel::Default => LevelFilter::WARN,
VerbosityLevel::Verbose => LevelFilter::INFO,

View File

@@ -34,6 +34,7 @@ impl Printer {
}
match self.verbosity {
VerbosityLevel::Silent => ProgressDrawTarget::hidden(),
VerbosityLevel::Quiet => ProgressDrawTarget::hidden(),
VerbosityLevel::Default => ProgressDrawTarget::stderr(),
// Hide the progress bar when in verbose mode.
@@ -50,6 +51,7 @@ impl Printer {
/// [`VerbosityLevel::Quiet`] is used.
fn stdout_important(self) -> Stdout {
match self.verbosity {
VerbosityLevel::Silent => Stdout::disabled(),
VerbosityLevel::Quiet => Stdout::enabled(),
VerbosityLevel::Default => Stdout::enabled(),
VerbosityLevel::Verbose => Stdout::enabled(),
@@ -63,6 +65,7 @@ impl Printer {
/// The returned stream will be disabled when [`VerbosityLevel::Quiet`] is used.
fn stdout_general(self) -> Stdout {
match self.verbosity {
VerbosityLevel::Silent => Stdout::disabled(),
VerbosityLevel::Quiet => Stdout::disabled(),
VerbosityLevel::Default => Stdout::enabled(),
VerbosityLevel::Verbose => Stdout::enabled(),

View File

@@ -69,6 +69,25 @@ fn test_quiet_output() -> anyhow::Result<()> {
----- stderr -----
");
// We allow `-q`
assert_cmd_snapshot!(case.command().arg("-q"), @r"
success: false
exit_code: 1
----- stdout -----
Found 1 diagnostic
----- stderr -----
");
// And repeated `-qq`
assert_cmd_snapshot!(case.command().arg("-qq"), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
");
Ok(())
}

View File

@@ -803,12 +803,12 @@ fn overrides_no_actual_overrides() -> anyhow::Result<()> {
warning[useless-overrides-section]: Useless `overrides` section
--> pyproject.toml:5:1
|
3 | division-by-zero = "error"
3 | division-by-zero = "error"
4 |
5 | / [[tool.ty.overrides]]
6 | | include = ["*.py"] # Has patterns but no rule overrides
| |__________________^ This overrides section configures no rules
7 | # Missing [tool.ty.overrides.rules] section entirely
5 | [[tool.ty.overrides]]
| ^^^^^^^^^^^^^^^^^^^^^ This overrides section configures no rules
6 | include = ["*.py"] # Has patterns but no rule overrides
7 | # Missing [tool.ty.overrides.rules] section entirely
|
info: It has no `rules` table
info: Add a `[overrides.rules]` table...

View File

@@ -1303,6 +1303,139 @@ quux.b<CURSOR>
");
}
#[test]
fn metaclass1() {
let test = cursor_test(
"\
class Meta(type):
@property
def meta_attr(self) -> int:
return 0
class C(metaclass=Meta): ...
C.<CURSOR>
",
);
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
meta_attr :: int
mro :: bound method <class 'C'>.mro() -> list[type]
__annotations__ :: dict[str, Any]
__base__ :: type | None
__bases__ :: tuple[type, ...]
__basicsize__ :: int
__call__ :: bound method <class 'C'>.__call__(...) -> Any
__class__ :: <class 'Meta'>
__delattr__ :: def __delattr__(self, name: str, /) -> None
__dict__ :: MappingProxyType[str, Any]
__dictoffset__ :: int
__dir__ :: def __dir__(self) -> Iterable[str]
__doc__ :: str | None
__eq__ :: def __eq__(self, value: object, /) -> bool
__flags__ :: int
__format__ :: def __format__(self, format_spec: str, /) -> str
__getattribute__ :: def __getattribute__(self, name: str, /) -> Any
__getstate__ :: def __getstate__(self) -> object
__hash__ :: def __hash__(self) -> int
__init__ :: def __init__(self) -> None
__init_subclass__ :: def __init_subclass__(cls) -> None
__instancecheck__ :: bound method <class 'C'>.__instancecheck__(instance: Any, /) -> bool
__itemsize__ :: int
__module__ :: str
__mro__ :: tuple[<class 'C'>, <class 'object'>]
__name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool
__new__ :: def __new__(cls) -> Self
__or__ :: bound method <class 'C'>.__or__(value: Any, /) -> UnionType
__prepare__ :: bound method <class 'Meta'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
__qualname__ :: str
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
__reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
__repr__ :: def __repr__(self) -> str
__ror__ :: bound method <class 'C'>.__ror__(value: Any, /) -> UnionType
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
__sizeof__ :: def __sizeof__(self) -> int
__str__ :: def __str__(self) -> str
__subclasscheck__ :: bound method <class 'C'>.__subclasscheck__(subclass: type, /) -> bool
__subclasses__ :: bound method <class 'C'>.__subclasses__() -> list[Self]
__subclasshook__ :: bound method <class 'C'>.__subclasshook__(subclass: type, /) -> bool
__text_signature__ :: str | None
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
__weakrefoffset__ :: int
");
}
#[test]
fn metaclass2() {
let test = cursor_test(
"\
class Meta(type):
@property
def meta_attr(self) -> int:
return 0
class C(metaclass=Meta): ...
Meta.<CURSOR>
",
);
insta::with_settings!({
// The formatting of some types are different depending on
// whether we're in release mode or not. These differences
// aren't really relevant for completion tests AFAIK, so
// just redact them. ---AG
filters => [(r"(?m)\s*__(annotations|new)__.+$", "")]},
{
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
meta_attr :: property
mro :: def mro(self) -> list[type]
__base__ :: type | None
__bases__ :: tuple[type, ...]
__basicsize__ :: int
__call__ :: def __call__(self, *args: Any, **kwds: Any) -> Any
__class__ :: <class 'type'>
__delattr__ :: def __delattr__(self, name: str, /) -> None
__dict__ :: MappingProxyType[str, Any]
__dictoffset__ :: int
__dir__ :: def __dir__(self) -> Iterable[str]
__doc__ :: str | None
__eq__ :: def __eq__(self, value: object, /) -> bool
__flags__ :: int
__format__ :: def __format__(self, format_spec: str, /) -> str
__getattribute__ :: def __getattribute__(self, name: str, /) -> Any
__getstate__ :: def __getstate__(self) -> object
__hash__ :: def __hash__(self) -> int
__init__ :: Overload[(self, o: object, /) -> None, (self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None]
__init_subclass__ :: def __init_subclass__(cls) -> None
__instancecheck__ :: def __instancecheck__(self, instance: Any, /) -> bool
__itemsize__ :: int
__module__ :: str
__mro__ :: tuple[<class 'Meta'>, <class 'type'>, <class 'object'>]
__name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool
__or__ :: def __or__(self, value: Any, /) -> UnionType
__prepare__ :: bound method <class 'Meta'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
__qualname__ :: str
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
__reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
__repr__ :: def __repr__(self) -> str
__ror__ :: def __ror__(self, value: Any, /) -> UnionType
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
__sizeof__ :: def __sizeof__(self) -> int
__str__ :: def __str__(self) -> str
__subclasscheck__ :: def __subclasscheck__(self, subclass: type, /) -> bool
__subclasses__ :: def __subclasses__(self: Self) -> list[Self]
__subclasshook__ :: bound method <class 'Meta'>.__subclasshook__(subclass: type, /) -> bool
__text_signature__ :: str | None
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
__weakrefoffset__ :: int
");
}
);
}
#[test]
fn class_init3() {
let test = cursor_test(
@@ -1358,7 +1491,7 @@ Quux.<CURSOR>
);
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
mro :: def mro(self) -> list[type]
mro :: bound method <class 'Quux'>.mro() -> list[type]
some_attribute :: int
some_class_method :: bound method <class 'Quux'>.some_class_method() -> int
some_method :: def some_method(self) -> int
@@ -1368,7 +1501,7 @@ Quux.<CURSOR>
__base__ :: type | None
__bases__ :: tuple[type, ...]
__basicsize__ :: int
__call__ :: def __call__(self, *args: Any, **kwds: Any) -> Any
__call__ :: bound method <class 'Quux'>.__call__(...) -> Any
__class__ :: <class 'type'>
__delattr__ :: def __delattr__(self, name: str, /) -> None
__dict__ :: MappingProxyType[str, Any]
@@ -1383,32 +1516,122 @@ Quux.<CURSOR>
__hash__ :: def __hash__(self) -> int
__init__ :: def __init__(self) -> Unknown
__init_subclass__ :: def __init_subclass__(cls) -> None
__instancecheck__ :: def __instancecheck__(self, instance: Any, /) -> bool
__instancecheck__ :: bound method <class 'Quux'>.__instancecheck__(instance: Any, /) -> bool
__itemsize__ :: int
__module__ :: str
__mro__ :: tuple[<class 'type'>, <class 'object'>]
__mro__ :: tuple[<class 'Quux'>, <class 'object'>]
__name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool
__new__ :: def __new__(cls) -> Self
__or__ :: def __or__(self, value: Any, /) -> UnionType
__or__ :: bound method <class 'Quux'>.__or__(value: Any, /) -> UnionType
__prepare__ :: bound method <class 'type'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
__qualname__ :: str
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
__reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
__repr__ :: def __repr__(self) -> str
__ror__ :: def __ror__(self, value: Any, /) -> UnionType
__ror__ :: bound method <class 'Quux'>.__ror__(value: Any, /) -> UnionType
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
__sizeof__ :: def __sizeof__(self) -> int
__str__ :: def __str__(self) -> str
__subclasscheck__ :: def __subclasscheck__(self, subclass: type, /) -> bool
__subclasses__ :: def __subclasses__(self: Self) -> list[Self]
__subclasshook__ :: bound method <class 'object'>.__subclasshook__(subclass: type, /) -> bool
__subclasscheck__ :: bound method <class 'Quux'>.__subclasscheck__(subclass: type, /) -> bool
__subclasses__ :: bound method <class 'Quux'>.__subclasses__() -> list[Self]
__subclasshook__ :: bound method <class 'Quux'>.__subclasshook__(subclass: type, /) -> bool
__text_signature__ :: str | None
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
__weakrefoffset__ :: int
");
}
#[test]
fn enum_attributes() {
let test = cursor_test(
"\
from enum import Enum
class Answer(Enum):
NO = 0
YES = 1
Answer.<CURSOR>
",
);
insta::with_settings!({
// See above: filter out some members which contain @Todo types that are
// rendered differently in release mode.
filters => [(r"(?m)\s*__(call|reduce_ex)__.+$", "")]},
{
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
NO :: Literal[Answer.NO]
YES :: Literal[Answer.YES]
mro :: bound method <class 'Answer'>.mro() -> list[type]
name :: Any
value :: Any
__annotations__ :: dict[str, Any]
__base__ :: type | None
__bases__ :: tuple[type, ...]
__basicsize__ :: int
__bool__ :: bound method <class 'Answer'>.__bool__() -> Literal[True]
__class__ :: <class 'EnumMeta'>
__contains__ :: bound method <class 'Answer'>.__contains__(value: object) -> bool
__copy__ :: def __copy__(self) -> Self
__deepcopy__ :: def __deepcopy__(self, memo: Any) -> Self
__delattr__ :: def __delattr__(self, name: str, /) -> None
__dict__ :: MappingProxyType[str, Any]
__dictoffset__ :: int
__dir__ :: def __dir__(self) -> list[str]
__doc__ :: str | None
__eq__ :: def __eq__(self, value: object, /) -> bool
__flags__ :: int
__format__ :: def __format__(self, format_spec: str) -> str
__getattribute__ :: def __getattribute__(self, name: str, /) -> Any
__getitem__ :: bound method <class 'Answer'>.__getitem__(name: str) -> _EnumMemberT
__getstate__ :: def __getstate__(self) -> object
__hash__ :: def __hash__(self) -> int
__init__ :: def __init__(self) -> None
__init_subclass__ :: def __init_subclass__(cls) -> None
__instancecheck__ :: bound method <class 'Answer'>.__instancecheck__(instance: Any, /) -> bool
__itemsize__ :: int
__iter__ :: bound method <class 'Answer'>.__iter__() -> Iterator[_EnumMemberT]
__len__ :: bound method <class 'Answer'>.__len__() -> int
__members__ :: MappingProxyType[str, Unknown]
__module__ :: str
__mro__ :: tuple[<class 'Answer'>, <class 'Enum'>, <class 'object'>]
__name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool
__new__ :: def __new__(cls, value: object) -> Self
__or__ :: bound method <class 'Answer'>.__or__(value: Any, /) -> UnionType
__order__ :: str
__prepare__ :: bound method <class 'EnumMeta'>.__prepare__(cls: str, bases: tuple[type, ...], **kwds: Any) -> _EnumDict
__qualname__ :: str
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
__repr__ :: def __repr__(self) -> str
__reversed__ :: bound method <class 'Answer'>.__reversed__() -> Iterator[_EnumMemberT]
__ror__ :: bound method <class 'Answer'>.__ror__(value: Any, /) -> UnionType
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
__signature__ :: bound method <class 'Answer'>.__signature__() -> str
__sizeof__ :: def __sizeof__(self) -> int
__str__ :: def __str__(self) -> str
__subclasscheck__ :: bound method <class 'Answer'>.__subclasscheck__(subclass: type, /) -> bool
__subclasses__ :: bound method <class 'Answer'>.__subclasses__() -> list[Self]
__subclasshook__ :: bound method <class 'Answer'>.__subclasshook__(subclass: type, /) -> bool
__text_signature__ :: str | None
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
__weakrefoffset__ :: int
_generate_next_value_ :: def _generate_next_value_(name: str, start: int, count: int, last_values: list[Any]) -> Any
_ignore_ :: str | list[str]
_member_map_ :: dict[str, Enum]
_member_names_ :: list[str]
_missing_ :: bound method <class 'Answer'>._missing_(value: object) -> Any
_name_ :: str
_order_ :: str
_value2member_map_ :: dict[Any, Enum]
_value_ :: Any
");
}
);
}
// We don't yet take function parameters into account.
#[test]
fn call_prefix1() {

View File

@@ -423,14 +423,14 @@ mod tests {
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:461:7
--> stdlib/builtins.pyi:892:7
|
459 | def __getitem__(self, key: int, /) -> str | int | None: ...
460 |
461 | class str(Sequence[str]):
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
| ^^^
462 | @overload
463 | def __new__(cls, object: object = ...) -> Self: ...
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:4:13
@@ -452,14 +452,14 @@ mod tests {
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:461:7
--> stdlib/builtins.pyi:892:7
|
459 | def __getitem__(self, key: int, /) -> str | int | None: ...
460 |
461 | class str(Sequence[str]):
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
| ^^^
462 | @overload
463 | def __new__(cls, object: object = ...) -> Self: ...
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:2:22
@@ -568,14 +568,14 @@ mod tests {
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:461:7
--> stdlib/builtins.pyi:892:7
|
459 | def __getitem__(self, key: int, /) -> str | int | None: ...
460 |
461 | class str(Sequence[str]):
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
| ^^^
462 | @overload
463 | def __new__(cls, object: object = ...) -> Self: ...
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:4:18
@@ -601,16 +601,16 @@ mod tests {
// TODO: This should jump to `str` and not `int` because
// the keyword is typed as a string. It's only the passed argument that
// is an int. Navigating to `str` would match pyright's behavior.
assert_snapshot!(test.goto_type_definition(), @r"
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:244:7
--> stdlib/builtins.pyi:338:7
|
242 | _LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0] # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed
243 |
244 | class int:
336 | _LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0] # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed
337 |
338 | class int:
| ^^^
245 | @overload
246 | def __new__(cls, x: ConvertibleToInt = ..., /) -> Self: ...
339 | """int([x]) -> integer
340 | int(x, base=10) -> integer
|
info: Source
--> main.py:4:18
@@ -620,7 +620,7 @@ mod tests {
4 | test(a= 123)
| ^
|
");
"#);
}
#[test]
@@ -637,14 +637,14 @@ f(**kwargs<CURSOR>)
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:1136:7
--> stdlib/builtins.pyi:2892:7
|
1134 | def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
1135 |
1136 | class dict(MutableMapping[_KT, _VT]):
2890 | """See PEP 585"""
2891 |
2892 | class dict(MutableMapping[_KT, _VT]):
| ^^^^
1137 | # __init__ should be kept roughly in line with `collections.UserDict.__init__`, which has similar semantics
1138 | # Also multiprocessing.managers.SyncManager.dict()
2893 | """dict() -> new empty dictionary
2894 | dict(mapping) -> new dictionary initialized from a mapping object's
|
info: Source
--> main.py:6:5
@@ -666,16 +666,16 @@ f(**kwargs<CURSOR>)
"#,
);
assert_snapshot!(test.goto_type_definition(), @r"
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:461:7
--> stdlib/builtins.pyi:892:7
|
459 | def __getitem__(self, key: int, /) -> str | int | None: ...
460 |
461 | class str(Sequence[str]):
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
| ^^^
462 | @overload
463 | def __new__(cls, object: object = ...) -> Self: ...
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:3:17
@@ -684,7 +684,7 @@ f(**kwargs<CURSOR>)
3 | a
| ^
|
");
"#);
}
#[test]
@@ -759,16 +759,16 @@ f(**kwargs<CURSOR>)
"#,
);
assert_snapshot!(test.goto_type_definition(), @r"
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:461:7
--> stdlib/builtins.pyi:892:7
|
459 | def __getitem__(self, key: int, /) -> str | int | None: ...
460 |
461 | class str(Sequence[str]):
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
| ^^^
462 | @overload
463 | def __new__(cls, object: object = ...) -> Self: ...
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:4:27
@@ -778,7 +778,7 @@ f(**kwargs<CURSOR>)
4 | print(a)
| ^
|
");
"#);
}
#[test]
@@ -790,15 +790,15 @@ f(**kwargs<CURSOR>)
"#,
);
assert_snapshot!(test.goto_type_definition(), @r"
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/types.pyi:691:11
--> stdlib/types.pyi:922:11
|
689 | if sys.version_info >= (3, 10):
690 | @final
691 | class NoneType:
920 | if sys.version_info >= (3, 10):
921 | @final
922 | class NoneType:
| ^^^^^^^^
692 | def __bool__(self) -> Literal[False]: ...
923 | """The type of the None singleton."""
|
info: Source
--> main.py:3:17
@@ -809,14 +809,14 @@ f(**kwargs<CURSOR>)
|
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:461:7
--> stdlib/builtins.pyi:892:7
|
459 | def __getitem__(self, key: int, /) -> str | int | None: ...
460 |
461 | class str(Sequence[str]):
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
| ^^^
462 | @overload
463 | def __new__(cls, object: object = ...) -> Self: ...
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:3:17
@@ -825,7 +825,7 @@ f(**kwargs<CURSOR>)
3 | a
| ^
|
");
"#);
}
impl CursorTest {

View File

@@ -3,6 +3,7 @@ use std::panic::{AssertUnwindSafe, RefUnwindSafe};
use std::sync::Arc;
use std::{cmp, fmt};
pub use self::changes::ChangeResult;
use crate::metadata::settings::file_settings;
use crate::{DEFAULT_LINT_REGISTRY, DummyReporter};
use crate::{ProgressReporter, Project, ProjectMetadata};

View File

@@ -41,8 +41,6 @@ impl ProjectDatabase {
let project_root = project.root(self).to_path_buf();
let config_file_override =
project_options_overrides.and_then(|options| options.config_file_override.clone());
let options =
project_options_overrides.map(|project_options| project_options.options.clone());
let program = Program::get(self);
let custom_stdlib_versions_path = program
.custom_stdlib_search_path(self)
@@ -218,16 +216,16 @@ impl ProjectDatabase {
};
match new_project_metadata {
Ok(mut metadata) => {
if let Some(cli_options) = options {
metadata.apply_options(cli_options);
}
if let Err(error) = metadata.apply_configuration_files(self.system()) {
tracing::error!(
"Failed to apply configuration files, continuing without applying them: {error}"
);
}
if let Some(overrides) = project_options_overrides {
metadata.apply_overrides(overrides);
}
match metadata.to_program_settings(self.system(), self.vendored()) {
Ok(program_settings) => {
let program = Program::get(self);

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