Compare commits

...

30 Commits

Author SHA1 Message Date
Alex Waygood
80f045a261 more 2025-11-02 01:01:28 -04:00
Alex Waygood
349e24db9a [ty] Less eager simplification of intersections that include constrained type variables 2025-11-02 00:41:42 -04:00
Alex Waygood
bff32a41dc [ty] Increase timeout-minutes to 10 for py-fuzzer job (#21196) 2025-11-01 22:06:03 -04:00
Micha Reiser
17c7b3cde1 Bump MSRV to Rust 1.89 (#21180) 2025-11-01 02:26:38 +00:00
Micha Reiser
921f409ee8 Update Rust toolchain to 1.91 (#21179) 2025-11-01 01:50:58 +00:00
github-actions[bot]
a151f9746d [ty] Sync vendored typeshed stubs (#21178)
Close and reopen this PR to trigger CI

---------

Co-authored-by: typeshedbot <>
2025-10-31 21:03:40 -04:00
Gautham Venkataraman
521217bb90 [ruff]: Make ruff analyze graph work with jupyter notebooks (#21161)
Co-authored-by: Gautham Venkataraman <gautham@dexterenergy.ai>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-10-31 21:47:01 +00:00
Alex Waygood
a32d5b8dc4 [ty] Improve exhaustiveness analysis for type variables with bounds or constraints (#21172) 2025-10-31 16:51:11 -04:00
Micha Reiser
6337e22f0c [ty] Smaller refactors to server API in prep for notebook support (#21095) 2025-10-31 20:00:04 +00:00
Brent Westbrook
827d8ae5d4 Allow newlines after function headers without docstrings (#21110)
Summary
--

This is a first step toward fixing #9745. After reviewing our open
issues and several Black issues and PRs, I personally found the function
case the most compelling, especially with very long argument lists:

```py
def func(
	self,
	arg1: int,
	arg2: bool,
	arg3: bool,
	arg4: float,
	arg5: bool,
) -> tuple[...]:
	if arg2 and arg3:
		raise ValueError
```

or many annotations:

```py
def function(
    self, data: torch.Tensor | tuple[torch.Tensor, ...], other_argument: int
) -> torch.Tensor | tuple[torch.Tensor, ...]:
    do_something(data)
    return something
```

I think docstrings help the situation substantially both because syntax
highlighting will usually give a very clear separation between the
annotations and the docstring and because we already allow a blank line
_after_ the docstring:

```py
def function(
    self, data: torch.Tensor | tuple[torch.Tensor, ...], other_argument: int
) -> torch.Tensor | tuple[torch.Tensor, ...]:
    """
	A function doing something.

	And a longer description of the things it does.
	"""

    do_something(data)
    return something
```

There are still other comments on #9745, such as [this one] with 9
upvotes, where users specifically request blank lines in all block
types, or at least including conditionals and loops. I'm sympathetic to
that case as well, even if personally I don't find an [example] like
this:

```py
if blah:

    # Do some stuff that is logically related
    data = get_data()

    # Do some different stuff that is logically related
    results = calculate_results()

    return results
```

to be much more readable than:

```py
if blah:
    # Do some stuff that is logically related
    data = get_data()

    # Do some different stuff that is logically related
    results = calculate_results()

    return results
```

I'm probably just used to the latter from the formatters I've used, but
I do prefer it. I also think that functions are the least susceptible to
the accidental introduction of a newline after refactoring described in
Micha's [comment] on #8893.

I actually considered further restricting this change to functions with
multiline headers. I don't think very short functions like:

```py
def foo():

    return 1
```

benefit nearly as much from the allowed newline, but I just went with
any function without a docstring for now. I guess a marginal case like:

```py
def foo(a_long_parameter: ALongType, b_long_parameter: BLongType) -> CLongType:

    return 1
```

might be a good argument for not restricting it.

I caused a couple of syntax errors before adding special handling for
the ellipsis-only case, so I suspect that there are some other
interesting edge cases that may need to be handled better.

Test Plan
--

Existing tests, plus a few simple new ones. As noted above, I suspect
that we may need a few more for edge cases I haven't considered.

[this one]:
https://github.com/astral-sh/ruff/issues/9745#issuecomment-2876771400
[example]:
https://github.com/psf/black/issues/902#issuecomment-1562154809
[comment]:
https://github.com/astral-sh/ruff/issues/8893#issuecomment-1867259744
2025-10-31 14:53:40 -04:00
David Peter
1734ddfb3e [ty] Do not promote literals in contravariant positions of generic specializations (#21171)
## Summary

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

supersedes https://github.com/astral-sh/ruff/pull/20950 by @ibraheemdev 

## Test Plan

New regression test
2025-10-31 17:48:34 +01:00
Ibraheem Ahmed
ff3a6a8fbd [ty] Support type context of union attribute assignments (#21170)
## Summary

Turns out this is easy to implement. Resolves
https://github.com/astral-sh/ty/issues/1375.
2025-10-31 12:41:14 -04:00
Carl Meyer
9664474c51 [ty] rollback preferring declared type on invalid TypedDict creation (#21169)
## Summary

Discussion with @ibraheemdev clarified that
https://github.com/astral-sh/ruff/pull/21168 was incorrect. In a case of
failed inference of a dict literal as a `TypedDict`, we should store the
context-less inferred type of the dict literal as the type of the dict
literal expression itself; the fallback to declared type should happen
at the level of the overall assignment definition.

The reason the latter isn't working yet is because currently we
(wrongly) consider a homogeneous dict type as assignable to a
`TypedDict`, so we don't actually consider the assignment itself as
failed. So the "bug" I observed (and tried to fix) will naturally be
fixed by implementing TypedDict assignability rules.

Rollback https://github.com/astral-sh/ruff/pull/21168 except for the
tests, and modify the tests to include TODOs as needed.

## Test Plan

Updated mdtests.
2025-10-31 12:06:47 -04:00
Luca Chiodini
69b4c29924 Consistently wrap tokens in parser diagnostics in backticks instead of 'quotes' (#21163)
The parser currently uses single quotes to wrap tokens. This is
inconsistent with the rest of ruff/ty, which use backticks.

For example, see the inconsistent diagnostics produced in this simple
example: https://play.ty.dev/0a9d6eab-6599-4a1d-8e40-032091f7f50f

Consistently wrapping tokens in backticks produces uniform diagnostics.
Following the style decision of #723, in #2889 some quotes were already
switched into backticks.

This is also in line with Rust's guide on diagnostics
(https://rustc-dev-guide.rust-lang.org/diagnostics.html#diagnostic-structure):

> When code or an identifier must appear in a message or label, it
should be surrounded with backticks
2025-10-31 11:59:11 -04:00
Ibraheem Ahmed
bb40c34361 [ty] Use declared attribute types as type context (#21143)
## Summary

For example:
```py
class X:
    x: list[int | str]

def _(x: X):
    x.x = [1]
```

Resolves https://github.com/astral-sh/ty/issues/1375.
2025-10-31 15:48:28 +00:00
chiri
b93d8f2b9f [refurb] Preserve argument ordering in autofix (FURB103) (#20790)
Fixes https://github.com/astral-sh/ruff/issues/20785
2025-10-31 11:16:09 -04:00
Carl Meyer
1d111c8780 [ty] prefer declared type on invalid TypedDict creation (#21168)
## Summary

In general, when we have an invalid assignment (inferred assigned type
is not assignable to declared type), we fall back to inferring the
declared type, since the declared type is a more explicit declaration of
the programmer's intent. This also maintains the invariant that our
inferred type for a name is always assignable to the declared type for
that same name. For example:

```py
x: str = 1
reveal_type(x)  # revealed: str
```

We weren't following this pattern for dictionary literals inferred (via
type context) as a typed dictionary; if the literal was not valid for
the annotated TypedDict type, we would just fall back to the normal
inferred type of the dict literal, effectively ignoring the annotation,
and resulting in inferred type not assignable to declared type.

## Test Plan

Added mdtest assertions.
2025-10-31 11:12:06 -04:00
chiri
9d7da914b9 Improve extend docs (#21135)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-10-31 15:10:14 +00:00
David Peter
0c2cf75869 [ty] Do not promote literals in contravariant position (#21164)
## Summary

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

## Test Plan

Regression tests
2025-10-31 16:00:30 +01:00
Ibraheem Ahmed
1d6ae8596a [ty] Prefer exact matches when solving constrained type variables (#21165)
## Summary

The solver is currently order-dependent, and will choose a supertype
over the exact type if it appears earlier in the list of constraints. We
could be smarter and try to choose the most precise subtype, but I
imagine this is something the new constraint solver will fix anyways,
and this fixes the issue showing up on
https://github.com/astral-sh/ruff/pull/21070.
2025-10-31 10:58:09 -04:00
Douglas Creager
cf4e82d4b0 [ty] Add and test when constraint sets are satisfied by their typevars (#21129)
This PR adds a new `satisfied_by_all_typevar` method, which implements
one of the final steps of actually using these dang constraint sets.
Constraint sets exist to help us check assignability and subtyping of
types in the presence of typevars. We construct a constraint set
describing the conditions under which assignability holds between the
two types. Then we check whether that constraint set is satisfied for
the valid specializations of the relevant typevars (which is this new
method).

We also add a new `ty_extensions.ConstraintSet` method so that we can
test this method's behavior in mdtests, before hooking it up to the rest
of the specialization inference machinery.
2025-10-31 10:53:37 -04:00
Ibraheem Ahmed
1baf98aab3 [ty] Fix is_disjoint_from with @final classes (#21167)
## Summary

We currently perform a subtyping check instead of the intended subclass
check (and the subtyping check is confusingly named `is_subclass_of`).
This showed up in https://github.com/astral-sh/ruff/pull/21070.
2025-10-31 14:50:54 +00:00
Carl Meyer
3179b05221 [ty] don't assume in diagnostic messages that a TypedDict key error is about subscript access (#21166)
## Summary

Before this PR, we would emit diagnostics like "Invalid key access" for
a TypedDict literal with invalid key, which doesn't make sense since
there's no "access" in that case. This PR just adjusts the wording to be
more general, and adjusts the documentation of the lint rule too.

I noticed this in the playground and thought it would be a quick fix. As
usual, it turned out to be a bit more subtle than I expected, but for
now I chose to punt on the complexity. We may ultimately want to have
different rules for invalid subscript vs invalid TypedDict literal,
because an invalid key in a TypedDict literal is low severity: it's a
typo detector, but not actually a type error. But then there's another
wrinkle there: if the TypedDict is `closed=True`, then it _is_ a type
error. So would we want to separate the open and closed cases into
separate rules, too? I decided to leave this as a question for future.

If we wanted to use separate rules, or use specific wording for each
case instead of the generalized wording I chose here, that would also
involve a bit of extra work to distinguish the cases, since we use a
generic set of functions for reporting these errors.

## Test Plan

Added and updated mdtests.
2025-10-31 10:49:59 -04:00
Aria Desires
172e8d4ae0 [ty] Support implicit imports of submodules in __init__.pyi (#20855)
This is a second take at the implicit imports approach, allowing `from .
import submodule` in an `__init__.pyi` to create the
`mypackage.submodule` attribute everyhere.

This implementation operates inside of the
available_submodule_attributes subsystem instead of as a re-export rule.

The upside of this is we are no longer purely syntactic, and absolute
from imports that happen to target submodules work (an intentional
discussed deviation from pyright which demands a relative from import).
Also we don't re-export functions or classes.

The downside(?) of this is star imports no longer see these attributes
(this may be either good or bad. I believe it's not a huge lift to make
it work with star imports but it's some non-trivial reworking).

I've also intentionally made `import mypackage.submodule` not trigger
this rule although it's trivial to change that.

I've tried to cover as many relevant cases as possible for discussion in
the new test file I've added (there are some random overlaps with
existing tests but trying to add them piecemeal felt confusing and
weird, so I just made a dedicated file for this extension to the rules).

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

<!--
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? -->

## Test Plan

<!-- How was it tested? -->
2025-10-31 14:29:24 +00:00
Mahmoud Saada
735ec0c1f9 [ty] Fix generic inference for non-dataclass inheriting from generic dataclass (#21159)
## Summary

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

This PR fixes a regression introduced in alpha.24 where non-dataclass
children of generic dataclasses lost generic type parameter information
during `__init__` synthesis.

The issue occurred because when looking up inherited members in the MRO,
the child class's `inherited_generic_context` was correctly passed down,
but `own_synthesized_member()` (which synthesizes dataclass `__init__`
methods) didn't accept this parameter. It only used
`self.inherited_generic_context(db)`, which returned the parent's
context instead of the child's.

The fix threads the child's generic context through to the synthesis
logic, allowing proper generic type inference for inherited dataclass
constructors.

## Test Plan

- Added regression test for non-dataclass inheriting from generic
dataclass
- Verified the exact repro case from the issue now works
- All 277 mdtest tests passing
- Clippy clean
- Manually verified with Python runtime, mypy, and pyright - all accept
this code pattern

## Verification

Tested against multiple type checkers:
-  Python runtime: Code works correctly
-  mypy: No issues found
-  pyright: 0 errors, 0 warnings
-  ty alpha.23: Worked (before regression)
-  ty alpha.24: Regression
-  ty with this fix: Works correctly

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: David Peter <mail@david-peter.de>
2025-10-31 13:55:17 +01:00
Ben Beasley
3585c96ea5 Update etcetera to 0.11.0 (#21160) 2025-10-31 12:53:18 +00:00
Micha Reiser
4b026c2a55 Fix missing diagnostics for notebooks (#21156) 2025-10-31 01:16:43 +00:00
Matthew Mckee
4b758b3746 [ty] Fix tests for definition completions (#21153) 2025-10-31 00:43:50 +00:00
Amethyst Reese
8737a2d5f5 Bump v0.14.3 (#21152)
- **Upgrade to rooster==0.1.1**
- **Changelog for v0.14.3**
- **Bump v0.14.3**
2025-10-30 17:06:29 -07:00
Matthew Mckee
3be3a10a2f [ty] Don't provide completions when in class or function definition (#21146) 2025-10-30 23:19:59 +00:00
233 changed files with 4252 additions and 1564 deletions

View File

@@ -277,8 +277,8 @@ jobs:
run: cargo test -p ty_python_semantic --test mdtest || true
- name: "Run tests"
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
# Dogfood ty on py-fuzzer
- run: uv run --project=./python/py-fuzzer cargo run -p ty check --project=./python/py-fuzzer
- name: Dogfood ty on py-fuzzer
run: uv run --project=./python/py-fuzzer cargo run -p ty check --project=./python/py-fuzzer
# Check for broken links in the documentation.
- run: cargo doc --all --no-deps
env:
@@ -649,7 +649,7 @@ jobs:
- determine_changes
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && (needs.determine_changes.outputs.ty == 'true' || needs.determine_changes.outputs.py-fuzzer == 'true') }}
timeout-minutes: ${{ github.repository == 'astral-sh/ruff' && 5 || 20 }}
timeout-minutes: ${{ github.repository == 'astral-sh/ruff' && 10 || 20 }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:

View File

@@ -1,5 +1,60 @@
# Changelog
## 0.14.3
Released on 2025-10-30.
### Preview features
- Respect `--output-format` with `--watch` ([#21097](https://github.com/astral-sh/ruff/pull/21097))
- \[`pydoclint`\] Fix false positive on explicit exception re-raising (`DOC501`, `DOC502`) ([#21011](https://github.com/astral-sh/ruff/pull/21011))
- \[`pyflakes`\] Revert to stable behavior if imports for module lie in alternate branches for `F401` ([#20878](https://github.com/astral-sh/ruff/pull/20878))
- \[`pylint`\] Implement `stop-iteration-return` (`PLR1708`) ([#20733](https://github.com/astral-sh/ruff/pull/20733))
- \[`ruff`\] Add support for additional eager conversion patterns (`RUF065`) ([#20657](https://github.com/astral-sh/ruff/pull/20657))
### Bug fixes
- Fix finding keyword range for clause header after statement ending with semicolon ([#21067](https://github.com/astral-sh/ruff/pull/21067))
- Fix syntax error false positive on nested alternative patterns ([#21104](https://github.com/astral-sh/ruff/pull/21104))
- \[`ISC001`\] Fix panic when string literals are unclosed ([#21034](https://github.com/astral-sh/ruff/pull/21034))
- \[`flake8-django`\] Apply `DJ001` to annotated fields ([#20907](https://github.com/astral-sh/ruff/pull/20907))
- \[`flake8-pyi`\] Fix `PYI034` to not trigger on metaclasses (`PYI034`) ([#20881](https://github.com/astral-sh/ruff/pull/20881))
- \[`flake8-type-checking`\] Fix `TC003` false positive with `future-annotations` ([#21125](https://github.com/astral-sh/ruff/pull/21125))
- \[`pyflakes`\] Fix false positive for `__class__` in lambda expressions within class definitions (`F821`) ([#20564](https://github.com/astral-sh/ruff/pull/20564))
- \[`pyupgrade`\] Fix false positive for `TypeVar` with default on Python \<3.13 (`UP046`,`UP047`) ([#21045](https://github.com/astral-sh/ruff/pull/21045))
### Rule changes
- Add missing docstring sections to the numpy list ([#20931](https://github.com/astral-sh/ruff/pull/20931))
- \[`airflow`\] Extend `airflow.models..Param` check (`AIR311`) ([#21043](https://github.com/astral-sh/ruff/pull/21043))
- \[`airflow`\] Warn that `airflow....DAG.create_dagrun` has been removed (`AIR301`) ([#21093](https://github.com/astral-sh/ruff/pull/21093))
- \[`refurb`\] Preserve digit separators in `Decimal` constructor (`FURB157`) ([#20588](https://github.com/astral-sh/ruff/pull/20588))
### Server
- Avoid sending an unnecessary "clear diagnostics" message for clients supporting pull diagnostics ([#21105](https://github.com/astral-sh/ruff/pull/21105))
### Documentation
- \[`flake8-bandit`\] Fix correct example for `S308` ([#21128](https://github.com/astral-sh/ruff/pull/21128))
### Other changes
- Clearer error message when `line-length` goes beyond threshold ([#21072](https://github.com/astral-sh/ruff/pull/21072))
### Contributors
- [@danparizher](https://github.com/danparizher)
- [@jvacek](https://github.com/jvacek)
- [@ntBre](https://github.com/ntBre)
- [@augustelalande](https://github.com/augustelalande)
- [@prakhar1144](https://github.com/prakhar1144)
- [@TaKO8Ki](https://github.com/TaKO8Ki)
- [@dylwil3](https://github.com/dylwil3)
- [@fatelei](https://github.com/fatelei)
- [@ShaharNaveh](https://github.com/ShaharNaveh)
- [@Lee-W](https://github.com/Lee-W)
## 0.14.2
Released on 2025-10-23.

44
Cargo.lock generated
View File

@@ -243,7 +243,7 @@ dependencies = [
"bitflags 2.9.4",
"cexpr",
"clang-sys",
"itertools 0.10.5",
"itertools 0.13.0",
"log",
"prettyplease",
"proc-macro2",
@@ -633,7 +633,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]]
@@ -642,7 +642,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]]
@@ -1007,7 +1007,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.60.2",
"windows-sys 0.61.0",
]
[[package]]
@@ -1093,7 +1093,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.61.0",
]
[[package]]
@@ -1115,13 +1115,12 @@ dependencies = [
[[package]]
name = "etcetera"
version = "0.10.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6"
checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96"
dependencies = [
"cfg-if",
"home",
"windows-sys 0.59.0",
"windows-sys 0.61.0",
]
[[package]]
@@ -1366,15 +1365,6 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "home"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "html-escape"
version = "0.2.13"
@@ -1563,7 +1553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
dependencies = [
"equivalent",
"hashbrown 0.15.5",
"hashbrown 0.16.0",
"serde",
"serde_core",
]
@@ -1690,7 +1680,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -1754,7 +1744,7 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -2835,7 +2825,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.14.2"
version = "0.14.3"
dependencies = [
"anyhow",
"argfile",
@@ -3092,7 +3082,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.14.2"
version = "0.14.3"
dependencies = [
"aho-corasick",
"anyhow",
@@ -3447,7 +3437,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.14.2"
version = "0.14.3"
dependencies = [
"console_error_panic_hook",
"console_log",
@@ -3545,7 +3535,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys 0.61.0",
]
[[package]]
@@ -3941,7 +3931,7 @@ dependencies = [
"getrandom 0.3.4",
"once_cell",
"rustix",
"windows-sys 0.52.0",
"windows-sys 0.61.0",
]
[[package]]
@@ -5021,7 +5011,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.61.0",
]
[[package]]

View File

@@ -5,7 +5,7 @@ resolver = "2"
[workspace.package]
# Please update rustfmt.toml when bumping the Rust edition
edition = "2024"
rust-version = "1.88"
rust-version = "1.89"
homepage = "https://docs.astral.sh/ruff"
documentation = "https://docs.astral.sh/ruff"
repository = "https://github.com/astral-sh/ruff"
@@ -84,7 +84,7 @@ dashmap = { version = "6.0.1" }
dir-test = { version = "0.4.0" }
dunce = { version = "1.0.5" }
drop_bomb = { version = "0.1.5" }
etcetera = { version = "0.10.0" }
etcetera = { version = "0.11.0" }
fern = { version = "0.7.0" }
filetime = { version = "0.2.23" }
getrandom = { version = "0.3.1" }

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ use path_absolutize::CWD;
use ruff_db::system::{SystemPath, SystemPathBuf};
use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports};
use ruff_linter::package::PackageRoot;
use ruff_linter::source_kind::SourceKind;
use ruff_linter::{warn_user, warn_user_once};
use ruff_python_ast::{PySourceType, SourceType};
use ruff_workspace::resolver::{ResolvedFile, match_exclusion, python_files_in_path};
@@ -127,10 +128,6 @@ pub(crate) fn analyze_graph(
},
Some(language) => PySourceType::from(language),
};
if matches!(source_type, PySourceType::Ipynb) {
debug!("Ignoring Jupyter notebook: {}", path.display());
continue;
}
// Convert to system paths.
let Ok(package) = package.map(SystemPathBuf::from_path_buf).transpose() else {
@@ -147,13 +144,34 @@ pub(crate) fn analyze_graph(
let root = root.clone();
let result = inner_result.clone();
scope.spawn(move |_| {
// Extract source code (handles both .py and .ipynb files)
let source_kind = match SourceKind::from_path(path.as_std_path(), source_type) {
Ok(Some(source_kind)) => source_kind,
Ok(None) => {
debug!("Skipping non-Python notebook: {path}");
return;
}
Err(err) => {
warn!("Failed to read source for {path}: {err}");
return;
}
};
let source_code = source_kind.source_code();
// Identify any imports via static analysis.
let mut imports =
ModuleImports::detect(&db, &path, package.as_deref(), string_imports)
.unwrap_or_else(|err| {
warn!("Failed to generate import map for {path}: {err}");
ModuleImports::default()
});
let mut imports = ModuleImports::detect(
&db,
source_code,
source_type,
&path,
package.as_deref(),
string_imports,
)
.unwrap_or_else(|err| {
warn!("Failed to generate import map for {path}: {err}");
ModuleImports::default()
});
debug!("Discovered {} imports for {}", imports.len(), path);

View File

@@ -370,7 +370,7 @@ pub(crate) fn format_source(
let line_index = LineIndex::from_source_text(unformatted);
let byte_range = range.to_text_range(unformatted, &line_index);
format_range(unformatted, byte_range, options).map(|formatted_range| {
let mut formatted = unformatted.to_string();
let mut formatted = unformatted.clone();
formatted.replace_range(
std::ops::Range::<usize>::from(formatted_range.source_range()),
formatted_range.as_code(),

View File

@@ -653,3 +653,133 @@ fn venv() -> Result<()> {
Ok(())
}
#[test]
fn notebook_basic() -> Result<()> {
let tempdir = TempDir::new()?;
let root = ChildPath::new(tempdir.path());
root.child("ruff").child("__init__.py").write_str("")?;
root.child("ruff")
.child("a.py")
.write_str(indoc::indoc! {r#"
def helper():
pass
"#})?;
// Create a basic notebook with a simple import
root.child("notebook.ipynb").write_str(indoc::indoc! {r#"
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ruff.a import helper"
]
}
],
"metadata": {
"language_info": {
"name": "python",
"version": "3.12.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
"#})?;
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
{
"notebook.ipynb": [
"ruff/a.py"
],
"ruff/__init__.py": [],
"ruff/a.py": []
}
----- stderr -----
"###);
});
Ok(())
}
#[test]
fn notebook_with_magic() -> Result<()> {
let tempdir = TempDir::new()?;
let root = ChildPath::new(tempdir.path());
root.child("ruff").child("__init__.py").write_str("")?;
root.child("ruff")
.child("a.py")
.write_str(indoc::indoc! {r#"
def helper():
pass
"#})?;
// Create a notebook with IPython magic commands and imports
root.child("notebook.ipynb").write_str(indoc::indoc! {r#"
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%load_ext autoreload\n",
"%autoreload 2"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ruff.a import helper"
]
}
],
"metadata": {
"language_info": {
"name": "python",
"version": "3.12.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
"#})?;
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
{
"notebook.ipynb": [
"ruff/a.py"
],
"ruff/__init__.py": [],
"ruff/a.py": []
}
----- stderr -----
"###);
});
Ok(())
}

View File

@@ -470,6 +470,11 @@ impl File {
self.source_type(db).is_stub()
}
/// Returns `true` if the file is an `__init__.pyi`
pub fn is_package_stub(self, db: &dyn Db) -> bool {
self.path(db).as_str().ends_with("__init__.pyi")
}
pub fn source_type(self, db: &dyn Db) -> PySourceType {
match self.path(db) {
FilePath::System(path) => path

View File

@@ -723,10 +723,11 @@ impl ruff_cache::CacheKey for SystemPathBuf {
/// A slice of a virtual path on [`System`](super::System) (akin to [`str`]).
#[repr(transparent)]
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct SystemVirtualPath(str);
impl SystemVirtualPath {
pub fn new(path: &str) -> &SystemVirtualPath {
pub const fn new(path: &str) -> &SystemVirtualPath {
// SAFETY: SystemVirtualPath is marked as #[repr(transparent)] so the conversion from a
// *const str to a *const SystemVirtualPath is valid.
unsafe { &*(path as *const str as *const SystemVirtualPath) }
@@ -767,8 +768,8 @@ pub struct SystemVirtualPathBuf(String);
impl SystemVirtualPathBuf {
#[inline]
pub fn as_path(&self) -> &SystemVirtualPath {
SystemVirtualPath::new(&self.0)
pub const fn as_path(&self) -> &SystemVirtualPath {
SystemVirtualPath::new(self.0.as_str())
}
}
@@ -852,6 +853,12 @@ impl ruff_cache::CacheKey for SystemVirtualPathBuf {
}
}
impl Borrow<SystemVirtualPath> for SystemVirtualPathBuf {
fn borrow(&self) -> &SystemVirtualPath {
self.as_path()
}
}
/// Deduplicates identical paths and removes nested paths.
///
/// # Examples

View File

@@ -62,7 +62,7 @@ fn generate_set(output: &mut String, set: Set, parents: &mut Vec<Set>) {
generate_set(
output,
Set::Named {
name: set_name.to_string(),
name: set_name.clone(),
set: *sub_set,
},
parents,

View File

@@ -104,7 +104,7 @@ fn generate_set(output: &mut String, set: Set, parents: &mut Vec<Set>) {
generate_set(
output,
Set::Named {
name: set_name.to_string(),
name: set_name.clone(),
set: *sub_set,
},
parents,

View File

@@ -1006,7 +1006,7 @@ impl<Context> std::fmt::Debug for Align<'_, Context> {
/// Block indents indent a block of code, such as in a function body, and therefore insert a line
/// break before and after the content.
///
/// Doesn't create an indentation if the passed in content is [`FormatElement.is_empty`].
/// Doesn't create an indentation if the passed in content is empty.
///
/// # Examples
///

View File

@@ -3,8 +3,9 @@ use std::collections::{BTreeMap, BTreeSet};
use anyhow::Result;
use ruff_db::system::{SystemPath, SystemPathBuf};
use ruff_python_ast::PySourceType;
use ruff_python_ast::helpers::to_module_path;
use ruff_python_parser::{Mode, ParseOptions, parse};
use ruff_python_parser::{ParseOptions, parse};
use crate::collector::Collector;
pub use crate::db::ModuleDb;
@@ -24,13 +25,14 @@ impl ModuleImports {
/// Detect the [`ModuleImports`] for a given Python file.
pub fn detect(
db: &ModuleDb,
source: &str,
source_type: PySourceType,
path: &SystemPath,
package: Option<&SystemPath>,
string_imports: StringImports,
) -> Result<Self> {
// Read and parse the source code.
let source = std::fs::read_to_string(path)?;
let parsed = parse(&source, ParseOptions::from(Mode::Module))?;
// Parse the source code.
let parsed = parse(source, ParseOptions::from(source_type))?;
let module_path =
package.and_then(|package| to_module_path(package.as_std_path(), path.as_std_path()));

View File

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

View File

@@ -145,3 +145,11 @@ with open("file.txt", "w") as f:
with open("file.txt", "w") as f:
for line in text:
f.write(line)
# See: https://github.com/astral-sh/ruff/issues/20785
import json
data = {"price": 100}
with open("test.json", "wb") as f:
f.write(json.dumps(data, indent=4).encode("utf-8"))

View File

@@ -4,4 +4,4 @@ expression: content
---
syntax_errors.py:
1:15 invalid-syntax: Expected one or more symbol names after import
3:12 invalid-syntax: Expected ')', found newline
3:12 invalid-syntax: Expected `)`, found newline

View File

@@ -78,7 +78,7 @@ pub(crate) fn unconventional_import_alias(
let mut diagnostic = checker.report_diagnostic(
UnconventionalImportAlias {
name: qualified_name,
asname: expected_alias.to_string(),
asname: expected_alias.clone(),
},
binding.range(),
);

View File

@@ -6,21 +6,17 @@ use ruff_macros::CacheKey;
#[derive(Clone, Copy, Debug, CacheKey, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Default)]
pub enum ParametrizeNameType {
#[serde(rename = "csv")]
Csv,
#[serde(rename = "tuple")]
#[default]
Tuple,
#[serde(rename = "list")]
List,
}
impl Default for ParametrizeNameType {
fn default() -> Self {
Self::Tuple
}
}
impl Display for ParametrizeNameType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
@@ -33,19 +29,15 @@ impl Display for ParametrizeNameType {
#[derive(Clone, Copy, Debug, CacheKey, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Default)]
pub enum ParametrizeValuesType {
#[serde(rename = "tuple")]
Tuple,
#[serde(rename = "list")]
#[default]
List,
}
impl Default for ParametrizeValuesType {
fn default() -> Self {
Self::List
}
}
impl Display for ParametrizeValuesType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
@@ -57,19 +49,15 @@ impl Display for ParametrizeValuesType {
#[derive(Clone, Copy, Debug, CacheKey, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Default)]
pub enum ParametrizeValuesRowType {
#[serde(rename = "tuple")]
#[default]
Tuple,
#[serde(rename = "list")]
List,
}
impl Default for ParametrizeValuesRowType {
fn default() -> Self {
Self::Tuple
}
}
impl Display for ParametrizeValuesRowType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {

View File

@@ -9,19 +9,15 @@ use ruff_macros::CacheKey;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, CacheKey)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Default)]
pub enum Quote {
/// Use double quotes.
#[default]
Double,
/// Use single quotes.
Single,
}
impl Default for Quote {
fn default() -> Self {
Self::Double
}
}
impl From<ruff_python_ast::str::Quote> for Quote {
fn from(value: ruff_python_ast::str::Quote) -> Self {
match value {

View File

@@ -116,7 +116,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) {
let mut diagnostic = checker.report_diagnostic(
ReimplementedBuiltin {
replacement: contents.to_string(),
replacement: contents.clone(),
},
TextRange::new(stmt.start(), terminal.stmt.end()),
);
@@ -212,7 +212,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) {
let mut diagnostic = checker.report_diagnostic(
ReimplementedBuiltin {
replacement: contents.to_string(),
replacement: contents.clone(),
},
TextRange::new(stmt.start(), terminal.stmt.end()),
);

View File

@@ -47,7 +47,7 @@ pub(crate) fn banned_api<T: Ranged>(checker: &Checker, policy: &NameMatchPolicy,
checker.report_diagnostic(
BannedApi {
name: banned_module,
message: reason.msg.to_string(),
message: reason.msg.clone(),
},
node.range(),
);
@@ -74,8 +74,8 @@ pub(crate) fn banned_attribute_access(checker: &Checker, expr: &Expr) {
{
checker.report_diagnostic(
BannedApi {
name: banned_path.to_string(),
message: ban.msg.to_string(),
name: banned_path.clone(),
message: ban.msg.clone(),
},
expr.range(),
);

View File

@@ -20,21 +20,17 @@ use super::categorize::ImportSection;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, CacheKey)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Default)]
pub enum RelativeImportsOrder {
/// Place "closer" imports (fewer `.` characters, most local) before
/// "further" imports (more `.` characters, least local).
ClosestToFurthest,
/// Place "further" imports (more `.` characters, least local) imports
/// before "closer" imports (fewer `.` characters, most local).
#[default]
FurthestToClosest,
}
impl Default for RelativeImportsOrder {
fn default() -> Self {
Self::FurthestToClosest
}
}
impl Display for RelativeImportsOrder {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {

View File

@@ -427,7 +427,7 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare)
for diagnostic in &mut diagnostics {
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
content.to_string(),
content.clone(),
compare.range(),
)));
}

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E231 [*] Missing whitespace after ','
E231 [*] Missing whitespace after `,`
--> E23.py:2:7
|
1 | #: E231
@@ -18,7 +18,7 @@ help: Add missing whitespace
4 | a[b1,:]
5 | #: E231
E231 [*] Missing whitespace after ','
E231 [*] Missing whitespace after `,`
--> E23.py:4:5
|
2 | a = (1,2)
@@ -38,7 +38,7 @@ help: Add missing whitespace
6 | a = [{'a':''}]
7 | #: Okay
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:6:10
|
4 | a[b1,:]
@@ -58,7 +58,7 @@ help: Add missing whitespace
8 | a = (4,)
9 | b = (5, )
E231 [*] Missing whitespace after ','
E231 [*] Missing whitespace after `,`
--> E23.py:19:10
|
17 | def foo() -> None:
@@ -77,7 +77,7 @@ help: Add missing whitespace
21 |
22 | #: Okay
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:29:20
|
27 | mdtypes_template = {
@@ -96,7 +96,7 @@ help: Add missing whitespace
31 |
32 | # E231
E231 [*] Missing whitespace after ','
E231 [*] Missing whitespace after `,`
--> E23.py:33:6
|
32 | # E231
@@ -115,7 +115,7 @@ help: Add missing whitespace
35 | # Okay because it's hard to differentiate between the usages of a colon in a f-string
36 | f"{a:=1}"
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:47:37
|
46 | #: E231
@@ -134,7 +134,7 @@ help: Add missing whitespace
49 | #: Okay
50 | a = (1,)
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:60:13
|
58 | results = {
@@ -154,7 +154,7 @@ help: Add missing whitespace
62 | results_in_tuple = (
63 | {
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:65:17
|
63 | {
@@ -174,7 +174,7 @@ help: Add missing whitespace
67 | )
68 | results_in_list = [
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:71:17
|
69 | {
@@ -194,7 +194,7 @@ help: Add missing whitespace
73 | ]
74 | results_in_list_first = [
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:76:17
|
74 | results_in_list_first = [
@@ -214,7 +214,7 @@ help: Add missing whitespace
78 | ]
79 |
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:82:13
|
80 | x = [
@@ -234,7 +234,7 @@ help: Add missing whitespace
84 | "k3":[2], # E231
85 | "k4": [2],
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:84:13
|
82 | "k1":[2], # E231
@@ -254,7 +254,7 @@ help: Add missing whitespace
86 | "k5": [2],
87 | "k6": [1, 2, 3, 4,5,6,7] # E231
E231 [*] Missing whitespace after ','
E231 [*] Missing whitespace after `,`
--> E23.py:87:26
|
85 | "k4": [2],
@@ -274,7 +274,7 @@ help: Add missing whitespace
89 | {
90 | "k1": [
E231 [*] Missing whitespace after ','
E231 [*] Missing whitespace after `,`
--> E23.py:87:28
|
85 | "k4": [2],
@@ -294,7 +294,7 @@ help: Add missing whitespace
89 | {
90 | "k1": [
E231 [*] Missing whitespace after ','
E231 [*] Missing whitespace after `,`
--> E23.py:87:30
|
85 | "k4": [2],
@@ -314,7 +314,7 @@ help: Add missing whitespace
89 | {
90 | "k1": [
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:92:21
|
90 | "k1": [
@@ -334,7 +334,7 @@ help: Add missing whitespace
94 | {
95 | "kb": [2,3], # E231
E231 [*] Missing whitespace after ','
E231 [*] Missing whitespace after `,`
--> E23.py:92:24
|
90 | "k1": [
@@ -354,7 +354,7 @@ help: Add missing whitespace
94 | {
95 | "kb": [2,3], # E231
E231 [*] Missing whitespace after ','
E231 [*] Missing whitespace after `,`
--> E23.py:95:25
|
93 | },
@@ -374,7 +374,7 @@ help: Add missing whitespace
97 | {
98 | "ka":[2, 3], # E231
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:98:21
|
96 | },
@@ -394,7 +394,7 @@ help: Add missing whitespace
100 | "kc": [2, 3], # Ok
101 | "kd": [2,3], # E231
E231 [*] Missing whitespace after ','
E231 [*] Missing whitespace after `,`
--> E23.py:101:25
|
99 | "kb": [2, 3], # Ok
@@ -414,7 +414,7 @@ help: Add missing whitespace
103 | },
104 | ]
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:102:21
|
100 | "kc": [2, 3], # Ok
@@ -434,7 +434,7 @@ help: Add missing whitespace
104 | ]
105 | }
E231 [*] Missing whitespace after ','
E231 [*] Missing whitespace after `,`
--> E23.py:102:24
|
100 | "kc": [2, 3], # Ok
@@ -454,7 +454,7 @@ help: Add missing whitespace
104 | ]
105 | }
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:109:18
|
108 | # Should be E231 errors on all of these type parameters and function parameters, but not on their (strange) defaults
@@ -473,7 +473,7 @@ help: Add missing whitespace
111 | y:B = [[["foo", "bar"]]],
112 | z:object = "fooo",
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:109:40
|
108 | # Should be E231 errors on all of these type parameters and function parameters, but not on their (strange) defaults
@@ -492,7 +492,7 @@ help: Add missing whitespace
111 | y:B = [[["foo", "bar"]]],
112 | z:object = "fooo",
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:109:70
|
108 | # Should be E231 errors on all of these type parameters and function parameters, but not on their (strange) defaults
@@ -511,7 +511,7 @@ help: Add missing whitespace
111 | y:B = [[["foo", "bar"]]],
112 | z:object = "fooo",
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:110:6
|
108 | # Should be E231 errors on all of these type parameters and function parameters, but not on their (strange) defaults
@@ -531,7 +531,7 @@ help: Add missing whitespace
112 | z:object = "fooo",
113 | ):
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:111:6
|
109 | def pep_696_bad[A:object="foo"[::-1], B:object =[[["foo", "bar"]]], C:object= bytes](
@@ -551,7 +551,7 @@ help: Add missing whitespace
113 | ):
114 | pass
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:112:6
|
110 | x:A = "foo"[::-1],
@@ -571,7 +571,7 @@ help: Add missing whitespace
114 | pass
115 |
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:116:18
|
114 | pass
@@ -591,7 +591,7 @@ help: Add missing whitespace
118 | self,
119 | x:A = "foo"[::-1],
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:116:40
|
114 | pass
@@ -611,7 +611,7 @@ help: Add missing whitespace
118 | self,
119 | x:A = "foo"[::-1],
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:116:70
|
114 | pass
@@ -631,7 +631,7 @@ help: Add missing whitespace
118 | self,
119 | x:A = "foo"[::-1],
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:117:29
|
116 | class PEP696Bad[A:object="foo"[::-1], B:object =[[["foo", "bar"]]], C:object= bytes]:
@@ -650,7 +650,7 @@ help: Add missing whitespace
119 | x:A = "foo"[::-1],
120 | y:B = [[["foo", "bar"]]],
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:117:51
|
116 | class PEP696Bad[A:object="foo"[::-1], B:object =[[["foo", "bar"]]], C:object= bytes]:
@@ -669,7 +669,7 @@ help: Add missing whitespace
119 | x:A = "foo"[::-1],
120 | y:B = [[["foo", "bar"]]],
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:117:81
|
116 | class PEP696Bad[A:object="foo"[::-1], B:object =[[["foo", "bar"]]], C:object= bytes]:
@@ -688,7 +688,7 @@ help: Add missing whitespace
119 | x:A = "foo"[::-1],
120 | y:B = [[["foo", "bar"]]],
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:119:10
|
117 | def pep_696_bad_method[A:object="foo"[::-1], B:object =[[["foo", "bar"]]], C:object= bytes](
@@ -708,7 +708,7 @@ help: Add missing whitespace
121 | z:object = "fooo",
122 | ):
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:120:10
|
118 | self,
@@ -728,7 +728,7 @@ help: Add missing whitespace
122 | ):
123 | pass
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:121:10
|
119 | x:A = "foo"[::-1],
@@ -748,7 +748,7 @@ help: Add missing whitespace
123 | pass
124 |
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:125:32
|
123 | pass
@@ -768,7 +768,7 @@ help: Add missing whitespace
127 | pass
128 |
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:125:54
|
123 | pass
@@ -788,7 +788,7 @@ help: Add missing whitespace
127 | pass
128 |
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:125:84
|
123 | pass
@@ -808,7 +808,7 @@ help: Add missing whitespace
127 | pass
128 |
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:126:47
|
125 | class PEP696BadWithEmptyBases[A:object="foo"[::-1], B:object =[[["foo", "bar"]]], C:object= bytes]():
@@ -826,7 +826,7 @@ help: Add missing whitespace
128 |
129 | # Should be no E231 errors on any of these:
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:126:69
|
125 | class PEP696BadWithEmptyBases[A:object="foo"[::-1], B:object =[[["foo", "bar"]]], C:object= bytes]():
@@ -844,7 +844,7 @@ help: Add missing whitespace
128 |
129 | # Should be no E231 errors on any of these:
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:126:99
|
125 | class PEP696BadWithEmptyBases[A:object="foo"[::-1], B:object =[[["foo", "bar"]]], C:object= bytes]():
@@ -862,7 +862,7 @@ help: Add missing whitespace
128 |
129 | # Should be no E231 errors on any of these:
E231 [*] Missing whitespace after ','
E231 [*] Missing whitespace after `,`
--> E23.py:147:6
|
146 | # E231
@@ -881,7 +881,7 @@ help: Add missing whitespace
149 | # Okay because it's hard to differentiate between the usages of a colon in a t-string
150 | t"{a:=1}"
E231 [*] Missing whitespace after ':'
E231 [*] Missing whitespace after `:`
--> E23.py:161:37
|
160 | #: E231

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
invalid-syntax: Expected ']', found '('
invalid-syntax: Expected `]`, found `(`
--> E30_syntax_error.py:4:15
|
2 | # parenthesis.
@@ -11,7 +11,7 @@ invalid-syntax: Expected ']', found '('
5 | pass
|
invalid-syntax: Expected ')', found newline
invalid-syntax: Expected `)`, found newline
--> E30_syntax_error.py:13:18
|
12 | class Foo:
@@ -32,7 +32,7 @@ E301 Expected 1 blank line, found 0
|
help: Add missing blank line
invalid-syntax: Expected ')', found newline
invalid-syntax: Expected `)`, found newline
--> E30_syntax_error.py:18:11
|
16 | pass
@@ -41,7 +41,7 @@ invalid-syntax: Expected ')', found newline
| ^
|
invalid-syntax: Expected ')', found newline
invalid-syntax: Expected `)`, found newline
--> E30_syntax_error.py:21:9
|
21 | def top(

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
invalid-syntax: Expected ']', found '('
invalid-syntax: Expected `]`, found `(`
--> E30_syntax_error.py:4:15
|
2 | # parenthesis.
@@ -22,7 +22,7 @@ E302 Expected 2 blank lines, found 1
|
help: Add missing blank line(s)
invalid-syntax: Expected ')', found newline
invalid-syntax: Expected `)`, found newline
--> E30_syntax_error.py:13:18
|
12 | class Foo:
@@ -32,7 +32,7 @@ invalid-syntax: Expected ')', found newline
15 | def method():
|
invalid-syntax: Expected ')', found newline
invalid-syntax: Expected `)`, found newline
--> E30_syntax_error.py:18:11
|
16 | pass
@@ -41,7 +41,7 @@ invalid-syntax: Expected ')', found newline
| ^
|
invalid-syntax: Expected ')', found newline
invalid-syntax: Expected `)`, found newline
--> E30_syntax_error.py:21:9
|
21 | def top(

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
invalid-syntax: Expected ']', found '('
invalid-syntax: Expected `]`, found `(`
--> E30_syntax_error.py:4:15
|
2 | # parenthesis.
@@ -21,7 +21,7 @@ E303 Too many blank lines (3)
|
help: Remove extraneous blank line(s)
invalid-syntax: Expected ')', found newline
invalid-syntax: Expected `)`, found newline
--> E30_syntax_error.py:13:18
|
12 | class Foo:
@@ -31,7 +31,7 @@ invalid-syntax: Expected ')', found newline
15 | def method():
|
invalid-syntax: Expected ')', found newline
invalid-syntax: Expected `)`, found newline
--> E30_syntax_error.py:18:11
|
16 | pass
@@ -40,7 +40,7 @@ invalid-syntax: Expected ')', found newline
| ^
|
invalid-syntax: Expected ')', found newline
invalid-syntax: Expected `)`, found newline
--> E30_syntax_error.py:21:9
|
21 | def top(

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
invalid-syntax: Expected ']', found '('
invalid-syntax: Expected `]`, found `(`
--> E30_syntax_error.py:4:15
|
2 | # parenthesis.
@@ -11,7 +11,7 @@ invalid-syntax: Expected ']', found '('
5 | pass
|
invalid-syntax: Expected ')', found newline
invalid-syntax: Expected `)`, found newline
--> E30_syntax_error.py:13:18
|
12 | class Foo:
@@ -31,7 +31,7 @@ E305 Expected 2 blank lines after class or function definition, found (1)
|
help: Add missing blank line(s)
invalid-syntax: Expected ')', found newline
invalid-syntax: Expected `)`, found newline
--> E30_syntax_error.py:18:11
|
16 | pass
@@ -40,7 +40,7 @@ invalid-syntax: Expected ')', found newline
| ^
|
invalid-syntax: Expected ')', found newline
invalid-syntax: Expected `)`, found newline
--> E30_syntax_error.py:21:9
|
21 | def top(

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
invalid-syntax: Expected ']', found '('
invalid-syntax: Expected `]`, found `(`
--> E30_syntax_error.py:4:15
|
2 | # parenthesis.
@@ -11,7 +11,7 @@ invalid-syntax: Expected ']', found '('
5 | pass
|
invalid-syntax: Expected ')', found newline
invalid-syntax: Expected `)`, found newline
--> E30_syntax_error.py:13:18
|
12 | class Foo:
@@ -21,7 +21,7 @@ invalid-syntax: Expected ')', found newline
15 | def method():
|
invalid-syntax: Expected ')', found newline
invalid-syntax: Expected `)`, found newline
--> E30_syntax_error.py:18:11
|
16 | pass
@@ -30,7 +30,7 @@ invalid-syntax: Expected ')', found newline
| ^
|
invalid-syntax: Expected ')', found newline
invalid-syntax: Expected `)`, found newline
--> E30_syntax_error.py:21:9
|
21 | def top(

View File

@@ -94,7 +94,7 @@ pub(crate) fn capitalized(checker: &Checker, docstring: &Docstring) {
let mut diagnostic = checker.report_diagnostic(
FirstWordUncapitalized {
first_word: first_word.to_string(),
capitalized_word: capitalized_word.to_string(),
capitalized_word: capitalized_word.clone(),
},
docstring.range(),
);

View File

@@ -188,7 +188,7 @@ pub(crate) fn bit_count(checker: &Checker, call: &ExprCall) {
let mut diagnostic = checker.report_diagnostic(
BitCount {
existing: SourceCodeSnippet::from_str(literal_text),
replacement: SourceCodeSnippet::new(replacement.to_string()),
replacement: SourceCodeSnippet::new(replacement.clone()),
},
call.range(),
);

View File

@@ -5,7 +5,6 @@ use ruff_python_ast::{
relocate::relocate_expr,
visitor::{self, Visitor},
};
use ruff_python_codegen::Generator;
use ruff_text_size::{Ranged, TextRange};

View File

@@ -134,3 +134,14 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(fo
75 | f.write(foobar)
|
help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")`
FURB103 `open` and `write` should be replaced by `Path("test.json")....`
--> FURB103.py:154:6
|
152 | data = {"price": 100}
153 |
154 | with open("test.json", "wb") as f:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
155 | f.write(json.dumps(data, indent=4).encode("utf-8"))
|
help: Replace with `Path("test.json")....`

View File

@@ -257,4 +257,25 @@ help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")`
75 + pathlib.Path("file.txt").write_text(foobar, newline="\r\n")
76 |
77 | # Non-errors.
78 |
78 |
FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....`
--> FURB103.py:154:6
|
152 | data = {"price": 100}
153 |
154 | with open("test.json", "wb") as f:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
155 | f.write(json.dumps(data, indent=4).encode("utf-8"))
|
help: Replace with `Path("test.json")....`
148 |
149 | # See: https://github.com/astral-sh/ruff/issues/20785
150 | import json
151 + import pathlib
152 |
153 | data = {"price": 100}
154 |
- with open("test.json", "wb") as f:
- f.write(json.dumps(data, indent=4).encode("utf-8"))
155 + pathlib.Path("test.json").write_bytes(json.dumps(data, indent=4).encode("utf-8"))

View File

@@ -104,3 +104,14 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(ba
51 | # writes a single time to file and that bit they can replace.
|
help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))`
FURB103 `open` and `write` should be replaced by `Path("test.json")....`
--> FURB103.py:154:6
|
152 | data = {"price": 100}
153 |
154 | with open("test.json", "wb") as f:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
155 | f.write(json.dumps(data, indent=4).encode("utf-8"))
|
help: Replace with `Path("test.json")....`

View File

@@ -3372,7 +3372,7 @@ impl Arguments {
pub fn arguments_source_order(&self) -> impl Iterator<Item = ArgOrKeyword<'_>> {
let args = self.args.iter().map(ArgOrKeyword::Arg);
let keywords = self.keywords.iter().map(ArgOrKeyword::Keyword);
args.merge_by(keywords, |left, right| left.start() < right.start())
args.merge_by(keywords, |left, right| left.start() <= right.start())
}
pub fn inner_range(&self) -> TextRange {

View File

@@ -335,3 +335,96 @@ def overload4():
# trailing comment
def overload4(a: int): ...
# In preview, we preserve these newlines at the start of functions:
def preserved1():
return 1
def preserved2():
pass
def preserved3():
def inner(): ...
def preserved4():
def inner():
print("with a body")
return 1
return 2
def preserved5():
...
# trailing comment prevents collapsing the stub
def preserved6():
# Comment
return 1
def preserved7():
# comment
# another line
# and a third
return 0
def preserved8(): # this also prevents collapsing the stub
...
# But we still discard these newlines:
def removed1():
"Docstring"
return 1
def removed2():
...
def removed3():
... # trailing same-line comment does not prevent collapsing the stub
# And we discard empty lines after the first:
def partially_preserved1():
return 1
# We only preserve blank lines, not add new ones
def untouched1():
# comment
return 0
def untouched2():
# comment
return 0
def untouched3():
# comment
# another line
# and a third
return 0

View File

@@ -61,3 +61,9 @@ def test6 ():
print("Format" )
print(3 + 4)<RANGE_END>
print("Format to fix indentation" )
def test7 ():
<RANGE_START>print("Format" )
print(3 + 4)<RANGE_END>
print("Format to fix indentation" )

View File

@@ -334,7 +334,7 @@ class A: ...
let options = PyFormatOptions::from_source_type(source_type);
let printed = format_range(&source, TextRange::new(start, end), options).unwrap();
let mut formatted = source.to_string();
let mut formatted = source.clone();
formatted.replace_range(
std::ops::Range::<usize>::from(printed.source_range()),
printed.as_code(),

View File

@@ -36,3 +36,10 @@ pub(crate) const fn is_remove_parens_around_except_types_enabled(
) -> bool {
context.is_preview()
}
/// Returns `true` if the
/// [`allow_newline_after_block_open`](https://github.com/astral-sh/ruff/pull/21110) preview style
/// is enabled.
pub(crate) const fn is_allow_newline_after_block_open_enabled(context: &PyFormatContext) -> bool {
context.is_preview()
}

View File

@@ -8,7 +8,7 @@ use ruff_python_trivia::{SimpleToken, SimpleTokenKind, SimpleTokenizer};
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::comments::{SourceComment, leading_alternate_branch_comments, trailing_comments};
use crate::statement::suite::{SuiteKind, contains_only_an_ellipsis};
use crate::statement::suite::{SuiteKind, as_only_an_ellipsis};
use crate::verbatim::write_suppressed_clause_header;
use crate::{has_skip_comment, prelude::*};
@@ -449,17 +449,10 @@ impl Format<PyFormatContext<'_>> for FormatClauseBody<'_> {
|| matches!(self.kind, SuiteKind::Function | SuiteKind::Class);
if should_collapse_stub
&& contains_only_an_ellipsis(self.body, f.context().comments())
&& let Some(ellipsis) = as_only_an_ellipsis(self.body, f.context().comments())
&& self.trailing_comments.is_empty()
{
write!(
f,
[
space(),
self.body.format().with_options(self.kind),
hard_line_break()
]
)
write!(f, [space(), ellipsis.format(), hard_line_break()])
} else {
write!(
f,

View File

@@ -13,7 +13,9 @@ use crate::comments::{
use crate::context::{NodeLevel, TopLevelStatementPosition, WithIndentLevel, WithNodeLevel};
use crate::other::string_literal::StringLiteralKind;
use crate::prelude::*;
use crate::preview::is_blank_line_before_decorated_class_in_stub_enabled;
use crate::preview::{
is_allow_newline_after_block_open_enabled, is_blank_line_before_decorated_class_in_stub_enabled,
};
use crate::statement::stmt_expr::FormatStmtExpr;
use crate::verbatim::{
suppressed_node, write_suppressed_statements_starting_with_leading_comment,
@@ -169,6 +171,22 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
false,
)
} else {
// Allow an empty line after a function header in preview, if the function has no
// docstring and no initial comment.
let allow_newline_after_block_open =
is_allow_newline_after_block_open_enabled(f.context())
&& matches!(self.kind, SuiteKind::Function)
&& matches!(first, SuiteChildStatement::Other(_));
let start = comments
.leading(first)
.first()
.map_or_else(|| first.start(), Ranged::start);
if allow_newline_after_block_open && lines_before(start, f.context().source()) > 1 {
empty_line().fmt(f)?;
}
first.fmt(f)?;
let empty_line_after_docstring = if matches!(first, SuiteChildStatement::Docstring(_))
@@ -218,7 +236,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
)?;
} else {
// Preserve empty lines after a stub implementation but don't insert a new one if there isn't any present in the source.
// This is useful when having multiple function overloads that should be grouped to getter by omitting new lines between them.
// This is useful when having multiple function overloads that should be grouped together by omitting new lines between them.
let is_preceding_stub_function_without_empty_line = following
.is_function_def_stmt()
&& preceding
@@ -728,17 +746,21 @@ fn stub_suite_can_omit_empty_line(preceding: &Stmt, following: &Stmt, f: &PyForm
/// Returns `true` if a function or class body contains only an ellipsis with no comments.
pub(crate) fn contains_only_an_ellipsis(body: &[Stmt], comments: &Comments) -> bool {
match body {
[Stmt::Expr(ast::StmtExpr { value, .. })] => {
let [node] = body else {
return false;
};
value.is_ellipsis_literal_expr()
&& !comments.has_leading(node)
&& !comments.has_trailing_own_line(node)
}
_ => false,
as_only_an_ellipsis(body, comments).is_some()
}
/// Returns `Some(Stmt::Ellipsis)` if a function or class body contains only an ellipsis with no
/// comments.
pub(crate) fn as_only_an_ellipsis<'a>(body: &'a [Stmt], comments: &Comments) -> Option<&'a Stmt> {
if let [node @ Stmt::Expr(ast::StmtExpr { value, .. })] = body
&& value.is_ellipsis_literal_expr()
&& !comments.has_leading(node)
&& !comments.has_trailing_own_line(node)
{
return Some(node);
}
None
}
/// Returns `true` if a [`Stmt`] is a class or function definition.

View File

@@ -1,7 +1,6 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/newlines.py
snapshot_kind: text
---
## Input
```python
@@ -342,6 +341,99 @@ def overload4():
# trailing comment
def overload4(a: int): ...
# In preview, we preserve these newlines at the start of functions:
def preserved1():
return 1
def preserved2():
pass
def preserved3():
def inner(): ...
def preserved4():
def inner():
print("with a body")
return 1
return 2
def preserved5():
...
# trailing comment prevents collapsing the stub
def preserved6():
# Comment
return 1
def preserved7():
# comment
# another line
# and a third
return 0
def preserved8(): # this also prevents collapsing the stub
...
# But we still discard these newlines:
def removed1():
"Docstring"
return 1
def removed2():
...
def removed3():
... # trailing same-line comment does not prevent collapsing the stub
# And we discard empty lines after the first:
def partially_preserved1():
return 1
# We only preserve blank lines, not add new ones
def untouched1():
# comment
return 0
def untouched2():
# comment
return 0
def untouched3():
# comment
# another line
# and a third
return 0
```
## Output
@@ -732,6 +824,88 @@ def overload4():
def overload4(a: int): ...
# In preview, we preserve these newlines at the start of functions:
def preserved1():
return 1
def preserved2():
pass
def preserved3():
def inner(): ...
def preserved4():
def inner():
print("with a body")
return 1
return 2
def preserved5():
...
# trailing comment prevents collapsing the stub
def preserved6():
# Comment
return 1
def preserved7():
# comment
# another line
# and a third
return 0
def preserved8(): # this also prevents collapsing the stub
...
# But we still discard these newlines:
def removed1():
"Docstring"
return 1
def removed2(): ...
def removed3(): ... # trailing same-line comment does not prevent collapsing the stub
# And we discard empty lines after the first:
def partially_preserved1():
return 1
# We only preserve blank lines, not add new ones
def untouched1():
# comment
return 0
def untouched2():
# comment
return 0
def untouched3():
# comment
# another line
# and a third
return 0
```
@@ -739,7 +913,15 @@ def overload4(a: int): ...
```diff
--- Stable
+++ Preview
@@ -277,6 +277,7 @@
@@ -253,6 +253,7 @@
def fakehttp():
+
class FakeHTTPConnection:
if mock_close:
@@ -277,6 +278,7 @@
def a():
return 1
@@ -747,7 +929,7 @@ def overload4(a: int): ...
else:
pass
@@ -293,6 +294,7 @@
@@ -293,6 +295,7 @@
def a():
return 1
@@ -755,7 +937,7 @@ def overload4(a: int): ...
case 1:
def a():
@@ -303,6 +305,7 @@
@@ -303,6 +306,7 @@
def a():
return 1
@@ -763,7 +945,7 @@ def overload4(a: int): ...
except RuntimeError:
def a():
@@ -313,6 +316,7 @@
@@ -313,6 +317,7 @@
def a():
return 1
@@ -771,7 +953,7 @@ def overload4(a: int): ...
finally:
def a():
@@ -323,18 +327,22 @@
@@ -323,18 +328,22 @@
def a():
return 1
@@ -794,4 +976,64 @@ def overload4(a: int): ...
finally:
def a():
@@ -388,18 +397,22 @@
# In preview, we preserve these newlines at the start of functions:
def preserved1():
+
return 1
def preserved2():
+
pass
def preserved3():
+
def inner(): ...
def preserved4():
+
def inner():
print("with a body")
return 1
@@ -408,17 +421,20 @@
def preserved5():
+
...
# trailing comment prevents collapsing the stub
def preserved6():
+
# Comment
return 1
def preserved7():
+
# comment
# another line
# and a third
@@ -427,6 +443,7 @@
def preserved8(): # this also prevents collapsing the stub
+
...
@@ -445,6 +462,7 @@
# And we discard empty lines after the first:
def partially_preserved1():
+
return 1
```

View File

@@ -67,6 +67,12 @@ def test6 ():
print("Format" )
print(3 + 4)<RANGE_END>
print("Format to fix indentation" )
def test7 ():
<RANGE_START>print("Format" )
print(3 + 4)<RANGE_END>
print("Format to fix indentation" )
```
## Outputs
@@ -146,6 +152,27 @@ def test6 ():
print("Format")
print(3 + 4)
print("Format to fix indentation" )
def test7 ():
print("Format")
print(3 + 4)
print("Format to fix indentation" )
```
#### Preview changes
```diff
--- Stable
+++ Preview
@@ -55,6 +55,7 @@
def test6 ():
+
print("Format")
print(3 + 4)
print("Format to fix indentation" )
```
@@ -225,6 +252,27 @@ def test6 ():
print("Format")
print(3 + 4)
print("Format to fix indentation")
def test7 ():
print("Format")
print(3 + 4)
print("Format to fix indentation")
```
#### Preview changes
```diff
--- Stable
+++ Preview
@@ -55,6 +55,7 @@
def test6 ():
+
print("Format")
print(3 + 4)
print("Format to fix indentation")
```
@@ -304,4 +352,25 @@ def test6 ():
print("Format")
print(3 + 4)
print("Format to fix indentation")
def test7 ():
print("Format")
print(3 + 4)
print("Format to fix indentation")
```
#### Preview changes
```diff
--- Stable
+++ Preview
@@ -55,6 +55,7 @@
def test6 ():
+
print("Format")
print(3 + 4)
print("Format to fix indentation")
```

View File

@@ -78,9 +78,9 @@ pub enum InterpolatedStringErrorType {
impl std::fmt::Display for InterpolatedStringErrorType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::UnclosedLbrace => write!(f, "expecting '}}'"),
Self::UnclosedLbrace => write!(f, "expecting `}}`"),
Self::InvalidConversionFlag => write!(f, "invalid conversion character"),
Self::SingleRbrace => write!(f, "single '}}' is not allowed"),
Self::SingleRbrace => write!(f, "single `}}` is not allowed"),
Self::UnterminatedString => write!(f, "unterminated string"),
Self::UnterminatedTripleQuotedString => write!(f, "unterminated triple-quoted string"),
Self::LambdaWithoutParentheses => {
@@ -232,7 +232,7 @@ impl std::fmt::Display for ParseErrorType {
ParseErrorType::UnexpectedTokenAfterAsync(kind) => {
write!(
f,
"Expected 'def', 'with' or 'for' to follow 'async', found {kind}",
"Expected `def`, `with` or `for` to follow `async`, found {kind}",
)
}
ParseErrorType::InvalidArgumentUnpackingOrder => {
@@ -286,10 +286,10 @@ impl std::fmt::Display for ParseErrorType {
f.write_str("Parameter without a default cannot follow a parameter with a default")
}
ParseErrorType::ExpectedKeywordParam => {
f.write_str("Expected one or more keyword parameter after '*' separator")
f.write_str("Expected one or more keyword parameter after `*` separator")
}
ParseErrorType::VarParameterWithDefault => {
f.write_str("Parameter with '*' or '**' cannot have default value")
f.write_str("Parameter with `*` or `**` cannot have default value")
}
ParseErrorType::InvalidStarPatternUsage => {
f.write_str("Star pattern cannot be used here")

View File

@@ -635,93 +635,93 @@ impl fmt::Display for TokenKind {
TokenKind::TStringEnd => "TStringEnd",
TokenKind::IpyEscapeCommand => "IPython escape command",
TokenKind::Comment => "comment",
TokenKind::Question => "'?'",
TokenKind::Exclamation => "'!'",
TokenKind::Lpar => "'('",
TokenKind::Rpar => "')'",
TokenKind::Lsqb => "'['",
TokenKind::Rsqb => "']'",
TokenKind::Lbrace => "'{'",
TokenKind::Rbrace => "'}'",
TokenKind::Equal => "'='",
TokenKind::ColonEqual => "':='",
TokenKind::Dot => "'.'",
TokenKind::Colon => "':'",
TokenKind::Semi => "';'",
TokenKind::Comma => "','",
TokenKind::Rarrow => "'->'",
TokenKind::Plus => "'+'",
TokenKind::Minus => "'-'",
TokenKind::Star => "'*'",
TokenKind::DoubleStar => "'**'",
TokenKind::Slash => "'/'",
TokenKind::DoubleSlash => "'//'",
TokenKind::Percent => "'%'",
TokenKind::Vbar => "'|'",
TokenKind::Amper => "'&'",
TokenKind::CircumFlex => "'^'",
TokenKind::LeftShift => "'<<'",
TokenKind::RightShift => "'>>'",
TokenKind::Tilde => "'~'",
TokenKind::At => "'@'",
TokenKind::Less => "'<'",
TokenKind::Greater => "'>'",
TokenKind::EqEqual => "'=='",
TokenKind::NotEqual => "'!='",
TokenKind::LessEqual => "'<='",
TokenKind::GreaterEqual => "'>='",
TokenKind::PlusEqual => "'+='",
TokenKind::MinusEqual => "'-='",
TokenKind::StarEqual => "'*='",
TokenKind::DoubleStarEqual => "'**='",
TokenKind::SlashEqual => "'/='",
TokenKind::DoubleSlashEqual => "'//='",
TokenKind::PercentEqual => "'%='",
TokenKind::VbarEqual => "'|='",
TokenKind::AmperEqual => "'&='",
TokenKind::CircumflexEqual => "'^='",
TokenKind::LeftShiftEqual => "'<<='",
TokenKind::RightShiftEqual => "'>>='",
TokenKind::AtEqual => "'@='",
TokenKind::Ellipsis => "'...'",
TokenKind::False => "'False'",
TokenKind::None => "'None'",
TokenKind::True => "'True'",
TokenKind::And => "'and'",
TokenKind::As => "'as'",
TokenKind::Assert => "'assert'",
TokenKind::Async => "'async'",
TokenKind::Await => "'await'",
TokenKind::Break => "'break'",
TokenKind::Class => "'class'",
TokenKind::Continue => "'continue'",
TokenKind::Def => "'def'",
TokenKind::Del => "'del'",
TokenKind::Elif => "'elif'",
TokenKind::Else => "'else'",
TokenKind::Except => "'except'",
TokenKind::Finally => "'finally'",
TokenKind::For => "'for'",
TokenKind::From => "'from'",
TokenKind::Global => "'global'",
TokenKind::If => "'if'",
TokenKind::Import => "'import'",
TokenKind::In => "'in'",
TokenKind::Is => "'is'",
TokenKind::Lambda => "'lambda'",
TokenKind::Nonlocal => "'nonlocal'",
TokenKind::Not => "'not'",
TokenKind::Or => "'or'",
TokenKind::Pass => "'pass'",
TokenKind::Raise => "'raise'",
TokenKind::Return => "'return'",
TokenKind::Try => "'try'",
TokenKind::While => "'while'",
TokenKind::Match => "'match'",
TokenKind::Type => "'type'",
TokenKind::Case => "'case'",
TokenKind::With => "'with'",
TokenKind::Yield => "'yield'",
TokenKind::Question => "`?`",
TokenKind::Exclamation => "`!`",
TokenKind::Lpar => "`(`",
TokenKind::Rpar => "`)`",
TokenKind::Lsqb => "`[`",
TokenKind::Rsqb => "`]`",
TokenKind::Lbrace => "`{`",
TokenKind::Rbrace => "`}`",
TokenKind::Equal => "`=`",
TokenKind::ColonEqual => "`:=`",
TokenKind::Dot => "`.`",
TokenKind::Colon => "`:`",
TokenKind::Semi => "`;`",
TokenKind::Comma => "`,`",
TokenKind::Rarrow => "`->`",
TokenKind::Plus => "`+`",
TokenKind::Minus => "`-`",
TokenKind::Star => "`*`",
TokenKind::DoubleStar => "`**`",
TokenKind::Slash => "`/`",
TokenKind::DoubleSlash => "`//`",
TokenKind::Percent => "`%`",
TokenKind::Vbar => "`|`",
TokenKind::Amper => "`&`",
TokenKind::CircumFlex => "`^`",
TokenKind::LeftShift => "`<<`",
TokenKind::RightShift => "`>>`",
TokenKind::Tilde => "`~`",
TokenKind::At => "`@`",
TokenKind::Less => "`<`",
TokenKind::Greater => "`>`",
TokenKind::EqEqual => "`==`",
TokenKind::NotEqual => "`!=`",
TokenKind::LessEqual => "`<=`",
TokenKind::GreaterEqual => "`>=`",
TokenKind::PlusEqual => "`+=`",
TokenKind::MinusEqual => "`-=`",
TokenKind::StarEqual => "`*=`",
TokenKind::DoubleStarEqual => "`**=`",
TokenKind::SlashEqual => "`/=`",
TokenKind::DoubleSlashEqual => "`//=`",
TokenKind::PercentEqual => "`%=`",
TokenKind::VbarEqual => "`|=`",
TokenKind::AmperEqual => "`&=`",
TokenKind::CircumflexEqual => "`^=`",
TokenKind::LeftShiftEqual => "`<<=`",
TokenKind::RightShiftEqual => "`>>=`",
TokenKind::AtEqual => "`@=`",
TokenKind::Ellipsis => "`...`",
TokenKind::False => "`False`",
TokenKind::None => "`None`",
TokenKind::True => "`True`",
TokenKind::And => "`and`",
TokenKind::As => "`as`",
TokenKind::Assert => "`assert`",
TokenKind::Async => "`async`",
TokenKind::Await => "`await`",
TokenKind::Break => "`break`",
TokenKind::Class => "`class`",
TokenKind::Continue => "`continue`",
TokenKind::Def => "`def`",
TokenKind::Del => "`del`",
TokenKind::Elif => "`elif`",
TokenKind::Else => "`else`",
TokenKind::Except => "`except`",
TokenKind::Finally => "`finally`",
TokenKind::For => "`for`",
TokenKind::From => "`from`",
TokenKind::Global => "`global`",
TokenKind::If => "`if`",
TokenKind::Import => "`import`",
TokenKind::In => "`in`",
TokenKind::Is => "`is`",
TokenKind::Lambda => "`lambda`",
TokenKind::Nonlocal => "`nonlocal`",
TokenKind::Not => "`not`",
TokenKind::Or => "`or`",
TokenKind::Pass => "`pass`",
TokenKind::Raise => "`raise`",
TokenKind::Return => "`return`",
TokenKind::Try => "`try`",
TokenKind::While => "`while`",
TokenKind::Match => "`match`",
TokenKind::Type => "`type`",
TokenKind::Case => "`case`",
TokenKind::With => "`with`",
TokenKind::Yield => "`yield`",
};
f.write_str(value)
}

View File

@@ -131,7 +131,7 @@ Module(
|
1 | assert *x
2 | assert assert x
| ^^^^^^ Syntax Error: Expected an identifier, but found a keyword 'assert' that cannot be used here
| ^^^^^^ Syntax Error: Expected an identifier, but found a keyword `assert` that cannot be used here
3 | assert yield x
4 | assert x := 1
|

View File

@@ -148,7 +148,7 @@ Module(
|
1 | a = pass = c
| ^^^^ Syntax Error: Expected an identifier, but found a keyword 'pass' that cannot be used here
| ^^^^ Syntax Error: Expected an identifier, but found a keyword `pass` that cannot be used here
2 | a + b
3 | a = b = pass = c
|
@@ -158,6 +158,6 @@ Module(
1 | a = pass = c
2 | a + b
3 | a = b = pass = c
| ^^^^ Syntax Error: Expected an identifier, but found a keyword 'pass' that cannot be used here
| ^^^^ Syntax Error: Expected an identifier, but found a keyword `pass` that cannot be used here
4 | a + b
|

View File

@@ -181,7 +181,7 @@ Module(
|
1 | async class Foo: ...
| ^^^^^ Syntax Error: Expected 'def', 'with' or 'for' to follow 'async', found 'class'
| ^^^^^ Syntax Error: Expected `def`, `with` or `for` to follow `async`, found `class`
2 | async while test: ...
3 | async x = 1
|
@@ -190,7 +190,7 @@ Module(
|
1 | async class Foo: ...
2 | async while test: ...
| ^^^^^ Syntax Error: Expected 'def', 'with' or 'for' to follow 'async', found 'while'
| ^^^^^ Syntax Error: Expected `def`, `with` or `for` to follow `async`, found `while`
3 | async x = 1
4 | async async def foo(): ...
|
@@ -200,7 +200,7 @@ Module(
1 | async class Foo: ...
2 | async while test: ...
3 | async x = 1
| ^ Syntax Error: Expected 'def', 'with' or 'for' to follow 'async', found name
| ^ Syntax Error: Expected `def`, `with` or `for` to follow `async`, found name
4 | async async def foo(): ...
5 | async match test:
|
@@ -210,7 +210,7 @@ Module(
2 | async while test: ...
3 | async x = 1
4 | async async def foo(): ...
| ^^^^^ Syntax Error: Expected 'def', 'with' or 'for' to follow 'async', found 'async'
| ^^^^^ Syntax Error: Expected `def`, `with` or `for` to follow `async`, found `async`
5 | async match test:
6 | case _: ...
|
@@ -220,6 +220,6 @@ Module(
3 | async x = 1
4 | async async def foo(): ...
5 | async match test:
| ^^^^^ Syntax Error: Expected 'def', 'with' or 'for' to follow 'async', found 'match'
| ^^^^^ Syntax Error: Expected `def`, `with` or `for` to follow `async`, found `match`
6 | case _: ...
|

View File

@@ -245,7 +245,7 @@ Module(
3 | *x += 1
4 | pass += 1
5 | x += pass
| ^^^^ Syntax Error: Expected an identifier, but found a keyword 'pass' that cannot be used here
| ^^^^ Syntax Error: Expected an identifier, but found a keyword `pass` that cannot be used here
6 | (x + y) += 1
|

View File

@@ -121,7 +121,7 @@ Module(
|
1 | class Foo[T1, *T2(a, b):
| ^ Syntax Error: Expected ']', found '('
| ^ Syntax Error: Expected `]`, found `(`
2 | pass
3 | x = 10
|

View File

@@ -68,7 +68,7 @@ Module(
|
1 | call(**x := 1)
| ^^ Syntax Error: Expected ',', found ':='
| ^^ Syntax Error: Expected `,`, found `:=`
|

View File

@@ -61,5 +61,5 @@ Module(
|
1 | # The comma between the first two elements is expected in `parse_list_expression`.
2 | [0, 1 2]
| ^ Syntax Error: Expected ',', found int
| ^ Syntax Error: Expected `,`, found int
|

View File

@@ -77,7 +77,7 @@ Module(
|
1 | (async)
| ^^^^^ Syntax Error: Expected an identifier, but found a keyword 'async' that cannot be used here
| ^^^^^ Syntax Error: Expected an identifier, but found a keyword `async` that cannot be used here
2 | (x async x in iter)
|
@@ -85,5 +85,5 @@ Module(
|
1 | (async)
2 | (x async x in iter)
| ^ Syntax Error: Expected 'for', found name
| ^ Syntax Error: Expected `for`, found name
|

View File

@@ -169,7 +169,7 @@ Module(
|
1 | @def foo(): ...
| ^^^ Syntax Error: Expected an identifier, but found a keyword 'def' that cannot be used here
| ^^^ Syntax Error: Expected an identifier, but found a keyword `def` that cannot be used here
2 | @
3 | def foo(): ...
|

View File

@@ -161,7 +161,7 @@ Module(
|
1 | @x def foo(): ...
| ^^^ Syntax Error: Expected newline, found 'def'
| ^^^ Syntax Error: Expected newline, found `def`
2 | @x async def foo(): ...
3 | @x class Foo: ...
|
@@ -170,7 +170,7 @@ Module(
|
1 | @x def foo(): ...
2 | @x async def foo(): ...
| ^^^^^ Syntax Error: Expected newline, found 'async'
| ^^^^^ Syntax Error: Expected newline, found `async`
3 | @x class Foo: ...
|
@@ -179,5 +179,5 @@ Module(
1 | @x def foo(): ...
2 | @x async def foo(): ...
3 | @x class Foo: ...
| ^^^^^ Syntax Error: Expected newline, found 'class'
| ^^^^^ Syntax Error: Expected newline, found `class`
|

View File

@@ -238,7 +238,7 @@ Module(
3 | call(***x)
4 |
5 | call(**x := 1)
| ^^ Syntax Error: Expected ',', found ':='
| ^^ Syntax Error: Expected `,`, found `:=`
|

View File

@@ -61,5 +61,5 @@ Module(
|
1 | call(x y)
| ^ Syntax Error: Expected ',', found name
| ^ Syntax Error: Expected `,`, found name
|

View File

@@ -76,7 +76,7 @@ Module(
|
1 | call(
| ^ Syntax Error: Expected ')', found newline
| ^ Syntax Error: Expected `)`, found newline
2 |
3 | def foo():
4 | pass

View File

@@ -85,7 +85,7 @@ Module(
|
1 | call(x
| ^ Syntax Error: Expected ')', found newline
| ^ Syntax Error: Expected `)`, found newline
2 |
3 | def foo():
4 | pass

View File

@@ -85,7 +85,7 @@ Module(
|
1 | call(x,
| ^ Syntax Error: Expected ')', found newline
| ^ Syntax Error: Expected `)`, found newline
2 |
3 | def foo():
4 | pass

View File

@@ -175,7 +175,7 @@ Module(
|
6 | # Same here as well, `not` without `in` is considered to be a unary operator
7 | x not is y
| ^^ Syntax Error: Expected an identifier, but found a keyword 'is' that cannot be used here
| ^^ Syntax Error: Expected an identifier, but found a keyword `is` that cannot be used here
|

View File

@@ -544,7 +544,7 @@ Module(
2 | # the ones which are higher than that.
3 |
4 | {**x := 1}
| ^^ Syntax Error: Expected ',', found ':='
| ^^ Syntax Error: Expected `,`, found `:=`
5 | {a: 1, **x if True else y}
6 | {**lambda x: x, b: 2}
|
@@ -554,7 +554,7 @@ Module(
2 | # the ones which are higher than that.
3 |
4 | {**x := 1}
| ^ Syntax Error: Expected ':', found '}'
| ^ Syntax Error: Expected `:`, found `}`
5 | {a: 1, **x if True else y}
6 | {**lambda x: x, b: 2}
|

View File

@@ -134,7 +134,7 @@ Module(
2 | # it's actually a comprehension.
3 |
4 | {**x: y for x, y in data}
| ^^^ Syntax Error: Expected ':', found 'for'
| ^^^ Syntax Error: Expected `:`, found `for`
5 |
6 | # TODO(dhruvmanila): This test case fails because there's no way to represent `**y`
|
@@ -144,7 +144,7 @@ Module(
2 | # it's actually a comprehension.
3 |
4 | {**x: y for x, y in data}
| ^ Syntax Error: Expected ',', found name
| ^ Syntax Error: Expected `,`, found name
5 |
6 | # TODO(dhruvmanila): This test case fails because there's no way to represent `**y`
|
@@ -154,7 +154,7 @@ Module(
2 | # it's actually a comprehension.
3 |
4 | {**x: y for x, y in data}
| ^ Syntax Error: Expected ':', found ','
| ^ Syntax Error: Expected `:`, found `,`
5 |
6 | # TODO(dhruvmanila): This test case fails because there's no way to represent `**y`
|
@@ -164,7 +164,7 @@ Module(
2 | # it's actually a comprehension.
3 |
4 | {**x: y for x, y in data}
| ^ Syntax Error: Expected ':', found '}'
| ^ Syntax Error: Expected `:`, found `}`
5 |
6 | # TODO(dhruvmanila): This test case fails because there's no way to represent `**y`
|

View File

@@ -86,7 +86,7 @@ Module(
1 | {x:
2 |
3 | def foo():
| ^^^ Syntax Error: Expected an identifier, but found a keyword 'def' that cannot be used here
| ^^^ Syntax Error: Expected an identifier, but found a keyword `def` that cannot be used here
4 | pass
|
@@ -95,7 +95,7 @@ Module(
1 | {x:
2 |
3 | def foo():
| ^^^ Syntax Error: Expected ',', found name
| ^^^ Syntax Error: Expected `,`, found name
4 | pass
|
@@ -103,7 +103,7 @@ Module(
|
3 | def foo():
4 | pass
| ^^^^ Syntax Error: Expected an identifier, but found a keyword 'pass' that cannot be used here
| ^^^^ Syntax Error: Expected an identifier, but found a keyword `pass` that cannot be used here
|

View File

@@ -85,7 +85,7 @@ Module(
|
1 | {x: 1,
| ^ Syntax Error: Expected '}', found newline
| ^ Syntax Error: Expected `}`, found newline
2 |
3 | def foo():
4 | pass

View File

@@ -149,7 +149,7 @@ Module(
1 | # Unparenthesized named expression not allowed in key
2 |
3 | {x := 1: y, z := 2: a}
| ^^ Syntax Error: Expected ':', found ':='
| ^^ Syntax Error: Expected `:`, found `:=`
4 |
5 | x + y
|

View File

@@ -145,7 +145,7 @@ Module(
1 | # Unparenthesized named expression not allowed in value
2 |
3 | {x: y := 1, z: a := 2}
| ^^ Syntax Error: Expected ',', found ':='
| ^^ Syntax Error: Expected `,`, found `:=`
4 |
5 | x + y
|
@@ -155,7 +155,7 @@ Module(
1 | # Unparenthesized named expression not allowed in value
2 |
3 | {x: y := 1, z: a := 2}
| ^ Syntax Error: Expected ':', found ','
| ^ Syntax Error: Expected `:`, found `,`
4 |
5 | x + y
|
@@ -165,7 +165,7 @@ Module(
1 | # Unparenthesized named expression not allowed in value
2 |
3 | {x: y := 1, z: a := 2}
| ^^ Syntax Error: Expected ',', found ':='
| ^^ Syntax Error: Expected `,`, found `:=`
4 |
5 | x + y
|
@@ -175,7 +175,7 @@ Module(
1 | # Unparenthesized named expression not allowed in value
2 |
3 | {x: y := 1, z: a := 2}
| ^ Syntax Error: Expected ':', found '}'
| ^ Syntax Error: Expected `:`, found `}`
4 |
5 | x + y
|

View File

@@ -504,7 +504,7 @@ Module(
|
9 | # Missing comma
10 | {1: 2 3: 4}
| ^ Syntax Error: Expected ',', found int
| ^ Syntax Error: Expected `,`, found int
11 |
12 | # No value
|

View File

@@ -338,7 +338,7 @@ Module(
7 | lambda a, *a: 1
8 |
9 | lambda a, *, **a: 1
| ^^^ Syntax Error: Expected one or more keyword parameter after '*' separator
| ^^^ Syntax Error: Expected one or more keyword parameter after `*` separator
|

View File

@@ -85,7 +85,7 @@ Module(
2 | # token starts a statement.
3 |
4 | [1, 2
| ^ Syntax Error: Expected ']', found newline
| ^ Syntax Error: Expected `]`, found newline
5 |
6 | def foo():
7 | pass

View File

@@ -305,7 +305,7 @@ Module(
|
9 | # Missing comma
10 | [1 2]
| ^ Syntax Error: Expected ',', found int
| ^ Syntax Error: Expected `,`, found int
11 |
12 | # Dictionary element in a list
|

View File

@@ -84,7 +84,7 @@ Module(
3 | (x :=
4 |
5 | def foo():
| ^^^ Syntax Error: Expected an identifier, but found a keyword 'def' that cannot be used here
| ^^^ Syntax Error: Expected an identifier, but found a keyword `def` that cannot be used here
6 | pass
|
@@ -93,7 +93,7 @@ Module(
3 | (x :=
4 |
5 | def foo():
| ^^^ Syntax Error: Expected ')', found name
| ^^^ Syntax Error: Expected `)`, found name
6 | pass
|
@@ -101,7 +101,7 @@ Module(
|
5 | def foo():
6 | pass
| ^^^^ Syntax Error: Expected an identifier, but found a keyword 'pass' that cannot be used here
| ^^^^ Syntax Error: Expected an identifier, but found a keyword `pass` that cannot be used here
|

View File

@@ -142,14 +142,14 @@ Module(
|
1 | (*x for x in y)
2 | (x := 1, for x in y)
| ^^^ Syntax Error: Expected ')', found 'for'
| ^^^ Syntax Error: Expected `)`, found `for`
|
|
1 | (*x for x in y)
2 | (x := 1, for x in y)
| ^ Syntax Error: Expected ':', found ')'
| ^ Syntax Error: Expected `:`, found `)`
|

View File

@@ -86,7 +86,7 @@ Module(
2 | # token starts a statement.
3 |
4 | (1, 2
| ^ Syntax Error: Expected ')', found newline
| ^ Syntax Error: Expected `)`, found newline
5 |
6 | def foo():
7 | pass

View File

@@ -315,7 +315,7 @@ Module(
|
9 | # Missing comma
10 | (1 2)
| ^ Syntax Error: Expected ')', found int
| ^ Syntax Error: Expected `)`, found int
11 |
12 | # Dictionary element in a list
|
@@ -343,7 +343,7 @@ Module(
|
12 | # Dictionary element in a list
13 | (1: 2)
| ^ Syntax Error: Expected ')', found ':'
| ^ Syntax Error: Expected `)`, found `:`
14 |
15 | # Missing expression
|
@@ -390,7 +390,7 @@ Module(
16 | (1, x + )
17 |
18 | (1; 2)
| ^ Syntax Error: Expected ')', found ';'
| ^ Syntax Error: Expected `)`, found `;`
19 |
20 | # Unparenthesized named expression is not allowed
|
@@ -420,5 +420,5 @@ Module(
|
20 | # Unparenthesized named expression is not allowed
21 | x, y := 2, z
| ^^ Syntax Error: Expected ',', found ':='
| ^^ Syntax Error: Expected `,`, found `:=`
|

View File

@@ -1542,5 +1542,5 @@ Module(
18 | *x if True else y, z, *x if True else y
19 | *lambda x: x, z, *lambda x: x
20 | *x := 2, z, *x := 2
| ^^ Syntax Error: Expected ',', found ':='
| ^^ Syntax Error: Expected `,`, found `:=`
|

View File

@@ -84,7 +84,7 @@ Module(
2 | # token starts a statement.
3 |
4 | {1, 2
| ^ Syntax Error: Expected '}', found newline
| ^ Syntax Error: Expected `}`, found newline
5 |
6 | def foo():
7 | pass

View File

@@ -302,7 +302,7 @@ Module(
|
11 | # Missing comma
12 | {1 2}
| ^ Syntax Error: Expected ',', found int
| ^ Syntax Error: Expected `,`, found int
13 |
14 | # Dictionary element in a list
|

View File

@@ -95,7 +95,7 @@ Module(
1 | x[::
2 |
3 | def foo():
| ^^^ Syntax Error: Expected an identifier, but found a keyword 'def' that cannot be used here
| ^^^ Syntax Error: Expected an identifier, but found a keyword `def` that cannot be used here
4 | pass
|
@@ -104,7 +104,7 @@ Module(
1 | x[::
2 |
3 | def foo():
| ^^^ Syntax Error: Expected ']', found name
| ^^^ Syntax Error: Expected `]`, found name
4 | pass
|
@@ -112,7 +112,7 @@ Module(
|
3 | def foo():
4 | pass
| ^^^^ Syntax Error: Expected an identifier, but found a keyword 'pass' that cannot be used here
| ^^^^ Syntax Error: Expected an identifier, but found a keyword `pass` that cannot be used here
|

View File

@@ -125,5 +125,5 @@ Module(
2 | yield x := 1
3 |
4 | yield 1, x := 2, 3
| ^^ Syntax Error: Expected ',', found ':='
| ^^ Syntax Error: Expected `,`, found `:=`
|

View File

@@ -117,7 +117,7 @@ Module(
|
1 | f"{lambda x: x}"
| ^^ Syntax Error: f-string: expecting '}'
| ^^ Syntax Error: f-string: expecting `}`
|

View File

@@ -267,7 +267,7 @@ Module(
|
1 | f"{"
2 | f"{foo!r"
| ^ Syntax Error: f-string: expecting '}'
| ^ Syntax Error: f-string: expecting `}`
3 | f"{foo="
4 | f"{"
|
@@ -277,7 +277,7 @@ Module(
1 | f"{"
2 | f"{foo!r"
3 | f"{foo="
| ^ Syntax Error: f-string: expecting '}'
| ^ Syntax Error: f-string: expecting `}`
4 | f"{"
5 | f"""{"""
|

View File

@@ -146,7 +146,7 @@ Module(
|
1 | f"hello {x:"
| ^ Syntax Error: f-string: expecting '}'
| ^ Syntax Error: f-string: expecting `}`
2 | f"hello {x:.3f"
|
@@ -154,5 +154,5 @@ Module(
|
1 | f"hello {x:"
2 | f"hello {x:.3f"
| ^ Syntax Error: f-string: expecting '}'
| ^ Syntax Error: f-string: expecting `}`
|

View File

@@ -192,7 +192,7 @@ Module(
1 | for x in *a and b: ...
2 | for x in yield a: ...
3 | for target in x := 1: ...
| ^^ Syntax Error: Expected ':', found ':='
| ^^ Syntax Error: Expected `:`, found `:=`
|

View File

@@ -498,7 +498,7 @@ Module(
4 | for *x | y in z: ...
5 | for await x in z: ...
6 | for yield x in y: ...
| ^ Syntax Error: Expected 'in', found ':'
| ^ Syntax Error: Expected `in`, found `:`
7 | for [x, 1, y, *["a"]] in z: ...
|

View File

@@ -94,7 +94,7 @@ Module(
|
1 | for a b: ...
| ^ Syntax Error: Expected 'in', found name
| ^ Syntax Error: Expected `in`, found name
2 | for a: ...
|
@@ -102,5 +102,5 @@ Module(
|
1 | for a b: ...
2 | for a: ...
| ^ Syntax Error: Expected 'in', found ':'
| ^ Syntax Error: Expected `in`, found `:`
|

View File

@@ -56,11 +56,11 @@ Module(
|
1 | for in x: ...
| ^^ Syntax Error: Expected an identifier, but found a keyword 'in' that cannot be used here
| ^^ Syntax Error: Expected an identifier, but found a keyword `in` that cannot be used here
|
|
1 | for in x: ...
| ^ Syntax Error: Expected 'in', found name
| ^ Syntax Error: Expected `in`, found name
|

View File

@@ -166,7 +166,7 @@ Module(
|
1 | from x import a.
| ^ Syntax Error: Expected ',', found '.'
| ^ Syntax Error: Expected `,`, found `.`
2 | from x import a.b
3 | from x import a, b.c, d, e.f, g
|
@@ -175,7 +175,7 @@ Module(
|
1 | from x import a.
2 | from x import a.b
| ^ Syntax Error: Expected ',', found '.'
| ^ Syntax Error: Expected `,`, found `.`
3 | from x import a, b.c, d, e.f, g
|
@@ -184,7 +184,7 @@ Module(
1 | from x import a.
2 | from x import a.b
3 | from x import a, b.c, d, e.f, g
| ^ Syntax Error: Expected ',', found '.'
| ^ Syntax Error: Expected `,`, found `.`
|
@@ -192,5 +192,5 @@ Module(
1 | from x import a.
2 | from x import a.b
3 | from x import a, b.c, d, e.f, g
| ^ Syntax Error: Expected ',', found '.'
| ^ Syntax Error: Expected `,`, found `.`
|

View File

@@ -152,7 +152,7 @@ Module(
|
1 | from x import (a, b
| ^ Syntax Error: Expected ')', found newline
| ^ Syntax Error: Expected `)`, found newline
2 | 1 + 1
3 | from x import (a, b,
4 | 2 + 2
@@ -163,6 +163,6 @@ Module(
1 | from x import (a, b
2 | 1 + 1
3 | from x import (a, b,
| ^ Syntax Error: Expected ')', found newline
| ^ Syntax Error: Expected `)`, found newline
4 | 2 + 2
|

View File

@@ -234,7 +234,7 @@ Module(
|
1 | def foo(a: int, b:
| ^ Syntax Error: Expected ')', found newline
| ^ Syntax Error: Expected `)`, found newline
2 | def foo():
3 | return 42
4 | def foo(a: int, b: str
@@ -254,7 +254,7 @@ Module(
3 | return 42
4 | def foo(a: int, b: str
5 | x = 10
| ^ Syntax Error: Expected ',', found name
| ^ Syntax Error: Expected `,`, found name
|

View File

@@ -163,7 +163,7 @@ Module(
|
1 | def foo[T1, *T2(a, b):
| ^ Syntax Error: Expected ']', found '('
| ^ Syntax Error: Expected `]`, found `(`
2 | return a + b
3 | x = 10
|

View File

@@ -79,7 +79,7 @@ Module(
1 | if x:
2 | pass
3 | elif y
| ^ Syntax Error: Expected ':', found newline
| ^ Syntax Error: Expected `:`, found newline
4 | pass
5 | else:
6 | pass

View File

@@ -82,7 +82,7 @@ Module(
|
1 | if x
| ^ Syntax Error: Expected ':', found newline
| ^ Syntax Error: Expected `:`, found newline
2 | if x
3 | pass
4 | a = 1
@@ -101,7 +101,7 @@ Module(
|
1 | if x
2 | if x
| ^ Syntax Error: Expected ':', found newline
| ^ Syntax Error: Expected `:`, found newline
3 | pass
4 | a = 1
|

View File

@@ -80,6 +80,6 @@ Module(
|
1 | match [1, 2]
| ^ Syntax Error: Expected ':', found newline
| ^ Syntax Error: Expected `:`, found newline
2 | case _: ...
|

View File

@@ -61,7 +61,7 @@ Module(
|
1 | match foo: case _: ...
| ^^^^ Syntax Error: Expected newline, found 'case'
| ^^^^ Syntax Error: Expected newline, found `case`
|

View File

@@ -326,7 +326,7 @@ Module(
|
1 | if True: pass elif False: pass else: pass
| ^^^^ Syntax Error: Expected newline, found 'elif'
| ^^^^ Syntax Error: Expected newline, found `elif`
2 | if True: pass; elif False: pass; else: pass
3 | for x in iter: break else: pass
|
@@ -334,7 +334,7 @@ Module(
|
1 | if True: pass elif False: pass else: pass
| ^^^^ Syntax Error: Expected newline, found 'else'
| ^^^^ Syntax Error: Expected newline, found `else`
2 | if True: pass; elif False: pass; else: pass
3 | for x in iter: break else: pass
|
@@ -343,7 +343,7 @@ Module(
|
1 | if True: pass elif False: pass else: pass
2 | if True: pass; elif False: pass; else: pass
| ^^^^ Syntax Error: Expected newline, found 'elif'
| ^^^^ Syntax Error: Expected newline, found `elif`
3 | for x in iter: break else: pass
4 | for x in iter: break; else: pass
|
@@ -352,7 +352,7 @@ Module(
|
1 | if True: pass elif False: pass else: pass
2 | if True: pass; elif False: pass; else: pass
| ^^^^ Syntax Error: Expected newline, found 'else'
| ^^^^ Syntax Error: Expected newline, found `else`
3 | for x in iter: break else: pass
4 | for x in iter: break; else: pass
|
@@ -362,7 +362,7 @@ Module(
1 | if True: pass elif False: pass else: pass
2 | if True: pass; elif False: pass; else: pass
3 | for x in iter: break else: pass
| ^^^^ Syntax Error: Expected newline, found 'else'
| ^^^^ Syntax Error: Expected newline, found `else`
4 | for x in iter: break; else: pass
5 | try: pass except exc: pass else: pass finally: pass
|
@@ -372,7 +372,7 @@ Module(
2 | if True: pass; elif False: pass; else: pass
3 | for x in iter: break else: pass
4 | for x in iter: break; else: pass
| ^^^^ Syntax Error: Expected newline, found 'else'
| ^^^^ Syntax Error: Expected newline, found `else`
5 | try: pass except exc: pass else: pass finally: pass
6 | try: pass; except exc: pass; else: pass; finally: pass
|
@@ -382,7 +382,7 @@ Module(
3 | for x in iter: break else: pass
4 | for x in iter: break; else: pass
5 | try: pass except exc: pass else: pass finally: pass
| ^^^^^^ Syntax Error: Expected newline, found 'except'
| ^^^^^^ Syntax Error: Expected newline, found `except`
6 | try: pass; except exc: pass; else: pass; finally: pass
|
@@ -391,7 +391,7 @@ Module(
3 | for x in iter: break else: pass
4 | for x in iter: break; else: pass
5 | try: pass except exc: pass else: pass finally: pass
| ^^^^ Syntax Error: Expected newline, found 'else'
| ^^^^ Syntax Error: Expected newline, found `else`
6 | try: pass; except exc: pass; else: pass; finally: pass
|
@@ -400,7 +400,7 @@ Module(
3 | for x in iter: break else: pass
4 | for x in iter: break; else: pass
5 | try: pass except exc: pass else: pass finally: pass
| ^^^^^^^ Syntax Error: Expected newline, found 'finally'
| ^^^^^^^ Syntax Error: Expected newline, found `finally`
6 | try: pass; except exc: pass; else: pass; finally: pass
|
@@ -409,7 +409,7 @@ Module(
4 | for x in iter: break; else: pass
5 | try: pass except exc: pass else: pass finally: pass
6 | try: pass; except exc: pass; else: pass; finally: pass
| ^^^^^^ Syntax Error: Expected newline, found 'except'
| ^^^^^^ Syntax Error: Expected newline, found `except`
|
@@ -417,7 +417,7 @@ Module(
4 | for x in iter: break; else: pass
5 | try: pass except exc: pass else: pass finally: pass
6 | try: pass; except exc: pass; else: pass; finally: pass
| ^^^^ Syntax Error: Expected newline, found 'else'
| ^^^^ Syntax Error: Expected newline, found `else`
|
@@ -425,5 +425,5 @@ Module(
4 | for x in iter: break; else: pass
5 | try: pass except exc: pass else: pass finally: pass
6 | try: pass; except exc: pass; else: pass; finally: pass
| ^^^^^^^ Syntax Error: Expected newline, found 'finally'
| ^^^^^^^ Syntax Error: Expected newline, found `finally`
|

View File

@@ -238,7 +238,7 @@ Module(
1 | # even after 3.9, an unparenthesized named expression is not allowed in a slice
2 | lst[x:=1:-1]
3 | lst[1:x:=1]
| ^^ Syntax Error: Expected ']', found ':='
| ^^ Syntax Error: Expected `]`, found `:=`
4 | lst[1:3:x:=1]
|
@@ -265,7 +265,7 @@ Module(
2 | lst[x:=1:-1]
3 | lst[1:x:=1]
4 | lst[1:3:x:=1]
| ^^ Syntax Error: Expected ']', found ':='
| ^^ Syntax Error: Expected `]`, found `:=`
|

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