Compare commits

..

32 Commits

Author SHA1 Message Date
Charlie Marsh
8fbec8e6a2 Update docs to match updated logo and color palette 2023-06-21 20:48:29 -04:00
Charlie Marsh
ac146e11f0 Allow typing.Final for mutable-class-default annotations (RUF012) (#5274)
## Summary

See: https://github.com/astral-sh/ruff/issues/5243.
2023-06-22 00:24:53 +00:00
Charlie Marsh
1229600e1d Ignore Pydantic classes when evaluating mutable-class-default (RUF012) (#5273)
Closes https://github.com/astral-sh/ruff/issues/5272.
2023-06-21 23:59:44 +00:00
Micha Reiser
ccf34aae8c Format Attribute Expression (#5259) 2023-06-21 21:33:53 +00:00
Tom Kuson
341b12d918 Complete documentation for Ruff-specific rules (#5262)
## Summary

Completes the documentation for the Ruff-specific ruleset. Related to
#2646.

## Test Plan

`python scripts/check_docs_formatted.py`
2023-06-21 21:30:44 +00:00
Micha Reiser
3d7411bfaf Use trait for labels instead of TypeId (#5270) 2023-06-21 22:26:09 +01:00
David Szotten
1eccbbb60e Format StmtFor (#5163)
<!--
Thank you for contributing to Ruff! 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?
- Does this pull request include references to any relevant issues?
-->

## Summary

format StmtFor

still trying to learn how to help out with the formatter. trying
something slightly more advanced than [break](#5158)

mostly copied form StmtWhile

## Test Plan

snapshots
2023-06-21 23:00:31 +02:00
Charlie Marsh
e71f044f0d Avoid including nursery rules in linter-level selectors (#5268)
## Summary

Ensures that `--select PL` and `--select PLC` don't include `PLC1901`.
Previously, `--select PL` _did_, because it's a "linter-level selector"
(`--select PLC` is viewed as selecting the `C` prefix from `PL`), and we
were missing this filtering path.
2023-06-21 20:11:40 +00:00
James Berry
f194572be8 Remove visit_arg_with_default (#5265)
## Summary

This is a follow up to #5221. Turns out it was easy to restructure the
visitor to get the right order, I'm just dumb 🤷‍♂️ I've
removed `visit_arg_with_default` entirely from the `Visitor`, although
it still exists as part of `preorder::Visitor`.
2023-06-21 16:00:24 -04:00
Charlie Marsh
62e2c46f98 Move compare-to-empty-string to nursery (#5264)
## Summary

This rule has too many false positives. It has parity with the Pylint
version, but the Pylint version is part of an
[extension](https://pylint.readthedocs.io/en/stable/user_guide/messages/convention/compare-to-empty-string.html),
and so requires explicit opt-in.

I'm moving this rule to the nursery to require explicit opt-in, as with
Pylint.

Closes #4282.
2023-06-21 19:47:02 +00:00
konstin
9419d3f9c8 Special ExprTuple formatting option for for-loops (#5175)
## Motivation

While black keeps parentheses nearly everywhere, the notable exception
is in the body of for loops:
```python
for (a, b) in x:
    pass
```
becomes
```python
for a, b in x:
    pass
```

This currently blocks #5163, which this PR should unblock.

## Solution

This changes the `ExprTuple` formatting option to include one additional
option that removes the parentheses when not using magic trailing comma
and not breaking. It is supposed to be used through
```rust
#[derive(Debug)]
struct ExprTupleWithoutParentheses<'a>(&'a Expr);

impl Format<PyFormatContext<'_>> for ExprTupleWithoutParentheses<'_> {
    fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
        match self.0 {
            Expr::Tuple(expr_tuple) => expr_tuple
                .format()
                .with_options(TupleParentheses::StripInsideForLoop)
                .fmt(f),
            other => other.format().with_options(Parenthesize::IfBreaks).fmt(f),
        }
    }
}
```


## Testing

The for loop formatting isn't merged due to missing this (and i didn't
want to create more git weirdness across two people), but I've confirmed
that when applying this to while loops instead of for loops, then
```rust
        write!(
            f,
            [
                text("while"),
                space(),
                ExprTupleWithoutParentheses(test.as_ref()),
                text(":"),
                trailing_comments(trailing_condition_comments),
                block_indent(&body.format())
            ]
        )?;
```
makes
```python
while (a, b):
    pass

while (
    ajssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssa,
    b,
):
    pass

while (a,b,):
    pass
```
formatted as
```python
while a, b:
    pass

while (
    ajssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssa,
    b,
):
    pass

while (
    a,
    b,
):
    pass
```
2023-06-21 21:17:47 +02:00
James Berry
9b5fb8f38f Fix AST visitor traversal order (#5221)
## Summary

According to the AST visitor documentation, the AST visitor "visits all
nodes in the AST recursively in evaluation-order". However, the current
traversal fails to meet this specification in a few places.

### Function traversal

```python
order = []
@(order.append("decorator") or (lambda x: x))
def f(
    posonly: order.append("posonly annotation") = order.append("posonly default"),
    /,
    arg: order.append("arg annotation") = order.append("arg default"),
    *args: order.append("vararg annotation"),
    kwarg: order.append("kwarg annotation") = order.append("kwarg default"),
    **kwargs: order.append("kwarg annotation")
) -> order.append("return annotation"):
    pass
print(order)
```

Executing the above snippet using CPython 3.10.6 prints the following
result (formatted for readability):
```python
[
    'decorator',
    'posonly default',
    'arg default',
    'kwarg default',
    'arg annotation',
    'posonly annotation',
    'vararg annotation',
    'kwarg annotation',
    'kwarg annotation',
    'return annotation',
]
```

Here we can see that decorators are evaluated first, followed by
argument defaults, and annotations are last. The current traversal of a
function's AST does not align with this order.

### Annotated assignment traversal
```python
order = []
x: order.append("annotation") = order.append("expression")
print(order)
```

Executing the above snippet using CPython 3.10.6 prints the following
result:
```python
['expression', 'annotation']
```

Here we can see that an annotated assignments annotation gets evaluated
after the assignment's expression. The current traversal of an annotated
assignment's AST does not align with this order.

## Why?

I'm slowly working on #3946 and porting over some of the logic and tests
from ssort. ssort is very sensitive to AST traversal order, so ensuring
the utmost correctness here is important.

## Test Plan

There doesn't seem to be existing tests for the AST visitor, so I didn't
bother adding tests for these very subtle changes. However, this
behavior will be captured in the tests for the PR which addresses #3946.
2023-06-21 14:40:58 -04:00
konstin
d7c7484618 Format function argument separator comments (#5211)
## Summary

This is a complete rewrite of the handling of `/` and `*` comment
handling in function signatures. The key problem is that slash and star
don't have a note. We now parse out the positions of slash and star and
their respective preceding and following note. I've left code comments
for each possible case of function signature structure and comment
placement

## Test Plan

I extended the function statement fixtures with cases that i found. If
you have more weird edge cases your input would be appreciated.
2023-06-21 17:56:47 +00:00
konstin
bc63cc9b3c Fix remaining CPython formatter errors except for function argument separator comments (#5210)
## Summary

This fixes two problems discovered when trying to format the cpython
repo with `cargo run --bin ruff_dev -- check-formatter-stability
projects/cpython`:

The first is to ignore try/except trailing comments for now since they
lead to unstable formatting on the dummy.

The second is to avoid dropping trailing if comments through placement:
This changes the placement to keep a comment trailing an if-elif or
if-elif-else to keep the comment a trailing comment on the entire if.
Previously the last comment would have been lost.
```python
if "first if":
    pass
elif "first elif":
    pass
```

The last remaining problem in cpython so far is function signature
argument separator comment placement which is its own PR on top of this.

## Test Plan

I added test fixtures of minimized examples with links back to the
original cpython location
2023-06-21 19:45:53 +02:00
Charlie Marsh
bf1a94ee54 Initialize caches for packages and standalone files (#5237)
## Summary

While fixing https://github.com/astral-sh/ruff/pull/5233, I noticed that
in FastAPI, 343 out of 823 files weren't hitting the cache. It turns out
these are standalone files in the documentation that lack a "package
root". Later, when looking up the cache entries, we fallback to the
package directory.

This PR ensures that we initialize the cache for both kinds of files:
those that are in a package, and those that aren't.

The total size of the FastAPI cache for me is now 388K. I also suspect
that this approach is much faster than as initially written, since
before, we were probably initializing one cache per _directory_.

## Test Plan

Ran `cargo run -p ruff_cli -- check ../fastapi --verbose`; verified
that, on second execution, there were no "Checking" entries in the logs.
2023-06-21 17:29:09 +00:00
Dhruv Manilawala
c792c10eaa Add support for nested quoted annotations in RUF013 (#5254)
## Summary

This is a follow up on #5235 to add support for nested quoted
annotations for RUF013.

## Test Plan

`cargo test`
2023-06-21 17:25:27 +00:00
Evan Rittenhouse
f9ffb3d50d Add Applicability to pylint (#5251) 2023-06-21 17:22:01 +00:00
Evan Rittenhouse
2b76d88bd3 Add Applicability to pandas_vet (#5252) 2023-06-21 17:12:47 +00:00
Evan Rittenhouse
41ef17b007 Add Applicability to pyflakes (#5253) 2023-06-21 17:04:55 +00:00
Charlie Marsh
0aa21277c6 Improve documentation for overlong-line rules (#5260)
Closes https://github.com/astral-sh/ruff/issues/5248.
2023-06-21 17:02:20 +00:00
Charlie Marsh
ecf61d49fa Restore existing bindings when unbinding caught exceptions (#5256)
## Summary

In the latest release, we made some improvements to the semantic model,
but our modifications to exception-unbinding are causing some
false-positives. For example:

```py
try:
    v = 3
except ImportError as v:
    print(v)
else:
    print(v)
```

In the latest release, we started unbinding `v` after the `except`
handler. (We used to restore the existing binding, the `v = 3`, but this
was quite complicated.) Because we don't have full branch analysis, we
can't then know that `v` is still bound in the `else` branch.

The solution here modifies `resolve_read` to skip-lookup when hitting
unbound exceptions. So when store the "unbind" for `except ImportError
as v`, we save the binding that it shadowed `v = 3`, and skip to that.

Closes #5249.

Closes #5250.
2023-06-21 12:53:58 -04:00
Charlie Marsh
d99b3bf661 Add some projects to the ecosystem CI check (#5258) 2023-06-21 12:42:58 -04:00
Micha Reiser
e47aa468d5 Format Identifier (#5255) 2023-06-21 17:35:37 +02:00
konstin
6155fd647d Format Slice Expressions (#5047)
This formats slice expressions and subscript expressions.

Spaces around the colons follows the same rules as black
(https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#slices):
```python
e00 = "e"[:]
e01 = "e"[:1]
e02 = "e"[: a()]
e10 = "e"[1:]
e11 = "e"[1:1]
e12 = "e"[1 : a()]
e20 = "e"[a() :]
e21 = "e"[a() : 1]
e22 = "e"[a() : a()]
e200 = "e"[a() : :]
e201 = "e"[a() :: 1]
e202 = "e"[a() :: a()]
e210 = "e"[a() : 1 :]
```

Comment placement is different due to our very different infrastructure.
If we have explicit bounds (e.g. `x[1:2]`) all comments get assigned as
leading or trailing to the bound expression. If a bound is missing
`[:]`, comments get marked as dangling and placed in the same section as
they were originally in:
```python
x = "x"[ # a
      # b
    :  # c
      # d
]
```
to
```python
x = "x"[
    # a
    # b
    :
    # c
    # d
]
```
Except for the potential trailing end-of-line comments, all comments get
formatted on their own line. This can be improved by keeping end-of-line
comments after the opening bracket or after a colon as such but the
changes were already complex enough.

I added tests for comment placement and spaces.
2023-06-21 15:09:39 +00:00
Charlie Marsh
4634560c80 Ensure release tagging has access to repo clone (#5240)
## Summary

The [release
failed](https://github.com/astral-sh/ruff/actions/runs/5329733171/jobs/9656004063),
but late enough that I was able to do the remaining steps manually. The
issue here is that the tagging step requires that we clone the repo. I
split the upload (to PyPI), tagging (in Git), and publishing (to GitHub
Releases) phases into their own steps, since they need different
resources + permissions anyway.
2023-06-21 10:24:33 -04:00
Charlie Marsh
10885d09a1 Add support for top-level quoted annotations in RUF013 (#5235)
## Summary

This PR adds support for autofixing annotations like:

```python
def f(x: "int" = None):
    ...
```

However, we don't yet support nested quotes, like:

```python
def f(x: Union["int", "str"] = None):
    ...
```

Closes #5231.
2023-06-21 10:23:37 -04:00
konstin
44156f6962 Improve debuggability of place_comment (#5209)
## Summary

I found it hard to figure out which function decides placement for a
specific comment. An explicit loop makes this easier to debug

## Test Plan

There should be no functional changes, no changes to the formatting of
the fixtures.
2023-06-21 09:52:13 +00:00
konstin
f551c9aad2 Unify benchmarking and profiling docs (#5145)
This moves all docs about benchmarking and profiling into
CONTRIBUTING.md by moving the readme of `ruff_benchmark` and adding more
information on profiling.

We need to somehow consolidate that documentation, but i'm not convinced
that this is the best way (i tried subpages in mkdocs, but that didn't
seem good either), so i'm happy to take suggestions.
2023-06-21 09:39:56 +00:00
Micha Reiser
653dbb6d17 Format BoolOp (#4986) 2023-06-21 09:27:57 +00:00
konstin
db301c14bd Consistently name comment own line/end-of-line line_position() (#5215)
## Summary

Previously, `DecoratedComment` used `text_position()` and
`SourceComment` used `position()`. This PR unifies this to
`line_position` everywhere.

## Test Plan

This is a rename refactoring.
2023-06-21 11:04:56 +02:00
Micha Reiser
1336ca601b Format UnaryExpr
<!--
Thank you for contributing to Ruff! 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?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR adds basic formatting for unary expressions.

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

## Test Plan

I added a new `unary.py` with custom test cases
2023-06-21 10:09:47 +02:00
Micha Reiser
3973836420 Correctly handle left/right breaking of binary expression
<!--
Thank you for contributing to Ruff! 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?
- Does this pull request include references to any relevant issues?
-->

## Summary
Black supports for layouts when it comes to breaking binary expressions:

```rust
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum BinaryLayout {
    /// Put each operand on their own line if either side expands
    Default,

    /// Try to expand the left to make it fit. Add parentheses if the left or right don't fit.
    ///
    ///```python
    /// [
    ///     a,
    ///     b
    /// ] & c
    ///```
    ExpandLeft,

    /// Try to expand the right to make it fix. Add parentheses if the left or right don't fit.
    ///
    /// ```python
    /// a & [
    ///     b,
    ///     c
    /// ]
    /// ```
    ExpandRight,

    /// Both the left and right side can be expanded. Try in the following order:
    /// * expand the right side
    /// * expand the left side
    /// * expand both sides
    ///
    /// to make the expression fit
    ///
    /// ```python
    /// [
    ///     a,
    ///     b
    /// ] & [
    ///     c,
    ///     d
    /// ]
    /// ```
    ExpandRightThenLeft,
}
```

Our current implementation only handles `ExpandRight` and `Default` correctly. This PR adds support for `ExpandRightThenLeft` and `ExpandLeft`. 

## Test Plan

I added tests that play through all 4 binary expression layouts.
2023-06-21 09:40:05 +02:00
133 changed files with 6023 additions and 1829 deletions

View File

@@ -423,8 +423,8 @@ jobs:
echo "Releasing ${git_sha}"
fi
release:
name: Release
upload-release:
name: Upload to PyPI
runs-on: ubuntu-latest
needs:
- macos-universal
@@ -442,8 +442,6 @@ jobs:
permissions:
# For pypi trusted publishing
id-token: write
# For GitHub release publishing
contents: write
steps:
- uses: actions/download-artifact@v3
with:
@@ -455,10 +453,18 @@ jobs:
skip-existing: true
packages-dir: wheels
verbose: true
- uses: actions/download-artifact@v3
with:
name: binaries
path: binaries
tag-release:
name: Tag release
runs-on: ubuntu-latest
needs: upload-release
# If you don't set an input tag, it's a dry run (no uploads).
if: ${{ inputs.tag }}
permissions:
# For git tag
contents: write
steps:
- uses: actions/checkout@v3
- name: git tag
run: |
git config user.email "hey@astral.sh"
@@ -467,10 +473,25 @@ jobs:
# If there is duplicate tag, this will fail. The publish to pypi action will have been a noop (due to skip
# existing), so we make a non-destructive exit here
git push --tags
publish-release:
name: Publish to GitHub
runs-on: ubuntu-latest
needs: tag-release
# If you don't set an input tag, it's a dry run (no uploads).
if: ${{ inputs.tag }}
permissions:
# For GitHub release publishing
contents: write
steps:
- uses: actions/download-artifact@v3
with:
name: binaries
path: binaries
- name: "Publish to GitHub"
uses: softprops/action-gh-release@v1
with:
draft: true
draft: false
files: binaries/*
tag_name: v${{ inputs.tag }}

View File

@@ -12,7 +12,7 @@ Welcome! We're happy to have you here. Thank you in advance for your contributio
- [Example: Adding a new configuration option](#example-adding-a-new-configuration-option)
- [MkDocs](#mkdocs)
- [Release Process](#release-process)
- [Benchmarks](#benchmarks)
- [Benchmarks](#benchmarking-and-profiling)
## The Basics
@@ -307,7 +307,15 @@ downloading the [`known-github-tomls.json`](https://github.com/akx/ruff-usage-ag
as `github_search.jsonl` and following the instructions in [scripts/Dockerfile.ecosystem](https://github.com/astral-sh/ruff/blob/main/scripts/Dockerfile.ecosystem).
Note that this check will take a while to run.
## Benchmarks
## Benchmarking and Profiling
We have several ways of benchmarking and profiling Ruff:
- Our main performance benchmark comparing Ruff with other tools on the CPython codebase
- Microbenchmarks which the linter or the formatter on individual files. There run on pull requests.
- Profiling the linter on either the microbenchmarks or entire projects
### CPython Benchmark
First, clone [CPython](https://github.com/python/cpython). It's a large and diverse Python codebase,
which makes it a good target for benchmarking.
@@ -386,9 +394,9 @@ Summary
159.43 ± 2.48 times faster than 'pycodestyle crates/ruff/resources/test/cpython'
```
You can run `poetry install` from `./scripts` to create a working environment for the above. All
reported benchmarks were computed using the versions specified by `./scripts/pyproject.toml`
on Python 3.11.
You can run `poetry install` from `./scripts/benchmarks` to create a working environment for the
above. All reported benchmarks were computed using the versions specified by
`./scripts/benchmarks/pyproject.toml` on Python 3.11.
To benchmark Pylint, remove the following files from the CPython repository:
@@ -429,3 +437,116 @@ Benchmark 1: find . -type f -name "*.py" | xargs -P 0 pyupgrade --py311-plus
Time (mean ± σ): 30.119 s ± 0.195 s [User: 28.638 s, System: 0.390 s]
Range (min … max): 29.813 s … 30.356 s 10 runs
```
## Microbenchmarks
The `ruff_benchmark` crate benchmarks the linter and the formatter on individual files.
You can run the benchmarks with
```shell
cargo benchmark
```
### Benchmark driven Development
Ruff uses [Criterion.rs](https://bheisler.github.io/criterion.rs/book/) for benchmarks. You can use
`--save-baseline=<name>` to store an initial baseline benchmark (e.g. on `main`) and then use
`--benchmark=<name>` to compare against that benchmark. Criterion will print a message telling you
if the benchmark improved/regressed compared to that baseline.
```shell
# Run once on your "baseline" code
cargo benchmark --save-baseline=main
# Then iterate with
cargo benchmark --baseline=main
```
### PR Summary
You can use `--save-baseline` and `critcmp` to get a pretty comparison between two recordings.
This is useful to illustrate the improvements of a PR.
```shell
# On main
cargo benchmark --save-baseline=main
# After applying your changes
cargo benchmark --save-baseline=pr
critcmp main pr
```
You must install [`critcmp`](https://github.com/BurntSushi/critcmp) for the comparison.
```bash
cargo install critcmp
```
### Tips
- Use `cargo benchmark <filter>` to only run specific benchmarks. For example: `cargo benchmark linter/pydantic`
to only run the pydantic tests.
- Use `cargo benchmark --quiet` for a more cleaned up output (without statistical relevance)
- Use `cargo benchmark --quick` to get faster results (more prone to noise)
## Profiling Projects
You can either use the microbenchmarks from above or a project directory for benchmarking. There
are a lot of profiling tools out there,
[The Rust Performance Book](https://nnethercote.github.io/perf-book/profiling.html) lists some
examples.
### Linux
Install `perf` and build `ruff_benchmark` with the `release-debug` profile and then run it with perf
```shell
cargo bench -p ruff_benchmark --no-run --profile=release-debug && perf record -g -F 9999 cargo bench -p ruff_benchmark --profile=release-debug -- --profile-time=1
```
You can also use the `ruff_dev` launcher to run `ruff check` multiple times on a repository to
gather enough samples for a good flamegraph (change the 999, the sample rate, and the 30, the number
of checks, to your liking)
```shell
cargo build --bin ruff_dev --profile=release-debug
perf record -g -F 999 target/release-debug/ruff_dev repeat --repeat 30 --exit-zero --no-cache path/to/cpython > /dev/null
```
Then convert the recorded profile
```shell
perf script -F +pid > /tmp/test.perf
```
You can now view the converted file with [firefox profiler](https://profiler.firefox.com/), with a
more in-depth guide [here](https://profiler.firefox.com/docs/#/./guide-perf-profiling)
An alternative is to convert the perf data to `flamegraph.svg` using
[flamegraph](https://github.com/flamegraph-rs/flamegraph) (`cargo install flamegraph`):
```shell
flamegraph --perfdata perf.data
```
### Mac
Install [`cargo-instruments`](https://crates.io/crates/cargo-instruments):
```shell
cargo install cargo-instruments
```
Then run the profiler with
```shell
cargo instruments -t time --bench linter --profile release-debug -p ruff_benchmark -- --profile-time=1
```
- `-t`: Specifies what to profile. Useful options are `time` to profile the wall time and `alloc`
for profiling the allocations.
- You may want to pass an additional filter to run a single test file
Otherwise, follow the instructions from the linux section.

View File

@@ -14,9 +14,9 @@ An extremely fast Python linter, written in Rust.
<p align="center">
<picture align="center">
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/1309177/212613422-7faaf278-706b-4294-ad92-236ffcab3430.svg">
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1309177/212613257-5f4bca12-6d6b-4c79-9bac-51a4c6d08928.svg">
<img alt="Shows a bar chart with benchmark results." src="https://user-images.githubusercontent.com/1309177/212613257-5f4bca12-6d6b-4c79-9bac-51a4c6d08928.svg">
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/1309177/232603514-c95e9b0f-6b31-43de-9a80-9e844173fd6a.svg">
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1309177/232603516-4fb4892d-585c-4b20-b810-3db9161831e4.svg">
<img alt="Shows a bar chart with benchmark results." src="https://user-images.githubusercontent.com/1309177/232603516-4fb4892d-585c-4b20-b810-3db9161831e4.svg">
</picture>
</p>

View File

@@ -1,5 +1,5 @@
import typing
from typing import ClassVar, Sequence
from typing import ClassVar, Sequence, Final
KNOWINGLY_MUTABLE_DEFAULT = []
@@ -10,6 +10,7 @@ class A:
without_annotation = []
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
class_variable: typing.ClassVar[list[int]] = []
final_variable: typing.Final[list[int]] = []
class B:
@@ -18,6 +19,7 @@ class B:
without_annotation = []
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
class_variable: ClassVar[list[int]] = []
final_variable: Final[list[int]] = []
from dataclasses import dataclass, field
@@ -31,3 +33,17 @@ class C:
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
perfectly_fine: list[int] = field(default_factory=list)
class_variable: ClassVar[list[int]] = []
final_variable: Final[list[int]] = []
from pydantic import BaseModel
class D(BaseModel):
mutable_default: list[int] = []
immutable_annotation: Sequence[int] = []
without_annotation = []
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
perfectly_fine: list[int] = field(default_factory=list)
class_variable: ClassVar[list[int]] = []
final_variable: Final[list[int]] = []

View File

@@ -18,19 +18,19 @@ def f(arg: object = None):
pass
def f(arg: int = None): # RUF011
def f(arg: int = None): # RUF013
pass
def f(arg: str = None): # RUF011
def f(arg: str = None): # RUF013
pass
def f(arg: typing.List[str] = None): # RUF011
def f(arg: typing.List[str] = None): # RUF013
pass
def f(arg: Tuple[str] = None): # RUF011
def f(arg: Tuple[str] = None): # RUF013
pass
@@ -64,15 +64,15 @@ def f(arg: Union[int, str, Any] = None):
pass
def f(arg: Union = None): # RUF011
def f(arg: Union = None): # RUF013
pass
def f(arg: Union[int, str] = None): # RUF011
def f(arg: Union[int, str] = None): # RUF013
pass
def f(arg: typing.Union[int, str] = None): # RUF011
def f(arg: typing.Union[int, str] = None): # RUF013
pass
@@ -91,11 +91,11 @@ def f(arg: int | float | str | None = None):
pass
def f(arg: int | float = None): # RUF011
def f(arg: int | float = None): # RUF013
pass
def f(arg: int | float | str | bytes = None): # RUF011
def f(arg: int | float | str | bytes = None): # RUF013
pass
@@ -110,11 +110,11 @@ def f(arg: Literal[1, 2, None, 3] = None):
pass
def f(arg: Literal[1, "foo"] = None): # RUF011
def f(arg: Literal[1, "foo"] = None): # RUF013
pass
def f(arg: typing.Literal[1, "foo", True] = None): # RUF011
def f(arg: typing.Literal[1, "foo", True] = None): # RUF013
pass
@@ -133,11 +133,11 @@ def f(arg: Annotated[Any, ...] = None):
pass
def f(arg: Annotated[int, ...] = None): # RUF011
def f(arg: Annotated[int, ...] = None): # RUF013
pass
def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF011
def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013
pass
@@ -153,9 +153,9 @@ def f(
def f(
arg1: int = None, # RUF011
arg2: Union[int, float] = None, # RUF011
arg3: Literal[1, 2, 3] = None, # RUF011
arg1: int = None, # RUF013
arg2: Union[int, float] = None, # RUF013
arg3: Literal[1, 2, 3] = None, # RUF013
):
pass
@@ -183,20 +183,41 @@ def f(arg: Union[Annotated[int, ...], Annotated[Optional[float], ...]] = None):
pass
def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF011
def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013
pass
# Quoted
def f(arg: "int" = None):
def f(arg: "int" = None): # RUF013
pass
def f(arg: "str" = None):
def f(arg: "str" = None): # RUF013
pass
def f(arg: "st" "r" = None): # RUF013
pass
def f(arg: "Optional[int]" = None):
pass
def f(arg: Union["int", "str"] = None): # RUF013
pass
def f(arg: Union["int", "None"] = None):
pass
def f(arg: Union["No" "ne", "int"] = None):
pass
# Avoid flagging when there's a parse error in the forward reference
def f(arg: Union["<>", "int"] = None):
pass

View File

@@ -3852,6 +3852,9 @@ where
);
}
// Store the existing binding, if any.
let existing_id = self.semantic.lookup(name);
// Add the bound exception name to the scope.
let binding_id = self.add_binding(
name,
@@ -3862,14 +3865,6 @@ where
walk_except_handler(self, except_handler);
// Remove it from the scope immediately after.
self.add_binding(
name,
range,
BindingKind::UnboundException,
BindingFlags::empty(),
);
// If the exception name wasn't used in the scope, emit a diagnostic.
if !self.semantic.is_used(binding_id) {
if self.enabled(Rule::UnusedVariable) {
@@ -3889,6 +3884,13 @@ where
self.diagnostics.push(diagnostic);
}
}
self.add_binding(
name,
range,
BindingKind::UnboundException(existing_id),
BindingFlags::empty(),
);
}
None => walk_except_handler(self, except_handler),
}
@@ -4236,7 +4238,7 @@ impl<'a> Checker<'a> {
let shadowed = &self.semantic.bindings[shadowed_id];
if !matches!(
shadowed.kind,
BindingKind::Builtin | BindingKind::Deletion | BindingKind::UnboundException,
BindingKind::Builtin | BindingKind::Deletion | BindingKind::UnboundException(_),
) {
let references = shadowed.references.clone();
let is_global = shadowed.is_global();

View File

@@ -157,7 +157,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
// pylint
(Pylint, "C0414") => (RuleGroup::Unspecified, rules::pylint::rules::UselessImportAlias),
(Pylint, "C1901") => (RuleGroup::Unspecified, rules::pylint::rules::CompareToEmptyString),
(Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString),
(Pylint, "C3002") => (RuleGroup::Unspecified, rules::pylint::rules::UnnecessaryDirectLambdaCall),
(Pylint, "C0208") => (RuleGroup::Unspecified, rules::pylint::rules::IterationOverSet),
(Pylint, "E0100") => (RuleGroup::Unspecified, rules::pylint::rules::YieldInInit),

View File

@@ -78,7 +78,7 @@ fn detect_package_root_with_cache<'a>(
current
}
/// Return a mapping from Python file to its package root.
/// Return a mapping from Python package to its package root.
pub fn detect_package_roots<'a>(
files: &[&'a Path],
resolver: &'a Resolver,

View File

@@ -251,7 +251,7 @@ impl Renamer {
| BindingKind::ClassDefinition
| BindingKind::FunctionDefinition
| BindingKind::Deletion
| BindingKind::UnboundException => {
| BindingKind::UnboundException(_) => {
Some(Edit::range_replacement(target.to_string(), binding.range))
}
}

View File

@@ -40,6 +40,5 @@ pub(super) fn convert_inplace_argument_to_assignment(
false,
)
.ok()?;
#[allow(deprecated)]
Some(Fix::unspecified_edits(insert_assignment, [remove_argument]))
Some(Fix::suggested_edits(insert_assignment, [remove_argument]))
}

View File

@@ -10,7 +10,21 @@ use crate::settings::Settings;
///
/// ## Why is this bad?
/// For flowing long blocks of text (docstrings or comments), overlong lines
/// can hurt readability.
/// can hurt readability. [PEP 8], for example, recommends that such lines be
/// limited to 72 characters.
///
/// In the context of this rule, a "doc line" is defined as a line consisting
/// of either a standalone comment or a standalone string, like a docstring.
///
/// In the interest of pragmatism, this rule makes a few exceptions when
/// determining whether a line is overlong. Namely, it ignores lines that
/// consist of a single "word" (i.e., without any whitespace between its
/// characters), and lines that end with a URL (as long as the URL starts
/// before the line-length threshold).
///
/// If `pycodestyle.ignore_overlong_task_comments` is `true`, this rule will
/// also ignore comments that start with any of the specified `task-tags`
/// (e.g., `# TODO:`).
///
/// ## Example
/// ```python
@@ -26,6 +40,13 @@ use crate::settings::Settings;
/// Duis auctor purus ut ex fermentum, at maximus est hendrerit.
/// """
/// ```
///
///
/// ## Options
/// - `task-tags`
/// - `pycodestyle.ignore-overlong-task-comments`
///
/// [PEP 8]: https://peps.python.org/pep-0008/#maximum-line-length
#[violation]
pub struct DocLineTooLong(pub usize, pub usize);

View File

@@ -9,7 +9,18 @@ use crate::settings::Settings;
/// Checks for lines that exceed the specified maximum character length.
///
/// ## Why is this bad?
/// Overlong lines can hurt readability.
/// Overlong lines can hurt readability. [PEP 8], for example, recommends
/// limiting lines to 79 characters.
///
/// In the interest of pragmatism, this rule makes a few exceptions when
/// determining whether a line is overlong. Namely, it ignores lines that
/// consist of a single "word" (i.e., without any whitespace between its
/// characters), and lines that end with a URL (as long as the URL starts
/// before the line-length threshold).
///
/// If `pycodestyle.ignore_overlong_task_comments` is `true`, this rule will
/// also ignore comments that start with any of the specified `task-tags`
/// (e.g., `# TODO:`).
///
/// ## Example
/// ```python
@@ -26,6 +37,9 @@ use crate::settings::Settings;
///
/// ## Options
/// - `task-tags`
/// - `pycodestyle.ignore-overlong-task-comments`
///
/// [PEP 8]: https://peps.python.org/pep-0008/#maximum-line-length
#[violation]
pub struct LineTooLong(pub usize, pub usize);

View File

@@ -353,9 +353,59 @@ mod tests {
except Exception as x:
pass
# No error here, though it should arguably be an F821 error. `x` will
# be unbound after the `except` block (assuming an exception is raised
# and caught).
print(x)
"#,
"print_after_shadowing_except"
"print_in_body_after_shadowing_except"
)]
#[test_case(
r#"
def f():
x = 1
try:
1 / 0
except ValueError as x:
pass
except ImportError as x:
pass
# No error here, though it should arguably be an F821 error. `x` will
# be unbound after the `except` block (assuming an exception is raised
# and caught).
print(x)
"#,
"print_in_body_after_double_shadowing_except"
)]
#[test_case(
r#"
def f():
try:
x = 3
except ImportError as x:
print(x)
else:
print(x)
"#,
"print_in_try_else_after_shadowing_except"
)]
#[test_case(
r#"
def f():
list = [1, 2, 3]
for e in list:
if e % 2 == 0:
try:
pass
except Exception as e:
print(e)
else:
print(e)
"#,
"print_in_if_else_after_shadowing_except"
)]
#[test_case(
r#"
@@ -366,6 +416,79 @@ mod tests {
"#,
"double_del"
)]
#[test_case(
r#"
x = 1
def f():
try:
pass
except ValueError as x:
pass
# This should resolve to the `x` in `x = 1`.
print(x)
"#,
"load_after_unbind_from_module_scope"
)]
#[test_case(
r#"
x = 1
def f():
try:
pass
except ValueError as x:
pass
try:
pass
except ValueError as x:
pass
# This should resolve to the `x` in `x = 1`.
print(x)
"#,
"load_after_multiple_unbinds_from_module_scope"
)]
#[test_case(
r#"
x = 1
def f():
try:
pass
except ValueError as x:
pass
def g():
try:
pass
except ValueError as x:
pass
# This should resolve to the `x` in `x = 1`.
print(x)
"#,
"load_after_unbind_from_nested_module_scope"
)]
#[test_case(
r#"
class C:
x = 1
def f():
try:
pass
except ValueError as x:
pass
# This should raise an F821 error, rather than resolving to the
# `x` in `x = 1`.
print(x)
"#,
"load_after_unbind_from_class_scope"
)]
fn contents(contents: &str, snapshot: &str) {
let diagnostics = test_snippet(contents, &Settings::for_rules(&Linter::Pyflakes));
assert_messages!(snapshot, diagnostics);

View File

@@ -89,8 +89,7 @@ fn fix_f_string_missing_placeholders(
checker: &mut Checker,
) -> Fix {
let content = &checker.locator.contents()[TextRange::new(prefix_range.end(), tok_range.end())];
#[allow(deprecated)]
Fix::unspecified(Edit::replacement(
Fix::automatic(Edit::replacement(
unescape_f_string(content),
prefix_range.start(),
tok_range.end(),

View File

@@ -11,7 +11,7 @@ F541.py:6:5: F541 [*] f-string without any placeholders
|
= help: Remove extraneous `f` prefix
Suggested fix
Fix
3 3 | b = f"ghi{'jkl'}"
4 4 |
5 5 | # Errors
@@ -32,7 +32,7 @@ F541.py:7:5: F541 [*] f-string without any placeholders
|
= help: Remove extraneous `f` prefix
Suggested fix
Fix
4 4 |
5 5 | # Errors
6 6 | c = f"def"
@@ -53,7 +53,7 @@ F541.py:9:5: F541 [*] f-string without any placeholders
|
= help: Remove extraneous `f` prefix
Suggested fix
Fix
6 6 | c = f"def"
7 7 | d = f"def" + "ghi"
8 8 | e = (
@@ -74,7 +74,7 @@ F541.py:13:5: F541 [*] f-string without any placeholders
|
= help: Remove extraneous `f` prefix
Suggested fix
Fix
10 10 | "ghi"
11 11 | )
12 12 | f = (
@@ -95,7 +95,7 @@ F541.py:14:5: F541 [*] f-string without any placeholders
|
= help: Remove extraneous `f` prefix
Suggested fix
Fix
11 11 | )
12 12 | f = (
13 13 | f"a"
@@ -116,7 +116,7 @@ F541.py:16:5: F541 [*] f-string without any placeholders
|
= help: Remove extraneous `f` prefix
Suggested fix
Fix
13 13 | f"a"
14 14 | F"b"
15 15 | "c"
@@ -137,7 +137,7 @@ F541.py:17:5: F541 [*] f-string without any placeholders
|
= help: Remove extraneous `f` prefix
Suggested fix
Fix
14 14 | F"b"
15 15 | "c"
16 16 | rf"d"
@@ -158,7 +158,7 @@ F541.py:19:5: F541 [*] f-string without any placeholders
|
= help: Remove extraneous `f` prefix
Suggested fix
Fix
16 16 | rf"d"
17 17 | fr"e"
18 18 | )
@@ -178,7 +178,7 @@ F541.py:25:13: F541 [*] f-string without any placeholders
|
= help: Remove extraneous `f` prefix
Suggested fix
Fix
22 22 | g = f"ghi{123:{45}}"
23 23 |
24 24 | # Error
@@ -198,7 +198,7 @@ F541.py:34:7: F541 [*] f-string without any placeholders
|
= help: Remove extraneous `f` prefix
Suggested fix
Fix
31 31 | f"{f'{v:0.2f}'}"
32 32 |
33 33 | # Errors
@@ -219,7 +219,7 @@ F541.py:35:4: F541 [*] f-string without any placeholders
|
= help: Remove extraneous `f` prefix
Suggested fix
Fix
32 32 |
33 33 | # Errors
34 34 | f"{v:{f'0.2f'}}"
@@ -240,7 +240,7 @@ F541.py:36:1: F541 [*] f-string without any placeholders
|
= help: Remove extraneous `f` prefix
Suggested fix
Fix
33 33 | # Errors
34 34 | f"{v:{f'0.2f'}}"
35 35 | f"{f''}"
@@ -261,7 +261,7 @@ F541.py:37:1: F541 [*] f-string without any placeholders
|
= help: Remove extraneous `f` prefix
Suggested fix
Fix
34 34 | f"{v:{f'0.2f'}}"
35 35 | f"{f''}"
36 36 | f"{{test}}"
@@ -281,7 +281,7 @@ F541.py:38:1: F541 [*] f-string without any placeholders
|
= help: Remove extraneous `f` prefix
Suggested fix
Fix
35 35 | f"{f''}"
36 36 | f"{{test}}"
37 37 | f'{{ 40 }}'
@@ -302,7 +302,7 @@ F541.py:39:1: F541 [*] f-string without any placeholders
|
= help: Remove extraneous `f` prefix
Suggested fix
Fix
36 36 | f"{{test}}"
37 37 | f'{{ 40 }}'
38 38 | f"{{a {{x}}"

View File

@@ -0,0 +1,44 @@
---
source: crates/ruff/src/rules/pyflakes/mod.rs
---
<filename>:7:26: F841 [*] Local variable `x` is assigned to but never used
|
5 | try:
6 | pass
7 | except ValueError as x:
| ^ F841
8 | pass
|
= help: Remove assignment to unused variable `x`
Fix
4 4 | def f():
5 5 | try:
6 6 | pass
7 |- except ValueError as x:
7 |+ except ValueError:
8 8 | pass
9 9 |
10 10 | try:
<filename>:12:26: F841 [*] Local variable `x` is assigned to but never used
|
10 | try:
11 | pass
12 | except ValueError as x:
| ^ F841
13 | pass
|
= help: Remove assignment to unused variable `x`
Fix
9 9 |
10 10 | try:
11 11 | pass
12 |- except ValueError as x:
12 |+ except ValueError:
13 13 | pass
14 14 |
15 15 | # This should resolve to the `x` in `x = 1`.

View File

@@ -0,0 +1,32 @@
---
source: crates/ruff/src/rules/pyflakes/mod.rs
---
<filename>:8:30: F841 [*] Local variable `x` is assigned to but never used
|
6 | try:
7 | pass
8 | except ValueError as x:
| ^ F841
9 | pass
|
= help: Remove assignment to unused variable `x`
Fix
5 5 | def f():
6 6 | try:
7 7 | pass
8 |- except ValueError as x:
8 |+ except ValueError:
9 9 | pass
10 10 |
11 11 | # This should raise an F821 error, rather than resolving to the
<filename>:13:15: F821 Undefined name `x`
|
11 | # This should raise an F821 error, rather than resolving to the
12 | # `x` in `x = 1`.
13 | print(x)
| ^ F821
|

View File

@@ -0,0 +1,24 @@
---
source: crates/ruff/src/rules/pyflakes/mod.rs
---
<filename>:7:26: F841 [*] Local variable `x` is assigned to but never used
|
5 | try:
6 | pass
7 | except ValueError as x:
| ^ F841
8 | pass
|
= help: Remove assignment to unused variable `x`
Fix
4 4 | def f():
5 5 | try:
6 6 | pass
7 |- except ValueError as x:
7 |+ except ValueError:
8 8 | pass
9 9 |
10 10 | # This should resolve to the `x` in `x = 1`.

View File

@@ -0,0 +1,44 @@
---
source: crates/ruff/src/rules/pyflakes/mod.rs
---
<filename>:7:26: F841 [*] Local variable `x` is assigned to but never used
|
5 | try:
6 | pass
7 | except ValueError as x:
| ^ F841
8 | pass
|
= help: Remove assignment to unused variable `x`
Fix
4 4 | def f():
5 5 | try:
6 6 | pass
7 |- except ValueError as x:
7 |+ except ValueError:
8 8 | pass
9 9 |
10 10 | def g():
<filename>:13:30: F841 [*] Local variable `x` is assigned to but never used
|
11 | try:
12 | pass
13 | except ValueError as x:
| ^ F841
14 | pass
|
= help: Remove assignment to unused variable `x`
Fix
10 10 | def g():
11 11 | try:
12 12 | pass
13 |- except ValueError as x:
13 |+ except ValueError:
14 14 | pass
15 15 |
16 16 | # This should resolve to the `x` in `x = 1`.

View File

@@ -0,0 +1,45 @@
---
source: crates/ruff/src/rules/pyflakes/mod.rs
---
<filename>:7:26: F841 [*] Local variable `x` is assigned to but never used
|
5 | try:
6 | 1 / 0
7 | except ValueError as x:
| ^ F841
8 | pass
9 | except ImportError as x:
|
= help: Remove assignment to unused variable `x`
Fix
4 4 |
5 5 | try:
6 6 | 1 / 0
7 |- except ValueError as x:
7 |+ except ValueError:
8 8 | pass
9 9 | except ImportError as x:
10 10 | pass
<filename>:9:27: F841 [*] Local variable `x` is assigned to but never used
|
7 | except ValueError as x:
8 | pass
9 | except ImportError as x:
| ^ F841
10 | pass
|
= help: Remove assignment to unused variable `x`
Fix
6 6 | 1 / 0
7 7 | except ValueError as x:
8 8 | pass
9 |- except ImportError as x:
9 |+ except ImportError:
10 10 | pass
11 11 |
12 12 | # No error here, though it should arguably be an F821 error. `x` will

View File

@@ -19,14 +19,6 @@ source: crates/ruff/src/rules/pyflakes/mod.rs
7 |+ except Exception:
8 8 | pass
9 9 |
10 10 | print(x)
<filename>:10:11: F821 Undefined name `x`
|
8 | pass
9 |
10 | print(x)
| ^ F821
|
10 10 | # No error here, though it should arguably be an F821 error. `x` will

View File

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

View File

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

View File

@@ -7,49 +7,6 @@ use ruff_macros::{derive_message_formats, violation};
use crate::checkers::ast::Checker;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub(crate) enum EmptyStringCmpOp {
Is,
IsNot,
Eq,
NotEq,
}
impl TryFrom<&CmpOp> for EmptyStringCmpOp {
type Error = anyhow::Error;
fn try_from(value: &CmpOp) -> Result<Self, Self::Error> {
match value {
CmpOp::Is => Ok(Self::Is),
CmpOp::IsNot => Ok(Self::IsNot),
CmpOp::Eq => Ok(Self::Eq),
CmpOp::NotEq => Ok(Self::NotEq),
_ => bail!("{value:?} cannot be converted to EmptyStringCmpOp"),
}
}
}
impl EmptyStringCmpOp {
pub(crate) fn into_unary(self) -> &'static str {
match self {
Self::Is | Self::Eq => "not ",
Self::IsNot | Self::NotEq => "",
}
}
}
impl std::fmt::Display for EmptyStringCmpOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let repr = match self {
Self::Is => "is",
Self::IsNot => "is not",
Self::Eq => "==",
Self::NotEq => "!=",
};
write!(f, "{repr}")
}
}
/// ## What it does
/// Checks for comparisons to empty strings.
///
@@ -83,13 +40,15 @@ pub struct CompareToEmptyString {
impl Violation for CompareToEmptyString {
#[derive_message_formats]
fn message(&self) -> String {
format!(
"`{}` can be simplified to `{}` as an empty string is falsey",
self.existing, self.replacement,
)
let CompareToEmptyString {
existing,
replacement,
} = self;
format!("`{existing}` can be simplified to `{replacement}` as an empty string is falsey",)
}
}
/// PLC1901
pub(crate) fn compare_to_empty_string(
checker: &mut Checker,
left: &Expr,
@@ -98,10 +57,12 @@ pub(crate) fn compare_to_empty_string(
) {
// Omit string comparison rules within subscripts. This is mostly commonly used within
// DataFrame and np.ndarray indexing.
for parent in checker.semantic().expr_ancestors() {
if matches!(parent, Expr::Subscript(_)) {
return;
}
if checker
.semantic()
.expr_ancestors()
.any(|parent| parent.is_subscript_expr())
{
return;
}
let mut first = true;
@@ -153,3 +114,46 @@ pub(crate) fn compare_to_empty_string(
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
enum EmptyStringCmpOp {
Is,
IsNot,
Eq,
NotEq,
}
impl TryFrom<&CmpOp> for EmptyStringCmpOp {
type Error = anyhow::Error;
fn try_from(value: &CmpOp) -> Result<Self, Self::Error> {
match value {
CmpOp::Is => Ok(Self::Is),
CmpOp::IsNot => Ok(Self::IsNot),
CmpOp::Eq => Ok(Self::Eq),
CmpOp::NotEq => Ok(Self::NotEq),
_ => bail!("{value:?} cannot be converted to EmptyStringCmpOp"),
}
}
}
impl EmptyStringCmpOp {
fn into_unary(self) -> &'static str {
match self {
Self::Is | Self::Eq => "not ",
Self::IsNot | Self::NotEq => "",
}
}
}
impl std::fmt::Display for EmptyStringCmpOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let repr = match self {
Self::Is => "is",
Self::IsNot => "is not",
Self::Eq => "==",
Self::NotEq => "!=",
};
write!(f, "{repr}")
}
}

View File

@@ -191,8 +191,7 @@ pub(crate) fn invalid_string_characters(locator: &Locator, range: TextRange) ->
let location = range.start() + TextSize::try_from(column).unwrap();
let range = TextRange::at(location, c.text_len());
#[allow(deprecated)]
diagnostics.push(Diagnostic::new(rule, range).with_fix(Fix::unspecified(
diagnostics.push(Diagnostic::new(rule, range).with_fix(Fix::automatic(
Edit::range_replacement(replacement.to_string(), range),
)));
}

View File

@@ -157,8 +157,7 @@ pub(crate) fn nested_min_max(
keywords: keywords.to_owned(),
range: TextRange::default(),
});
#[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
checker.generator().expr(&flattened_expr),
expr.range(),
)));

View File

@@ -82,8 +82,7 @@ pub(crate) fn sys_exit_alias(checker: &mut Checker, func: &Expr) {
checker.semantic(),
)?;
let reference_edit = Edit::range_replacement(binding, func.range());
#[allow(deprecated)]
Ok(Fix::unspecified_edits(import_edit, [reference_edit]))
Ok(Fix::suggested_edits(import_edit, [reference_edit]))
});
}
checker.diagnostics.push(diagnostic);

View File

@@ -49,8 +49,7 @@ pub(crate) fn useless_import_alias(checker: &mut Checker, alias: &Alias) {
let mut diagnostic = Diagnostic::new(UselessImportAlias, alias.range());
if checker.patch(diagnostic.kind.rule()) {
#[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
asname.to_string(),
alias.range(),
)));

View File

@@ -12,7 +12,7 @@ invalid_characters.py:15:6: PLE2510 [*] Invalid unescaped character backspace, u
|
= help: Replace with escape sequence
Suggested fix
Fix
12 12 | # (Pylint, "C0414") => Rule::UselessImportAlias,
13 13 | # (Pylint, "C3002") => Rule::UnnecessaryDirectLambdaCall,
14 14 | #foo = 'hi'

View File

@@ -12,7 +12,7 @@ invalid_characters.py:21:12: PLE2512 [*] Invalid unescaped character SUB, use "\
|
= help: Replace with escape sequence
Suggested fix
Fix
18 18 |
19 19 | cr_ok = '\\r'
20 20 |

View File

@@ -12,7 +12,7 @@ invalid_characters.py:25:16: PLE2513 [*] Invalid unescaped character ESC, use "\
|
= help: Replace with escape sequence
Suggested fix
Fix
22 22 |
23 23 | sub_ok = '\x1a'
24 24 |

View File

@@ -12,7 +12,7 @@ invalid_characters.py:34:13: PLE2515 [*] Invalid unescaped character zero-width-
|
= help: Replace with escape sequence
Suggested fix
Fix
31 31 |
32 32 | nul_ok = '\0'
33 33 |
@@ -32,7 +32,7 @@ invalid_characters.py:38:36: PLE2515 [*] Invalid unescaped character zero-width-
|
= help: Replace with escape sequence
Suggested fix
Fix
35 35 |
36 36 | zwsp_ok = '\u200b'
37 37 |
@@ -48,7 +48,7 @@ invalid_characters.py:39:60: PLE2515 [*] Invalid unescaped character zero-width-
|
= help: Replace with escape sequence
Suggested fix
Fix
36 36 | zwsp_ok = '\u200b'
37 37 |
38 38 | zwsp_after_multibyte_character = "ಫ​"
@@ -63,7 +63,7 @@ invalid_characters.py:39:61: PLE2515 [*] Invalid unescaped character zero-width-
|
= help: Replace with escape sequence
Suggested fix
Fix
36 36 | zwsp_ok = '\u200b'
37 37 |
38 38 | zwsp_after_multibyte_character = "ಫ​"

View File

@@ -11,6 +11,22 @@ use crate::rules::ruff::rules::confusables::CONFUSABLES;
use crate::rules::ruff::rules::Context;
use crate::settings::Settings;
/// ## What it does
/// Checks for ambiguous unicode characters in strings.
///
/// ## Why is this bad?
/// The use of ambiguous unicode characters can confuse readers and cause
/// subtle bugs.
///
/// ## Example
/// ```python
/// print("Ηello, world!") # "Η" is the Greek eta (`U+0397`).
/// ```
///
/// Use instead:
/// ```python
/// print("Hello, world!") # "H" is the Latin capital H (`U+0048`).
/// ```
#[violation]
pub struct AmbiguousUnicodeCharacterString {
confusable: char,
@@ -44,6 +60,22 @@ impl AlwaysAutofixableViolation for AmbiguousUnicodeCharacterString {
}
}
/// ## What it does
/// Checks for ambiguous unicode characters in docstrings.
///
/// ## Why is this bad?
/// The use of ambiguous unicode characters can confuse readers and cause
/// subtle bugs.
///
/// ## Example
/// ```python
/// """A lovely docstring (with a `U+FF09` parenthesis."""
/// ```
///
/// Use instead:
/// ```python
/// """A lovely docstring (with no strange parentheses)."""
/// ```
#[violation]
pub struct AmbiguousUnicodeCharacterDocstring {
confusable: char,
@@ -77,6 +109,22 @@ impl AlwaysAutofixableViolation for AmbiguousUnicodeCharacterDocstring {
}
}
/// ## What it does
/// Checks for ambiguous unicode characters in comments.
///
/// ## Why is this bad?
/// The use of ambiguous unicode characters can confuse readers and cause
/// subtle bugs.
///
/// ## Example
/// ```python
/// foo() # nоqa # "о" is Cyrillic (`U+043E`)
/// ```
///
/// Use instead:
/// ```python
/// foo() # noqa # "o" is Latin (`U+006F`)
/// ```
#[violation]
pub struct AmbiguousUnicodeCharacterComment {
confusable: char,

View File

@@ -13,6 +13,36 @@ pub struct CollectionLiteralConcatenation {
expr: String,
}
/// ## What it does
/// Checks for uses of the `+` operator to concatenate collections.
///
/// ## Why is this bad?
/// In Python, the `+` operator can be used to concatenate collections (e.g.,
/// `x + y` to concatenate the lists `x` and `y`).
///
/// However, collections can be concatenated more efficiently using the
/// unpacking operator (e.g., `[*x, *y]` to concatenate `x` and `y`).
///
/// Prefer the unpacking operator to concatenate collections, as it is more
/// readable and flexible. The `*` operator can unpack any iterable, whereas
/// `+` operates only on particular sequences which, in many cases, must be of
/// the same type.
///
/// ## Example
/// ```python
/// foo = [2, 3, 4]
/// bar = [1] + foo + [5, 6]
/// ```
///
/// Use instead:
/// ```python
/// foo = [2, 3, 4]
/// bar = [1, *foo, 5, 6]
/// ```
///
/// ## References
/// - [PEP 448 Additional Unpacking Generalizations](https://peps.python.org/pep-0448/)
/// - [Python docs: Sequence Types — `list`, `tuple`, `range`](https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range)
impl Violation for CollectionLiteralConcatenation {
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;

View File

@@ -55,7 +55,7 @@ use crate::rules::ruff::rules::helpers::{
/// - `flake8-bugbear.extend-immutable-calls`
#[violation]
pub struct FunctionCallInDataclassDefaultArgument {
pub name: Option<String>,
name: Option<String>,
}
impl Violation for FunctionCallInDataclassDefaultArgument {

View File

@@ -18,6 +18,14 @@ pub(super) fn is_class_var_annotation(annotation: &Expr, semantic: &SemanticMode
semantic.match_typing_expr(value, "ClassVar")
}
/// Returns `true` if the given [`Expr`] is a `typing.Final` annotation.
pub(super) fn is_final_annotation(annotation: &Expr, semantic: &SemanticModel) -> bool {
let Expr::Subscript(ast::ExprSubscript { value, .. }) = &annotation else {
return false;
};
semantic.match_typing_expr(value, "Final")
}
/// Returns `true` if the given class is a dataclass.
pub(super) fn is_dataclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
class_def.decorator_list.iter().any(|decorator| {
@@ -28,3 +36,12 @@ pub(super) fn is_dataclass(class_def: &ast::StmtClassDef, semantic: &SemanticMod
})
})
}
/// Returns `true` if the given class is a Pydantic `BaseModel`.
pub(super) fn is_pydantic_model(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
class_def.bases.iter().any(|expr| {
semantic.resolve_call_path(expr).map_or(false, |call_path| {
matches!(call_path.as_slice(), ["pydantic", "BaseModel"])
})
})
}

View File

@@ -4,9 +4,11 @@ use anyhow::Result;
use ruff_text_size::TextRange;
use rustpython_parser::ast::{self, ArgWithDefault, Arguments, Constant, Expr, Operator, Ranged};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::is_const_none;
use ruff_python_ast::source_code::Locator;
use ruff_python_ast::typing::parse_type_annotation;
use ruff_python_semantic::SemanticModel;
use crate::checkers::ast::Checker;
@@ -65,14 +67,16 @@ pub struct ImplicitOptional {
conversion_type: ConversionType,
}
impl AlwaysAutofixableViolation for ImplicitOptional {
impl Violation for ImplicitOptional {
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
format!("PEP 484 prohibits implicit `Optional`")
}
fn autofix_title(&self) -> String {
format!("Convert to `{}`", self.conversion_type)
fn autofix_title(&self) -> Option<String> {
Some(format!("Convert to `{}`", self.conversion_type))
}
}
@@ -142,15 +146,15 @@ enum TypingTarget<'a> {
None,
Any,
Object,
ForwardReference,
Optional,
ForwardReference(Expr),
Union(Vec<&'a Expr>),
Literal(Vec<&'a Expr>),
Annotated(&'a Expr),
}
impl<'a> TypingTarget<'a> {
fn try_from_expr(expr: &'a Expr, semantic: &SemanticModel) -> Option<Self> {
fn try_from_expr(expr: &'a Expr, semantic: &SemanticModel, locator: &Locator) -> Option<Self> {
match expr {
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
if semantic.match_typing_expr(value, "Optional") {
@@ -177,9 +181,14 @@ impl<'a> TypingTarget<'a> {
..
}) => Some(TypingTarget::None),
Expr::Constant(ast::ExprConstant {
value: Constant::Str(_),
value: Constant::Str(string),
range,
..
}) => Some(TypingTarget::ForwardReference),
}) => parse_type_annotation(string, *range, locator)
// In case of a parse error, we return `Any` to avoid false positives.
.map_or(Some(TypingTarget::Any), |(expr, _)| {
Some(TypingTarget::ForwardReference(expr))
}),
_ => semantic.resolve_call_path(expr).and_then(|call_path| {
if semantic.match_typing_call_path(&call_path, "Any") {
Some(TypingTarget::Any)
@@ -193,44 +202,42 @@ impl<'a> TypingTarget<'a> {
}
/// Check if the [`TypingTarget`] explicitly allows `None`.
fn contains_none(&self, semantic: &SemanticModel) -> bool {
fn contains_none(&self, semantic: &SemanticModel, locator: &Locator) -> bool {
match self {
TypingTarget::None
| TypingTarget::Optional
| TypingTarget::Any
| TypingTarget::Object => true,
TypingTarget::Literal(elements) => elements.iter().any(|element| {
let Some(new_target) = TypingTarget::try_from_expr(element, semantic) else {
let Some(new_target) = TypingTarget::try_from_expr(element, semantic, locator) else {
return false;
};
// Literal can only contain `None`, a literal value, other `Literal`
// or an enum value.
match new_target {
TypingTarget::None => true,
TypingTarget::Literal(_) => new_target.contains_none(semantic),
TypingTarget::Literal(_) => new_target.contains_none(semantic, locator),
_ => false,
}
}),
TypingTarget::Union(elements) => elements.iter().any(|element| {
let Some(new_target) = TypingTarget::try_from_expr(element, semantic) else {
let Some(new_target) = TypingTarget::try_from_expr(element, semantic, locator) else {
return false;
};
match new_target {
TypingTarget::None => true,
_ => new_target.contains_none(semantic),
}
new_target.contains_none(semantic, locator)
}),
TypingTarget::Annotated(element) => {
let Some(new_target) = TypingTarget::try_from_expr(element, semantic) else {
let Some(new_target) = TypingTarget::try_from_expr(element, semantic, locator) else {
return false;
};
match new_target {
TypingTarget::None => true,
_ => new_target.contains_none(semantic),
}
new_target.contains_none(semantic, locator)
}
TypingTarget::ForwardReference(expr) => {
let Some(new_target) = TypingTarget::try_from_expr(expr, semantic, locator) else {
return false;
};
new_target.contains_none(semantic, locator)
}
// TODO(charlie): Add support for forward references (quoted annotations).
TypingTarget::ForwardReference => true,
}
}
}
@@ -245,8 +252,9 @@ impl<'a> TypingTarget<'a> {
fn type_hint_explicitly_allows_none<'a>(
annotation: &'a Expr,
semantic: &SemanticModel,
locator: &Locator,
) -> Option<&'a Expr> {
let Some(target) = TypingTarget::try_from_expr(annotation, semantic) else {
let Some(target) = TypingTarget::try_from_expr(annotation, semantic, locator) else {
return Some(annotation);
};
match target {
@@ -256,9 +264,9 @@ fn type_hint_explicitly_allows_none<'a>(
// return the inner type if it doesn't allow `None`. If `Annotated`
// is found nested inside another type, then the outer type should
// be returned.
TypingTarget::Annotated(expr) => type_hint_explicitly_allows_none(expr, semantic),
TypingTarget::Annotated(expr) => type_hint_explicitly_allows_none(expr, semantic, locator),
_ => {
if target.contains_none(semantic) {
if target.contains_none(semantic, locator) {
None
} else {
Some(annotation)
@@ -333,15 +341,42 @@ pub(crate) fn implicit_optional(checker: &mut Checker, arguments: &Arguments) {
let Some(annotation) = &def.annotation else {
continue
};
let Some(expr) = type_hint_explicitly_allows_none(annotation, checker.semantic()) else {
continue;
};
let conversion_type = checker.settings.target_version.into();
let mut diagnostic = Diagnostic::new(ImplicitOptional { conversion_type }, expr.range());
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| generate_fix(checker, conversion_type, expr));
if let Expr::Constant(ast::ExprConstant {
range,
value: Constant::Str(string),
..
}) = annotation.as_ref()
{
// Quoted annotation.
if let Ok((annotation, kind)) = parse_type_annotation(string, *range, checker.locator) {
let Some(expr) = type_hint_explicitly_allows_none(&annotation, checker.semantic(), checker.locator) else {
continue;
};
let conversion_type = checker.settings.target_version.into();
let mut diagnostic =
Diagnostic::new(ImplicitOptional { conversion_type }, expr.range());
if checker.patch(diagnostic.kind.rule()) {
if kind.is_simple() {
diagnostic.try_set_fix(|| generate_fix(checker, conversion_type, expr));
}
}
checker.diagnostics.push(diagnostic);
}
} else {
// Unquoted annotation.
let Some(expr) = type_hint_explicitly_allows_none(annotation, checker.semantic(), checker.locator) else {
continue;
};
let conversion_type = checker.settings.target_version.into();
let mut diagnostic =
Diagnostic::new(ImplicitOptional { conversion_type }, expr.range());
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| generate_fix(checker, conversion_type, expr));
}
checker.diagnostics.push(diagnostic);
}
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -5,7 +5,9 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_expr};
use crate::checkers::ast::Checker;
use crate::rules::ruff::rules::helpers::{is_class_var_annotation, is_dataclass};
use crate::rules::ruff::rules::helpers::{
is_class_var_annotation, is_dataclass, is_final_annotation, is_pydantic_model,
};
/// ## What it does
/// Checks for mutable default values in class attributes.
@@ -54,9 +56,15 @@ pub(crate) fn mutable_class_default(checker: &mut Checker, class_def: &ast::Stmt
}) => {
if is_mutable_expr(value, checker.semantic())
&& !is_class_var_annotation(annotation, checker.semantic())
&& !is_final_annotation(annotation, checker.semantic())
&& !is_immutable_annotation(annotation, checker.semantic())
&& !is_dataclass(class_def, checker.semantic())
{
// Avoid Pydantic models, which end up copying defaults on instance creation.
if is_pydantic_model(class_def, checker.semantic()) {
return;
}
checker
.diagnostics
.push(Diagnostic::new(MutableClassDefault, value.range()));
@@ -64,6 +72,11 @@ pub(crate) fn mutable_class_default(checker: &mut Checker, class_def: &ast::Stmt
}
Stmt::Assign(ast::StmtAssign { value, .. }) => {
if is_mutable_expr(value, checker.semantic()) {
// Avoid Pydantic models, which end up copying defaults on instance creation.
if is_pydantic_model(class_def, checker.semantic()) {
return;
}
checker
.diagnostics
.push(Diagnostic::new(MutableClassDefault, value.range()));

View File

@@ -6,6 +6,32 @@ use ruff_macros::{derive_message_formats, violation};
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for use of `zip()` to iterate over successive pairs of elements.
///
/// ## Why is this bad?
/// When iterating over successive pairs of elements, prefer
/// `itertools.pairwise()` over `zip()`.
///
/// `itertools.pairwise()` is more readable and conveys the intent of the code
/// more clearly.
///
/// ## Example
/// ```python
/// letters = "ABCD"
/// zip(letters, letters[1:]) # ("A", "B"), ("B", "C"), ("C", "D")
/// ```
///
/// Use instead:
/// ```python
/// from itertools import pairwise
///
/// letters = "ABCD"
/// pairwise(letters) # ("A", "B"), ("B", "C"), ("C", "D")
/// ```
///
/// ## References
/// - [Python documentation: `itertools.pairwise`](https://docs.python.org/3/library/itertools.html#itertools.pairwise)
#[violation]
pub struct PairwiseOverZipped;

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/ruff/mod.rs
---
RUF013_0.py:21:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
21 | def f(arg: int = None): # RUF011
21 | def f(arg: int = None): # RUF013
| ^^^ RUF013
22 | pass
|
@@ -13,15 +13,15 @@ RUF013_0.py:21:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
18 18 | pass
19 19 |
20 20 |
21 |-def f(arg: int = None): # RUF011
21 |+def f(arg: Optional[int] = None): # RUF011
21 |-def f(arg: int = None): # RUF013
21 |+def f(arg: Optional[int] = None): # RUF013
22 22 | pass
23 23 |
24 24 |
RUF013_0.py:25:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
25 | def f(arg: str = None): # RUF011
25 | def f(arg: str = None): # RUF013
| ^^^ RUF013
26 | pass
|
@@ -31,15 +31,15 @@ RUF013_0.py:25:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
22 22 | pass
23 23 |
24 24 |
25 |-def f(arg: str = None): # RUF011
25 |+def f(arg: Optional[str] = None): # RUF011
25 |-def f(arg: str = None): # RUF013
25 |+def f(arg: Optional[str] = None): # RUF013
26 26 | pass
27 27 |
28 28 |
RUF013_0.py:29:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
29 | def f(arg: typing.List[str] = None): # RUF011
29 | def f(arg: typing.List[str] = None): # RUF013
| ^^^^^^^^^^^^^^^^ RUF013
30 | pass
|
@@ -49,15 +49,15 @@ RUF013_0.py:29:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
26 26 | pass
27 27 |
28 28 |
29 |-def f(arg: typing.List[str] = None): # RUF011
29 |+def f(arg: Optional[typing.List[str]] = None): # RUF011
29 |-def f(arg: typing.List[str] = None): # RUF013
29 |+def f(arg: Optional[typing.List[str]] = None): # RUF013
30 30 | pass
31 31 |
32 32 |
RUF013_0.py:33:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
33 | def f(arg: Tuple[str] = None): # RUF011
33 | def f(arg: Tuple[str] = None): # RUF013
| ^^^^^^^^^^ RUF013
34 | pass
|
@@ -67,15 +67,15 @@ RUF013_0.py:33:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
30 30 | pass
31 31 |
32 32 |
33 |-def f(arg: Tuple[str] = None): # RUF011
33 |+def f(arg: Optional[Tuple[str]] = None): # RUF011
33 |-def f(arg: Tuple[str] = None): # RUF013
33 |+def f(arg: Optional[Tuple[str]] = None): # RUF013
34 34 | pass
35 35 |
36 36 |
RUF013_0.py:67:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
67 | def f(arg: Union = None): # RUF011
67 | def f(arg: Union = None): # RUF013
| ^^^^^ RUF013
68 | pass
|
@@ -85,15 +85,15 @@ RUF013_0.py:67:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
64 64 | pass
65 65 |
66 66 |
67 |-def f(arg: Union = None): # RUF011
67 |+def f(arg: Optional[Union] = None): # RUF011
67 |-def f(arg: Union = None): # RUF013
67 |+def f(arg: Optional[Union] = None): # RUF013
68 68 | pass
69 69 |
70 70 |
RUF013_0.py:71:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
71 | def f(arg: Union[int, str] = None): # RUF011
71 | def f(arg: Union[int, str] = None): # RUF013
| ^^^^^^^^^^^^^^^ RUF013
72 | pass
|
@@ -103,15 +103,15 @@ RUF013_0.py:71:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
68 68 | pass
69 69 |
70 70 |
71 |-def f(arg: Union[int, str] = None): # RUF011
71 |+def f(arg: Optional[Union[int, str]] = None): # RUF011
71 |-def f(arg: Union[int, str] = None): # RUF013
71 |+def f(arg: Optional[Union[int, str]] = None): # RUF013
72 72 | pass
73 73 |
74 74 |
RUF013_0.py:75:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
75 | def f(arg: typing.Union[int, str] = None): # RUF011
75 | def f(arg: typing.Union[int, str] = None): # RUF013
| ^^^^^^^^^^^^^^^^^^^^^^ RUF013
76 | pass
|
@@ -121,15 +121,15 @@ RUF013_0.py:75:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
72 72 | pass
73 73 |
74 74 |
75 |-def f(arg: typing.Union[int, str] = None): # RUF011
75 |+def f(arg: Optional[typing.Union[int, str]] = None): # RUF011
75 |-def f(arg: typing.Union[int, str] = None): # RUF013
75 |+def f(arg: Optional[typing.Union[int, str]] = None): # RUF013
76 76 | pass
77 77 |
78 78 |
RUF013_0.py:94:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
94 | def f(arg: int | float = None): # RUF011
94 | def f(arg: int | float = None): # RUF013
| ^^^^^^^^^^^ RUF013
95 | pass
|
@@ -139,15 +139,15 @@ RUF013_0.py:94:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
91 91 | pass
92 92 |
93 93 |
94 |-def f(arg: int | float = None): # RUF011
94 |+def f(arg: Optional[int | float] = None): # RUF011
94 |-def f(arg: int | float = None): # RUF013
94 |+def f(arg: Optional[int | float] = None): # RUF013
95 95 | pass
96 96 |
97 97 |
RUF013_0.py:98:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
98 | def f(arg: int | float | str | bytes = None): # RUF011
98 | def f(arg: int | float | str | bytes = None): # RUF013
| ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013
99 | pass
|
@@ -157,15 +157,15 @@ RUF013_0.py:98:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
95 95 | pass
96 96 |
97 97 |
98 |-def f(arg: int | float | str | bytes = None): # RUF011
98 |+def f(arg: Optional[int | float | str | bytes] = None): # RUF011
98 |-def f(arg: int | float | str | bytes = None): # RUF013
98 |+def f(arg: Optional[int | float | str | bytes] = None): # RUF013
99 99 | pass
100 100 |
101 101 |
RUF013_0.py:113:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
113 | def f(arg: Literal[1, "foo"] = None): # RUF011
113 | def f(arg: Literal[1, "foo"] = None): # RUF013
| ^^^^^^^^^^^^^^^^^ RUF013
114 | pass
|
@@ -175,15 +175,15 @@ RUF013_0.py:113:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
110 110 | pass
111 111 |
112 112 |
113 |-def f(arg: Literal[1, "foo"] = None): # RUF011
113 |+def f(arg: Optional[Literal[1, "foo"]] = None): # RUF011
113 |-def f(arg: Literal[1, "foo"] = None): # RUF013
113 |+def f(arg: Optional[Literal[1, "foo"]] = None): # RUF013
114 114 | pass
115 115 |
116 116 |
RUF013_0.py:117:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
117 | def f(arg: typing.Literal[1, "foo", True] = None): # RUF011
117 | def f(arg: typing.Literal[1, "foo", True] = None): # RUF013
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013
118 | pass
|
@@ -193,15 +193,15 @@ RUF013_0.py:117:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
114 114 | pass
115 115 |
116 116 |
117 |-def f(arg: typing.Literal[1, "foo", True] = None): # RUF011
117 |+def f(arg: Optional[typing.Literal[1, "foo", True]] = None): # RUF011
117 |-def f(arg: typing.Literal[1, "foo", True] = None): # RUF013
117 |+def f(arg: Optional[typing.Literal[1, "foo", True]] = None): # RUF013
118 118 | pass
119 119 |
120 120 |
RUF013_0.py:136:22: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
136 | def f(arg: Annotated[int, ...] = None): # RUF011
136 | def f(arg: Annotated[int, ...] = None): # RUF013
| ^^^ RUF013
137 | pass
|
@@ -211,15 +211,15 @@ RUF013_0.py:136:22: RUF013 [*] PEP 484 prohibits implicit `Optional`
133 133 | pass
134 134 |
135 135 |
136 |-def f(arg: Annotated[int, ...] = None): # RUF011
136 |+def f(arg: Annotated[Optional[int], ...] = None): # RUF011
136 |-def f(arg: Annotated[int, ...] = None): # RUF013
136 |+def f(arg: Annotated[Optional[int], ...] = None): # RUF013
137 137 | pass
138 138 |
139 139 |
RUF013_0.py:140:32: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
140 | def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF011
140 | def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013
| ^^^^^^^^^ RUF013
141 | pass
|
@@ -229,8 +229,8 @@ RUF013_0.py:140:32: RUF013 [*] PEP 484 prohibits implicit `Optional`
137 137 | pass
138 138 |
139 139 |
140 |-def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF011
140 |+def f(arg: Annotated[Annotated[Optional[int | str], ...], ...] = None): # RUF011
140 |-def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013
140 |+def f(arg: Annotated[Annotated[Optional[int | str], ...], ...] = None): # RUF013
141 141 | pass
142 142 |
143 143 |
@@ -238,10 +238,10 @@ RUF013_0.py:140:32: RUF013 [*] PEP 484 prohibits implicit `Optional`
RUF013_0.py:156:11: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
155 | def f(
156 | arg1: int = None, # RUF011
156 | arg1: int = None, # RUF013
| ^^^ RUF013
157 | arg2: Union[int, float] = None, # RUF011
158 | arg3: Literal[1, 2, 3] = None, # RUF011
157 | arg2: Union[int, float] = None, # RUF013
158 | arg3: Literal[1, 2, 3] = None, # RUF013
|
= help: Convert to `Optional[T]`
@@ -249,19 +249,19 @@ RUF013_0.py:156:11: RUF013 [*] PEP 484 prohibits implicit `Optional`
153 153 |
154 154 |
155 155 | def f(
156 |- arg1: int = None, # RUF011
156 |+ arg1: Optional[int] = None, # RUF011
157 157 | arg2: Union[int, float] = None, # RUF011
158 158 | arg3: Literal[1, 2, 3] = None, # RUF011
156 |- arg1: int = None, # RUF013
156 |+ arg1: Optional[int] = None, # RUF013
157 157 | arg2: Union[int, float] = None, # RUF013
158 158 | arg3: Literal[1, 2, 3] = None, # RUF013
159 159 | ):
RUF013_0.py:157:11: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
155 | def f(
156 | arg1: int = None, # RUF011
157 | arg2: Union[int, float] = None, # RUF011
156 | arg1: int = None, # RUF013
157 | arg2: Union[int, float] = None, # RUF013
| ^^^^^^^^^^^^^^^^^ RUF013
158 | arg3: Literal[1, 2, 3] = None, # RUF011
158 | arg3: Literal[1, 2, 3] = None, # RUF013
159 | ):
|
= help: Convert to `Optional[T]`
@@ -269,18 +269,18 @@ RUF013_0.py:157:11: RUF013 [*] PEP 484 prohibits implicit `Optional`
Suggested fix
154 154 |
155 155 | def f(
156 156 | arg1: int = None, # RUF011
157 |- arg2: Union[int, float] = None, # RUF011
157 |+ arg2: Optional[Union[int, float]] = None, # RUF011
158 158 | arg3: Literal[1, 2, 3] = None, # RUF011
156 156 | arg1: int = None, # RUF013
157 |- arg2: Union[int, float] = None, # RUF013
157 |+ arg2: Optional[Union[int, float]] = None, # RUF013
158 158 | arg3: Literal[1, 2, 3] = None, # RUF013
159 159 | ):
160 160 | pass
RUF013_0.py:158:11: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
156 | arg1: int = None, # RUF011
157 | arg2: Union[int, float] = None, # RUF011
158 | arg3: Literal[1, 2, 3] = None, # RUF011
156 | arg1: int = None, # RUF013
157 | arg2: Union[int, float] = None, # RUF013
158 | arg3: Literal[1, 2, 3] = None, # RUF013
| ^^^^^^^^^^^^^^^^ RUF013
159 | ):
160 | pass
@@ -289,17 +289,17 @@ RUF013_0.py:158:11: RUF013 [*] PEP 484 prohibits implicit `Optional`
Suggested fix
155 155 | def f(
156 156 | arg1: int = None, # RUF011
157 157 | arg2: Union[int, float] = None, # RUF011
158 |- arg3: Literal[1, 2, 3] = None, # RUF011
158 |+ arg3: Optional[Literal[1, 2, 3]] = None, # RUF011
156 156 | arg1: int = None, # RUF013
157 157 | arg2: Union[int, float] = None, # RUF013
158 |- arg3: Literal[1, 2, 3] = None, # RUF013
158 |+ arg3: Optional[Literal[1, 2, 3]] = None, # RUF013
159 159 | ):
160 160 | pass
161 161 |
RUF013_0.py:186:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
186 | def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF011
186 | def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013
187 | pass
|
@@ -309,10 +309,72 @@ RUF013_0.py:186:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
183 183 | pass
184 184 |
185 185 |
186 |-def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF011
186 |+def f(arg: Optional[Union[Annotated[int, ...], Union[str, bytes]]] = None): # RUF011
186 |-def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013
186 |+def f(arg: Optional[Union[Annotated[int, ...], Union[str, bytes]]] = None): # RUF013
187 187 | pass
188 188 |
189 189 |
RUF013_0.py:193:13: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
193 | def f(arg: "int" = None): # RUF013
| ^^^ RUF013
194 | pass
|
= help: Convert to `Optional[T]`
Suggested fix
190 190 | # Quoted
191 191 |
192 192 |
193 |-def f(arg: "int" = None): # RUF013
193 |+def f(arg: "Optional[int]" = None): # RUF013
194 194 | pass
195 195 |
196 196 |
RUF013_0.py:197:13: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
197 | def f(arg: "str" = None): # RUF013
| ^^^ RUF013
198 | pass
|
= help: Convert to `Optional[T]`
Suggested fix
194 194 | pass
195 195 |
196 196 |
197 |-def f(arg: "str" = None): # RUF013
197 |+def f(arg: "Optional[str]" = None): # RUF013
198 198 | pass
199 199 |
200 200 |
RUF013_0.py:201:12: RUF013 PEP 484 prohibits implicit `Optional`
|
201 | def f(arg: "st" "r" = None): # RUF013
| ^^^^^^^^ RUF013
202 | pass
|
= help: Convert to `Optional[T]`
RUF013_0.py:209:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
209 | def f(arg: Union["int", "str"] = None): # RUF013
| ^^^^^^^^^^^^^^^^^^^ RUF013
210 | pass
|
= help: Convert to `Optional[T]`
Suggested fix
206 206 | pass
207 207 |
208 208 |
209 |-def f(arg: Union["int", "str"] = None): # RUF013
209 |+def f(arg: Optional[Union["int", "str"]] = None): # RUF013
210 210 | pass
211 211 |
212 212 |

View File

@@ -20,33 +20,33 @@ RUF012.py:10:26: RUF012 Mutable class attributes should be annotated with `typin
12 | class_variable: typing.ClassVar[list[int]] = []
|
RUF012.py:16:34: RUF012 Mutable class attributes should be annotated with `typing.ClassVar`
RUF012.py:17:34: RUF012 Mutable class attributes should be annotated with `typing.ClassVar`
|
15 | class B:
16 | mutable_default: list[int] = []
16 | class B:
17 | mutable_default: list[int] = []
| ^^ RUF012
17 | immutable_annotation: Sequence[int] = []
18 | without_annotation = []
18 | immutable_annotation: Sequence[int] = []
19 | without_annotation = []
|
RUF012.py:18:26: RUF012 Mutable class attributes should be annotated with `typing.ClassVar`
RUF012.py:19:26: RUF012 Mutable class attributes should be annotated with `typing.ClassVar`
|
16 | mutable_default: list[int] = []
17 | immutable_annotation: Sequence[int] = []
18 | without_annotation = []
17 | mutable_default: list[int] = []
18 | immutable_annotation: Sequence[int] = []
19 | without_annotation = []
| ^^ RUF012
19 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
20 | class_variable: ClassVar[list[int]] = []
20 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
21 | class_variable: ClassVar[list[int]] = []
|
RUF012.py:30:26: RUF012 Mutable class attributes should be annotated with `typing.ClassVar`
RUF012.py:32:26: RUF012 Mutable class attributes should be annotated with `typing.ClassVar`
|
28 | mutable_default: list[int] = []
29 | immutable_annotation: Sequence[int] = []
30 | without_annotation = []
30 | mutable_default: list[int] = []
31 | immutable_annotation: Sequence[int] = []
32 | without_annotation = []
| ^^ RUF012
31 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
32 | perfectly_fine: list[int] = field(default_factory=list)
33 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
34 | perfectly_fine: list[int] = field(default_factory=list)
|

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/ruff/mod.rs
---
RUF013_0.py:21:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
21 | def f(arg: int = None): # RUF011
21 | def f(arg: int = None): # RUF013
| ^^^ RUF013
22 | pass
|
@@ -13,15 +13,15 @@ RUF013_0.py:21:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
18 18 | pass
19 19 |
20 20 |
21 |-def f(arg: int = None): # RUF011
21 |+def f(arg: int | None = None): # RUF011
21 |-def f(arg: int = None): # RUF013
21 |+def f(arg: int | None = None): # RUF013
22 22 | pass
23 23 |
24 24 |
RUF013_0.py:25:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
25 | def f(arg: str = None): # RUF011
25 | def f(arg: str = None): # RUF013
| ^^^ RUF013
26 | pass
|
@@ -31,15 +31,15 @@ RUF013_0.py:25:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
22 22 | pass
23 23 |
24 24 |
25 |-def f(arg: str = None): # RUF011
25 |+def f(arg: str | None = None): # RUF011
25 |-def f(arg: str = None): # RUF013
25 |+def f(arg: str | None = None): # RUF013
26 26 | pass
27 27 |
28 28 |
RUF013_0.py:29:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
29 | def f(arg: typing.List[str] = None): # RUF011
29 | def f(arg: typing.List[str] = None): # RUF013
| ^^^^^^^^^^^^^^^^ RUF013
30 | pass
|
@@ -49,15 +49,15 @@ RUF013_0.py:29:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
26 26 | pass
27 27 |
28 28 |
29 |-def f(arg: typing.List[str] = None): # RUF011
29 |+def f(arg: typing.List[str] | None = None): # RUF011
29 |-def f(arg: typing.List[str] = None): # RUF013
29 |+def f(arg: typing.List[str] | None = None): # RUF013
30 30 | pass
31 31 |
32 32 |
RUF013_0.py:33:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
33 | def f(arg: Tuple[str] = None): # RUF011
33 | def f(arg: Tuple[str] = None): # RUF013
| ^^^^^^^^^^ RUF013
34 | pass
|
@@ -67,15 +67,15 @@ RUF013_0.py:33:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
30 30 | pass
31 31 |
32 32 |
33 |-def f(arg: Tuple[str] = None): # RUF011
33 |+def f(arg: Tuple[str] | None = None): # RUF011
33 |-def f(arg: Tuple[str] = None): # RUF013
33 |+def f(arg: Tuple[str] | None = None): # RUF013
34 34 | pass
35 35 |
36 36 |
RUF013_0.py:67:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
67 | def f(arg: Union = None): # RUF011
67 | def f(arg: Union = None): # RUF013
| ^^^^^ RUF013
68 | pass
|
@@ -85,15 +85,15 @@ RUF013_0.py:67:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
64 64 | pass
65 65 |
66 66 |
67 |-def f(arg: Union = None): # RUF011
67 |+def f(arg: Union | None = None): # RUF011
67 |-def f(arg: Union = None): # RUF013
67 |+def f(arg: Union | None = None): # RUF013
68 68 | pass
69 69 |
70 70 |
RUF013_0.py:71:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
71 | def f(arg: Union[int, str] = None): # RUF011
71 | def f(arg: Union[int, str] = None): # RUF013
| ^^^^^^^^^^^^^^^ RUF013
72 | pass
|
@@ -103,15 +103,15 @@ RUF013_0.py:71:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
68 68 | pass
69 69 |
70 70 |
71 |-def f(arg: Union[int, str] = None): # RUF011
71 |+def f(arg: Union[int, str] | None = None): # RUF011
71 |-def f(arg: Union[int, str] = None): # RUF013
71 |+def f(arg: Union[int, str] | None = None): # RUF013
72 72 | pass
73 73 |
74 74 |
RUF013_0.py:75:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
75 | def f(arg: typing.Union[int, str] = None): # RUF011
75 | def f(arg: typing.Union[int, str] = None): # RUF013
| ^^^^^^^^^^^^^^^^^^^^^^ RUF013
76 | pass
|
@@ -121,15 +121,15 @@ RUF013_0.py:75:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
72 72 | pass
73 73 |
74 74 |
75 |-def f(arg: typing.Union[int, str] = None): # RUF011
75 |+def f(arg: typing.Union[int, str] | None = None): # RUF011
75 |-def f(arg: typing.Union[int, str] = None): # RUF013
75 |+def f(arg: typing.Union[int, str] | None = None): # RUF013
76 76 | pass
77 77 |
78 78 |
RUF013_0.py:94:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
94 | def f(arg: int | float = None): # RUF011
94 | def f(arg: int | float = None): # RUF013
| ^^^^^^^^^^^ RUF013
95 | pass
|
@@ -139,15 +139,15 @@ RUF013_0.py:94:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
91 91 | pass
92 92 |
93 93 |
94 |-def f(arg: int | float = None): # RUF011
94 |+def f(arg: int | float | None = None): # RUF011
94 |-def f(arg: int | float = None): # RUF013
94 |+def f(arg: int | float | None = None): # RUF013
95 95 | pass
96 96 |
97 97 |
RUF013_0.py:98:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
98 | def f(arg: int | float | str | bytes = None): # RUF011
98 | def f(arg: int | float | str | bytes = None): # RUF013
| ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013
99 | pass
|
@@ -157,15 +157,15 @@ RUF013_0.py:98:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
95 95 | pass
96 96 |
97 97 |
98 |-def f(arg: int | float | str | bytes = None): # RUF011
98 |+def f(arg: int | float | str | bytes | None = None): # RUF011
98 |-def f(arg: int | float | str | bytes = None): # RUF013
98 |+def f(arg: int | float | str | bytes | None = None): # RUF013
99 99 | pass
100 100 |
101 101 |
RUF013_0.py:113:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
113 | def f(arg: Literal[1, "foo"] = None): # RUF011
113 | def f(arg: Literal[1, "foo"] = None): # RUF013
| ^^^^^^^^^^^^^^^^^ RUF013
114 | pass
|
@@ -175,15 +175,15 @@ RUF013_0.py:113:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
110 110 | pass
111 111 |
112 112 |
113 |-def f(arg: Literal[1, "foo"] = None): # RUF011
113 |+def f(arg: Literal[1, "foo"] | None = None): # RUF011
113 |-def f(arg: Literal[1, "foo"] = None): # RUF013
113 |+def f(arg: Literal[1, "foo"] | None = None): # RUF013
114 114 | pass
115 115 |
116 116 |
RUF013_0.py:117:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
117 | def f(arg: typing.Literal[1, "foo", True] = None): # RUF011
117 | def f(arg: typing.Literal[1, "foo", True] = None): # RUF013
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013
118 | pass
|
@@ -193,15 +193,15 @@ RUF013_0.py:117:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
114 114 | pass
115 115 |
116 116 |
117 |-def f(arg: typing.Literal[1, "foo", True] = None): # RUF011
117 |+def f(arg: typing.Literal[1, "foo", True] | None = None): # RUF011
117 |-def f(arg: typing.Literal[1, "foo", True] = None): # RUF013
117 |+def f(arg: typing.Literal[1, "foo", True] | None = None): # RUF013
118 118 | pass
119 119 |
120 120 |
RUF013_0.py:136:22: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
136 | def f(arg: Annotated[int, ...] = None): # RUF011
136 | def f(arg: Annotated[int, ...] = None): # RUF013
| ^^^ RUF013
137 | pass
|
@@ -211,15 +211,15 @@ RUF013_0.py:136:22: RUF013 [*] PEP 484 prohibits implicit `Optional`
133 133 | pass
134 134 |
135 135 |
136 |-def f(arg: Annotated[int, ...] = None): # RUF011
136 |+def f(arg: Annotated[int | None, ...] = None): # RUF011
136 |-def f(arg: Annotated[int, ...] = None): # RUF013
136 |+def f(arg: Annotated[int | None, ...] = None): # RUF013
137 137 | pass
138 138 |
139 139 |
RUF013_0.py:140:32: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
140 | def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF011
140 | def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013
| ^^^^^^^^^ RUF013
141 | pass
|
@@ -229,8 +229,8 @@ RUF013_0.py:140:32: RUF013 [*] PEP 484 prohibits implicit `Optional`
137 137 | pass
138 138 |
139 139 |
140 |-def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF011
140 |+def f(arg: Annotated[Annotated[int | str | None, ...], ...] = None): # RUF011
140 |-def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013
140 |+def f(arg: Annotated[Annotated[int | str | None, ...], ...] = None): # RUF013
141 141 | pass
142 142 |
143 143 |
@@ -238,10 +238,10 @@ RUF013_0.py:140:32: RUF013 [*] PEP 484 prohibits implicit `Optional`
RUF013_0.py:156:11: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
155 | def f(
156 | arg1: int = None, # RUF011
156 | arg1: int = None, # RUF013
| ^^^ RUF013
157 | arg2: Union[int, float] = None, # RUF011
158 | arg3: Literal[1, 2, 3] = None, # RUF011
157 | arg2: Union[int, float] = None, # RUF013
158 | arg3: Literal[1, 2, 3] = None, # RUF013
|
= help: Convert to `T | None`
@@ -249,19 +249,19 @@ RUF013_0.py:156:11: RUF013 [*] PEP 484 prohibits implicit `Optional`
153 153 |
154 154 |
155 155 | def f(
156 |- arg1: int = None, # RUF011
156 |+ arg1: int | None = None, # RUF011
157 157 | arg2: Union[int, float] = None, # RUF011
158 158 | arg3: Literal[1, 2, 3] = None, # RUF011
156 |- arg1: int = None, # RUF013
156 |+ arg1: int | None = None, # RUF013
157 157 | arg2: Union[int, float] = None, # RUF013
158 158 | arg3: Literal[1, 2, 3] = None, # RUF013
159 159 | ):
RUF013_0.py:157:11: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
155 | def f(
156 | arg1: int = None, # RUF011
157 | arg2: Union[int, float] = None, # RUF011
156 | arg1: int = None, # RUF013
157 | arg2: Union[int, float] = None, # RUF013
| ^^^^^^^^^^^^^^^^^ RUF013
158 | arg3: Literal[1, 2, 3] = None, # RUF011
158 | arg3: Literal[1, 2, 3] = None, # RUF013
159 | ):
|
= help: Convert to `T | None`
@@ -269,18 +269,18 @@ RUF013_0.py:157:11: RUF013 [*] PEP 484 prohibits implicit `Optional`
Suggested fix
154 154 |
155 155 | def f(
156 156 | arg1: int = None, # RUF011
157 |- arg2: Union[int, float] = None, # RUF011
157 |+ arg2: Union[int, float] | None = None, # RUF011
158 158 | arg3: Literal[1, 2, 3] = None, # RUF011
156 156 | arg1: int = None, # RUF013
157 |- arg2: Union[int, float] = None, # RUF013
157 |+ arg2: Union[int, float] | None = None, # RUF013
158 158 | arg3: Literal[1, 2, 3] = None, # RUF013
159 159 | ):
160 160 | pass
RUF013_0.py:158:11: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
156 | arg1: int = None, # RUF011
157 | arg2: Union[int, float] = None, # RUF011
158 | arg3: Literal[1, 2, 3] = None, # RUF011
156 | arg1: int = None, # RUF013
157 | arg2: Union[int, float] = None, # RUF013
158 | arg3: Literal[1, 2, 3] = None, # RUF013
| ^^^^^^^^^^^^^^^^ RUF013
159 | ):
160 | pass
@@ -289,17 +289,17 @@ RUF013_0.py:158:11: RUF013 [*] PEP 484 prohibits implicit `Optional`
Suggested fix
155 155 | def f(
156 156 | arg1: int = None, # RUF011
157 157 | arg2: Union[int, float] = None, # RUF011
158 |- arg3: Literal[1, 2, 3] = None, # RUF011
158 |+ arg3: Literal[1, 2, 3] | None = None, # RUF011
156 156 | arg1: int = None, # RUF013
157 157 | arg2: Union[int, float] = None, # RUF013
158 |- arg3: Literal[1, 2, 3] = None, # RUF013
158 |+ arg3: Literal[1, 2, 3] | None = None, # RUF013
159 159 | ):
160 160 | pass
161 161 |
RUF013_0.py:186:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
186 | def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF011
186 | def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013
187 | pass
|
@@ -309,10 +309,72 @@ RUF013_0.py:186:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
183 183 | pass
184 184 |
185 185 |
186 |-def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF011
186 |+def f(arg: Union[Annotated[int, ...], Union[str, bytes]] | None = None): # RUF011
186 |-def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013
186 |+def f(arg: Union[Annotated[int, ...], Union[str, bytes]] | None = None): # RUF013
187 187 | pass
188 188 |
189 189 |
RUF013_0.py:193:13: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
193 | def f(arg: "int" = None): # RUF013
| ^^^ RUF013
194 | pass
|
= help: Convert to `T | None`
Suggested fix
190 190 | # Quoted
191 191 |
192 192 |
193 |-def f(arg: "int" = None): # RUF013
193 |+def f(arg: "int | None" = None): # RUF013
194 194 | pass
195 195 |
196 196 |
RUF013_0.py:197:13: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
197 | def f(arg: "str" = None): # RUF013
| ^^^ RUF013
198 | pass
|
= help: Convert to `T | None`
Suggested fix
194 194 | pass
195 195 |
196 196 |
197 |-def f(arg: "str" = None): # RUF013
197 |+def f(arg: "str | None" = None): # RUF013
198 198 | pass
199 199 |
200 200 |
RUF013_0.py:201:12: RUF013 PEP 484 prohibits implicit `Optional`
|
201 | def f(arg: "st" "r" = None): # RUF013
| ^^^^^^^^ RUF013
202 | pass
|
= help: Convert to `T | None`
RUF013_0.py:209:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
209 | def f(arg: Union["int", "str"] = None): # RUF013
| ^^^^^^^^^^^^^^^^^^^ RUF013
210 | pass
|
= help: Convert to `T | None`
Suggested fix
206 206 | pass
207 207 |
208 208 |
209 |-def f(arg: Union["int", "str"] = None): # RUF013
209 |+def f(arg: Union["int", "str"] | None = None): # RUF013
210 210 | pass
211 211 |
212 212 |

View File

@@ -1,91 +1,5 @@
# Ruff Micro-benchmarks
# Ruff Benchmarks
Benchmarks for the different Ruff-tools.
The `ruff_benchmark` crate benchmarks the linter and the formatter on individual files.
## Run Benchmark
You can run the benchmarks with
```shell
cargo benchmark
```
## Benchmark driven Development
You can use `--save-baseline=<name>` to store an initial baseline benchmark (e.g. on `main`) and
then use `--benchmark=<name>` to compare against that benchmark. Criterion will print a message
telling you if the benchmark improved/regressed compared to that baseline.
```shell
# Run once on your "baseline" code
cargo benchmark --save-baseline=main
# Then iterate with
cargo benchmark --baseline=main
```
## PR Summary
You can use `--save-baseline` and `critcmp` to get a pretty comparison between two recordings.
This is useful to illustrate the improvements of a PR.
```shell
# On main
cargo benchmark --save-baseline=main
# After applying your changes
cargo benchmark --save-baseline=pr
critcmp main pr
```
You must install [`critcmp`](https://github.com/BurntSushi/critcmp) for the comparison.
```bash
cargo install critcmp
```
## Tips
- Use `cargo benchmark <filter>` to only run specific benchmarks. For example: `cargo benchmark linter/pydantic`
to only run the pydantic tests.
- Use `cargo benchmark --quiet` for a more cleaned up output (without statistical relevance)
- Use `cargo benchmark --quick` to get faster results (more prone to noise)
## Profiling
### Linux
Install `perf` and build `ruff_benchmark` with the `release-debug` profile and then run it with perf
```shell
cargo bench -p ruff_benchmark --no-run --profile=release-debug && perf record -g -F 9999 cargo bench -p ruff_benchmark --profile=release-debug -- --profile-time=1
```
Then convert the recorded profile
```shell
perf script -F +pid > /tmp/test.perf
```
You can now view the converted file with [firefox profiler](https://profiler.firefox.com/)
You can find a more in-depth guide [here](https://profiler.firefox.com/docs/#/./guide-perf-profiling)
### Mac
Install [`cargo-instruments`](https://crates.io/crates/cargo-instruments):
```shell
cargo install cargo-instruments
```
Then run the profiler with
```shell
cargo instruments -t time --bench linter --profile release-debug -p ruff_benchmark -- --profile-time=1
```
- `-t`: Specifies what to profile. Useful options are `time` to profile the wall time and `alloc`
for profiling the allocations.
- You may want to pass an additional filter to run a single test file
See [CONTRIBUTING.md](../../CONTRIBUTING.md) on how to use these benchmarks.

View File

@@ -81,17 +81,18 @@ pub(crate) fn run(
// Load the caches.
let caches = bool::from(cache).then(|| {
package_roots
.values()
.flatten()
.dedup()
.map(|package_root| {
let settings = resolver.resolve_all(package_root, pyproject_config);
.iter()
.map(|(package, package_root)| package_root.unwrap_or(package))
.unique()
.par_bridge()
.map(|cache_root| {
let settings = resolver.resolve_all(cache_root, pyproject_config);
let cache = Cache::open(
&settings.cli.cache_dir,
package_root.to_path_buf(),
cache_root.to_path_buf(),
&settings.lib,
);
(&**package_root, cache)
(cache_root, cache)
})
.collect::<HashMap<&Path, Cache>>()
});
@@ -109,8 +110,16 @@ pub(crate) fn run(
.and_then(|package| *package);
let settings = resolver.resolve_all(path, pyproject_config);
let package_root = package.unwrap_or_else(|| path.parent().unwrap_or(path));
let cache = caches.as_ref().and_then(|caches| caches.get(&package_root));
let cache_root = package.unwrap_or_else(|| path.parent().unwrap_or(path));
let cache = caches.as_ref().and_then(|caches| {
if let Some(cache) = caches.get(&cache_root) {
Some(cache)
} else {
debug!("No cache found for {}", cache_root.display());
None
}
});
lint_path(path, package, settings, cache, noqa, autofix).map_err(|e| {
(Some(path.to_owned()), {

View File

@@ -528,7 +528,22 @@ impl<Context> Format<Context> for LineSuffixBoundary {
/// use ruff_formatter::prelude::*;
/// use ruff_formatter::{format, write, LineWidth};
///
/// enum SomeLabelId {}
/// #[derive(Debug, Copy, Clone)]
/// enum MyLabels {
/// Main
/// }
///
/// impl tag::LabelDefinition for MyLabels {
/// fn value(&self) -> u64 {
/// *self as u64
/// }
///
/// fn name(&self) -> &'static str {
/// match self {
/// Self::Main => "Main"
/// }
/// }
/// }
///
/// # fn main() -> FormatResult<()> {
/// let formatted = format!(
@@ -537,24 +552,24 @@ impl<Context> Format<Context> for LineSuffixBoundary {
/// let mut recording = f.start_recording();
/// write!(recording, [
/// labelled(
/// LabelId::of::<SomeLabelId>(),
/// LabelId::of(MyLabels::Main),
/// &text("'I have a label'")
/// )
/// ])?;
///
/// let recorded = recording.stop();
///
/// let is_labelled = recorded.first().map_or(false, |element| element.has_label(LabelId::of::<SomeLabelId>()));
/// let is_labelled = recorded.first().map_or(false, |element| element.has_label(LabelId::of(MyLabels::Main)));
///
/// if is_labelled {
/// write!(f, [text(" has label SomeLabelId")])
/// write!(f, [text(" has label `Main`")])
/// } else {
/// write!(f, [text(" doesn't have label SomeLabelId")])
/// write!(f, [text(" doesn't have label `Main`")])
/// }
/// })]
/// )?;
///
/// assert_eq!("'I have a label' has label SomeLabelId", formatted.print()?.as_code());
/// assert_eq!("'I have a label' has label `Main`", formatted.print()?.as_code());
/// # Ok(())
/// # }
/// ```

View File

@@ -1,8 +1,5 @@
use crate::format_element::PrintMode;
use crate::{GroupId, TextSize};
#[cfg(debug_assertions)]
use std::any::type_name;
use std::any::TypeId;
use std::cell::Cell;
use std::num::NonZeroU8;
@@ -235,37 +232,48 @@ impl Align {
}
}
#[derive(Eq, PartialEq, Copy, Clone)]
#[derive(Debug, Eq, Copy, Clone)]
pub struct LabelId {
id: TypeId,
value: u64,
#[cfg(debug_assertions)]
label: &'static str,
name: &'static str,
}
#[cfg(debug_assertions)]
impl std::fmt::Debug for LabelId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.label)
}
}
impl PartialEq for LabelId {
fn eq(&self, other: &Self) -> bool {
let is_equal = self.value == other.value;
#[cfg(not(debug_assertions))]
impl std::fmt::Debug for LabelId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::write!(f, "#{:?}", self.id)
#[cfg(debug_assertions)]
{
if is_equal {
assert_eq!(self.name, other.name, "Two `LabelId`s with different names have the same `value`. Are you mixing labels of two different `LabelDefinition` or are the values returned by the `LabelDefinition` not unique?");
}
}
is_equal
}
}
impl LabelId {
pub fn of<T: ?Sized + 'static>() -> Self {
pub fn of<T: LabelDefinition>(label: T) -> Self {
Self {
id: TypeId::of::<T>(),
value: label.value(),
#[cfg(debug_assertions)]
label: type_name::<T>(),
name: label.name(),
}
}
}
/// Defines the valid labels of a language. You want to have at most one implementation per formatter
/// project.
pub trait LabelDefinition {
/// Returns the `u64` uniquely identifying this specific label.
fn value(&self) -> u64;
/// Returns the name of the label that is shown in debug builds.
fn name(&self) -> &'static str;
}
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub enum VerbatimKind {
Bogus,

View File

@@ -192,7 +192,8 @@ fn rules_by_prefix(
for (code, rule) in rules {
// Nursery rules have to be explicitly selected, so we ignore them when looking at
// prefixes.
// prefix-level selectors (e.g., `--select SIM10`), but add the rule itself under
// its fully-qualified code (e.g., `--select SIM101`).
if is_nursery(&rule.group) {
rules_by_prefix.insert(code.clone(), vec![(rule.path.clone(), rule.attrs.clone())]);
continue;
@@ -329,10 +330,17 @@ fn generate_iter_impl(
) -> TokenStream {
let mut linter_into_iter_match_arms = quote!();
for (linter, map) in linter_to_rules {
let rule_paths = map.values().map(|Rule { attrs, path, .. }| {
let rule_name = path.segments.last().unwrap();
quote!(#(#attrs)* Rule::#rule_name)
});
let rule_paths = map
.values()
.filter(|rule| {
// Nursery rules have to be explicitly selected, so we ignore them when looking at
// linter-level selectors (e.g., `--select SIM`).
!is_nursery(&rule.group)
})
.map(|Rule { attrs, path, .. }| {
let rule_name = path.segments.last().unwrap();
quote!(#(#attrs)* Rule::#rule_name)
});
linter_into_iter_match_arms.extend(quote! {
Linter::#linter => vec![#(#rule_paths,)*].into_iter(),
});

View File

@@ -2,7 +2,7 @@
pub mod preorder;
use rustpython_ast::{ArgWithDefault, Decorator};
use rustpython_ast::Decorator;
use rustpython_parser::ast::{
self, Alias, Arg, Arguments, BoolOp, CmpOp, Comprehension, Constant, ExceptHandler, Expr,
ExprContext, Keyword, MatchCase, Operator, Pattern, Stmt, UnaryOp, WithItem,
@@ -61,9 +61,6 @@ pub trait Visitor<'a> {
fn visit_arg(&mut self, arg: &'a Arg) {
walk_arg(self, arg);
}
fn visit_arg_with_default(&mut self, arg_with_default: &'a ArgWithDefault) {
walk_arg_with_default(self, arg_with_default);
}
fn visit_keyword(&mut self, keyword: &'a Keyword) {
walk_keyword(self, keyword);
}
@@ -99,10 +96,10 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
returns,
..
}) => {
visitor.visit_arguments(args);
for decorator in decorator_list {
visitor.visit_decorator(decorator);
}
visitor.visit_arguments(args);
for expr in returns {
visitor.visit_annotation(expr);
}
@@ -115,10 +112,10 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
returns,
..
}) => {
visitor.visit_arguments(args);
for decorator in decorator_list {
visitor.visit_decorator(decorator);
}
visitor.visit_arguments(args);
for expr in returns {
visitor.visit_annotation(expr);
}
@@ -131,15 +128,15 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
decorator_list,
..
}) => {
for decorator in decorator_list {
visitor.visit_decorator(decorator);
}
for expr in bases {
visitor.visit_expr(expr);
}
for keyword in keywords {
visitor.visit_keyword(keyword);
}
for decorator in decorator_list {
visitor.visit_decorator(decorator);
}
visitor.visit_body(body);
}
Stmt::Return(ast::StmtReturn {
@@ -180,10 +177,10 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
value,
..
}) => {
visitor.visit_annotation(annotation);
if let Some(expr) = value {
visitor.visit_expr(expr);
}
visitor.visit_annotation(annotation);
visitor.visit_expr(target);
}
Stmt::For(ast::StmtFor {
@@ -606,17 +603,34 @@ pub fn walk_except_handler<'a, V: Visitor<'a> + ?Sized>(
}
pub fn walk_arguments<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, arguments: &'a Arguments) {
// Defaults are evaluated before annotations.
for arg in &arguments.posonlyargs {
visitor.visit_arg_with_default(arg);
if let Some(default) = &arg.default {
visitor.visit_expr(default);
}
}
for arg in &arguments.args {
visitor.visit_arg_with_default(arg);
if let Some(default) = &arg.default {
visitor.visit_expr(default);
}
}
for arg in &arguments.kwonlyargs {
if let Some(default) = &arg.default {
visitor.visit_expr(default);
}
}
for arg in &arguments.posonlyargs {
visitor.visit_arg(&arg.def);
}
for arg in &arguments.args {
visitor.visit_arg(&arg.def);
}
if let Some(arg) = &arguments.vararg {
visitor.visit_arg(arg);
}
for arg in &arguments.kwonlyargs {
visitor.visit_arg_with_default(arg);
visitor.visit_arg(&arg.def);
}
if let Some(arg) = &arguments.kwarg {
visitor.visit_arg(arg);
@@ -629,16 +643,6 @@ pub fn walk_arg<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, arg: &'a Arg) {
}
}
pub fn walk_arg_with_default<'a, V: Visitor<'a> + ?Sized>(
visitor: &mut V,
arg_with_default: &'a ArgWithDefault,
) {
visitor.visit_arg(&arg_with_default.def);
if let Some(expr) = &arg_with_default.default {
visitor.visit_expr(expr);
}
}
pub fn walk_keyword<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, keyword: &'a Keyword) {
visitor.visit_expr(&keyword.value);
}

View File

@@ -0,0 +1,29 @@
(
a
# comment
.b # trailing comment
)
(
a
# comment
.b # trailing dot comment # trailing identifier comment
)
(
a
# comment
.b # trailing identifier comment
)
(
a
# comment
. # trailing dot comment
# in between
b # trailing identifier comment
)
aaaaaaaaaaaaaaaaaaaaa.lllllllllllllllllllllllllllloooooooooong.chaaaaaaaaaaaaaaaaaaaaaaiiiiiiiiiiiiiiiiiiiiiiinnnnnnnn.ooooooooooooooooooooooooofffffffff.aaaaaaaaaattr

View File

@@ -52,3 +52,138 @@ if (
ccccccccccc
):
pass
# Left only breaks
if [
aaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbb,
cccccccccccccccccccc,
dddddddddddddddddddd,
eeeeeeeeee,
] & aaaaaaaaaaaaaaaaaaaaaaaaaa:
...
if [
aaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbb,
cccccccccccccccccccc,
dddddddddddddddddddd,
eeeeeeeeee,
] & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
...
# Right only can break
if aaaaaaaaaaaaaaaaaaaaaaaaaa & [
aaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbb,
cccccccccccccccccccc,
dddddddddddddddddddd,
eeeeeeeeee,
]:
...
if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & [
aaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbb,
cccccccccccccccccccc,
dddddddddddddddddddd,
eeeeeeeeee,
]:
...
# Left or right can break
if [2222, 333] & [
aaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbb,
cccccccccccccccccccc,
dddddddddddddddddddd,
eeeeeeeeee,
]:
...
if [
aaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbb,
cccccccccccccccccccc,
dddddddddddddddddddd,
eeeeeeeeee,
] & [2222, 333]:
...
if [
aaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbb,
cccccccccccccccccccc,
dddddddddddddddddddd,
eeeeeeeeee,
] & [fffffffffffffffff, gggggggggggggggggggg, hhhhhhhhhhhhhhhhhhhhh, iiiiiiiiiiiiiiii, jjjjjjjjjjjjj]:
...
if (
# comment
[
aaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbb,
cccccccccccccccccccc,
dddddddddddddddddddd,
eeeeeeeeee,
]
) & [
fffffffffffffffff,
gggggggggggggggggggg,
hhhhhhhhhhhhhhhhhhhhh,
iiiiiiiiiiiiiiii,
jjjjjjjjjjjjj,
]:
pass
...
# Nesting
if (aaaa + b) & [
fffffffffffffffff,
gggggggggggggggggggg,
hhhhhhhhhhhhhhhhhhhhh,
iiiiiiiiiiiiiiii,
jjjjjjjjjjjjj,
]:
...
if [
fffffffffffffffff,
gggggggggggggggggggg,
hhhhhhhhhhhhhhhhhhhhh,
iiiiiiiiiiiiiiii,
jjjjjjjjjjjjj,
] & (a + b):
...
if [
fffffffffffffffff,
gggggggggggggggggggg,
hhhhhhhhhhhhhhhhhhhhh,
iiiiiiiiiiiiiiii,
jjjjjjjjjjjjj,
] & (
# comment
a
+ b
):
...
if (
[
fffffffffffffffff,
gggggggggggggggggggg,
hhhhhhhhhhhhhhhhhhhhh,
iiiiiiiiiiiiiiii,
jjjjjjjjjjjjj,
]
&
# comment
a + b
):
...

View File

@@ -0,0 +1,64 @@
if (
self._proc
# has the child process finished?
and self._returncode
# the child process has finished, but the
# transport hasn't been notified yet?
and self._proc.poll()
):
pass
if (
self._proc
and self._returncode
and self._proc.poll()
and self._proc
and self._returncode
and self._proc.poll()
):
...
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
...
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaas
and aaaaaaaaaaaaaaaaa
):
...
if [2222, 333] and [
aaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbb,
cccccccccccccccccccc,
dddddddddddddddddddd,
eeeeeeeeee,
]:
...
if [
aaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbb,
cccccccccccccccccccc,
dddddddddddddddddddd,
eeeeeeeeee,
] and [2222, 333]:
pass
# Break right only applies for boolean operations with a left and right side
if (
aaaaaaaaaaaaaaaaaaaaaaaaaa
and bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
and ccccccccccccccccc
and [dddddddddddddd, eeeeeeeeee, fffffffffffffff]
):
pass

View File

@@ -0,0 +1,82 @@
# Handle comments both when lower and upper exist and when they don't
a1 = "a"[
# a
1 # b
: # c
2 # d
]
a2 = "a"[
# a
# b
: # c
# d
]
# Check all places where comments can exist
b1 = "b"[ # a
# b
1 # c
# d
: # e
# f
2 # g
# h
: # i
# j
3 # k
# l
]
# Handle the spacing from the colon correctly with upper leading comments
c1 = "c"[
1
: # e
# f
2
]
c2 = "c"[
1
: # e
2
]
c3 = "c"[
1
:
# f
2
]
c4 = "c"[
1
: # f
2
]
# End of line comments
d1 = "d"[ # comment
:
]
d2 = "d"[ # comment
1:
]
d3 = "d"[
1 # comment
:
]
# Spacing around the colon(s)
def a():
...
e00 = "e"[:]
e01 = "e"[:1]
e02 = "e"[: a()]
e10 = "e"[1:]
e11 = "e"[1:1]
e12 = "e"[1 : a()]
e20 = "e"[a() :]
e21 = "e"[a() : 1]
e22 = "e"[a() : a()]
e200 = "e"[a() :: ]
e201 = "e"[a() :: 1]
e202 = "e"[a() :: a()]
e210 = "e"[a() : 1 :]

View File

@@ -0,0 +1,138 @@
if not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:
pass
a = True
not a
b = 10
-b
+b
## Leading operand comments
if not (
# comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if ~(
# comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb):
pass
if -(
# comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb):
pass
if +(
# comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb):
pass
if (
not
# comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if (
~
# comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb):
pass
if (
-
# comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb):
pass
if (
+
# comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb):
pass
## Parentheses
if (
# unary comment
not
# operand comment
(
# comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
)
):
pass
if (not (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
)
):
pass
if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & (not (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
)
):
pass
if (
not (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
)
& aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
pass
## Trailing operator comments
if (
not # comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if (
~ # comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if (
- # comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if (
+ # comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
## Varia
if not \
a:
pass

View File

@@ -0,0 +1,34 @@
for x in y: # trailing test comment
pass # trailing last statement comment
# trailing for body comment
# leading else comment
else: # trailing else comment
pass
# trailing else body comment
for aVeryLongNameThatSpillsOverToTheNextLineBecauseItIsExtremelyLongAndGoesOnAndOnAndOnAndOnAndOnAndOnAndOnAndOnAndOn in anotherVeryLongNameThatSpillsOverToTheNextLineBecauseItIsExtremelyLongAndGoesOnAndOnAndOnAndOnAndOnAndOnAndOnAndOnAndOn: # trailing comment
pass
else:
...
for (
x,
y,
) in z: # comment
...
# remove brackets around x,y but keep them around z,w
for (x, y) in (z, w):
...
# type comment
for x in (): # type: int
...

View File

@@ -97,3 +97,137 @@ def foo(
b=3 + 2 # comment
):
...
# Comments on the slash or the star, both of which don't have a node
def f11(
a,
# positional only comment, leading
/, # positional only comment, trailing
b,
):
pass
def f12(
a=1,
# positional only comment, leading
/, # positional only comment, trailing
b=2,
):
pass
def f13(
a,
# positional only comment, leading
/, # positional only comment, trailing
):
pass
def f21(
a=1,
# keyword only comment, leading
*, # keyword only comment, trailing
b=2,
):
pass
def f22(
a,
# keyword only comment, leading
*, # keyword only comment, trailing
b,
):
pass
def f23(
a,
# keyword only comment, leading
*args, # keyword only comment, trailing
b,
):
pass
def f24(
# keyword only comment, leading
*, # keyword only comment, trailing
a
):
pass
def f31(
a=1,
# positional only comment, leading
/, # positional only comment, trailing
b=2,
# keyword only comment, leading
*, # keyword only comment, trailing
c=3,
):
pass
def f32(
a,
# positional only comment, leading
/, # positional only comment, trailing
b,
# keyword only comment, leading
*, # keyword only comment, trailing
c,
):
pass
def f33(
a,
# positional only comment, leading
/, # positional only comment, trailing
# keyword only comment, leading
*args, # keyword only comment, trailing
c,
):
pass
def f34(
a,
# positional only comment, leading
/, # positional only comment, trailing
# keyword only comment, leading
*, # keyword only comment, trailing
c,
):
pass
def f35(
# keyword only comment, leading
*, # keyword only comment, trailing
c,
):
pass
# Multiple trailing comments
def f41(
a,
/ # 1
, # 2
# 3
* # 4
, # 5
c,
):
pass
# Multiple trailing comments strangely places. The goal here is only stable formatting,
# the comments are placed to strangely to keep their relative position intact
def f42(
a,
/ # 1
# 2
, # 3
# 4
* # 5
# 6
, # 7
c,
):
pass

View File

@@ -35,3 +35,30 @@ elif aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
else:
...
# Regression test: Don't drop the trailing comment by associating it with the elif
# instead of the else.
# Originally found in https://github.com/python/cpython/blob/ab3823a97bdeefb0266b3c8d493f7f6223ce3686/Lib/dataclasses.py#L539
if "if 1":
pass
elif "elif 1":
pass
# Don't drop this comment 1
x = 1
if "if 2":
pass
elif "elif 2":
pass
else:
pass
# Don't drop this comment 2
x = 2
if "if 3":
pass
else:
pass
# Don't drop this comment 3
x = 3

View File

@@ -26,7 +26,7 @@ impl Debug for DebugComment<'_> {
strut
.field("text", &self.comment.slice.text(self.source_code))
.field("position", &self.comment.position);
.field("position", &self.comment.line_position);
#[cfg(debug_assertions)]
strut.field("formatted", &self.comment.formatted.get());
@@ -177,7 +177,7 @@ impl Debug for DebugNodeCommentSlice<'_> {
#[cfg(test)]
mod tests {
use crate::comments::map::MultiMap;
use crate::comments::{CommentTextPosition, Comments, CommentsMap, SourceComment};
use crate::comments::{CommentLinePosition, Comments, CommentsMap, SourceComment};
use insta::assert_debug_snapshot;
use ruff_formatter::SourceCode;
use ruff_python_ast::node::AnyNode;
@@ -208,7 +208,7 @@ break;
continue_statement.as_ref().into(),
SourceComment::new(
source_code.slice(TextRange::at(TextSize::new(0), TextSize::new(17))),
CommentTextPosition::OwnLine,
CommentLinePosition::OwnLine,
),
);
@@ -216,7 +216,7 @@ break;
continue_statement.as_ref().into(),
SourceComment::new(
source_code.slice(TextRange::at(TextSize::new(28), TextSize::new(10))),
CommentTextPosition::EndOfLine,
CommentLinePosition::EndOfLine,
),
);
@@ -224,7 +224,7 @@ break;
break_statement.as_ref().into(),
SourceComment::new(
source_code.slice(TextRange::at(TextSize::new(39), TextSize::new(15))),
CommentTextPosition::OwnLine,
CommentLinePosition::OwnLine,
),
);

View File

@@ -136,7 +136,7 @@ impl Format<PyFormatContext<'_>> for FormatTrailingComments<'_> {
{
let slice = trailing.slice();
has_trailing_own_line_comment |= trailing.position().is_own_line();
has_trailing_own_line_comment |= trailing.line_position().is_own_line();
if has_trailing_own_line_comment {
let lines_before_comment = lines_before(slice.start(), f.context().contents());
@@ -208,7 +208,7 @@ impl Format<PyFormatContext<'_>> for FormatDanglingComments<'_> {
.iter()
.filter(|comment| comment.is_unformatted())
{
if first && comment.position().is_end_of_line() {
if first && comment.line_position().is_end_of_line() {
write!(f, [space(), space()])?;
}

View File

@@ -117,14 +117,14 @@ pub(crate) struct SourceComment {
slice: SourceCodeSlice,
/// Whether the comment has been formatted or not.
formatted: Cell<bool>,
position: CommentTextPosition,
line_position: CommentLinePosition,
}
impl SourceComment {
fn new(slice: SourceCodeSlice, position: CommentTextPosition) -> Self {
fn new(slice: SourceCodeSlice, position: CommentLinePosition) -> Self {
Self {
slice,
position,
line_position: position,
formatted: Cell::new(false),
}
}
@@ -135,8 +135,8 @@ impl SourceComment {
&self.slice
}
pub(crate) const fn position(&self) -> CommentTextPosition {
self.position
pub(crate) const fn line_position(&self) -> CommentLinePosition {
self.line_position
}
/// Marks the comment as formatted
@@ -163,7 +163,7 @@ impl SourceComment {
/// The position of a comment in the source text.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) enum CommentTextPosition {
pub(crate) enum CommentLinePosition {
/// A comment that is on the same line as the preceding token and is separated by at least one line break from the following token.
///
/// # Examples
@@ -176,7 +176,7 @@ pub(crate) enum CommentTextPosition {
/// ```
///
/// `# comment` is an end of line comments because it is separated by at least one line break from the following token `b`.
/// Comments that not only end, but also start on a new line are [`OwnLine`](CommentTextPosition::OwnLine) comments.
/// Comments that not only end, but also start on a new line are [`OwnLine`](CommentLinePosition::OwnLine) comments.
EndOfLine,
/// A Comment that is separated by at least one line break from the preceding token.
@@ -193,13 +193,13 @@ pub(crate) enum CommentTextPosition {
OwnLine,
}
impl CommentTextPosition {
impl CommentLinePosition {
pub(crate) const fn is_own_line(self) -> bool {
matches!(self, CommentTextPosition::OwnLine)
matches!(self, CommentLinePosition::OwnLine)
}
pub(crate) const fn is_end_of_line(self) -> bool {
matches!(self, CommentTextPosition::EndOfLine)
matches!(self, CommentLinePosition::EndOfLine)
}
}
@@ -335,7 +335,7 @@ impl<'a> Comments<'a> {
{
self.trailing_comments(node)
.iter()
.any(|comment| comment.position().is_own_line())
.any(|comment| comment.line_position().is_own_line())
}
/// Returns an iterator over the [leading](self#leading-comments) and [trailing comments](self#trailing-comments) of `node`.

View File

@@ -1,38 +1,45 @@
use std::cmp::Ordering;
use ruff_text_size::{TextRange, TextSize};
use rustpython_parser::ast::Ranged;
use ruff_python_ast::node::AnyNodeRef;
use crate::comments::visitor::{CommentPlacement, DecoratedComment};
use crate::expression::expr_slice::{assign_comment_in_slice, ExprSliceCommentSection};
use crate::other::arguments::{
assign_argument_separator_comment_placement, find_argument_separators,
};
use crate::trivia::{first_non_trivia_token_rev, SimpleTokenizer, Token, TokenKind};
use ruff_python_ast::node::{AnyNodeRef, AstNode};
use ruff_python_ast::source_code::Locator;
use ruff_python_ast::whitespace;
use ruff_python_whitespace::{PythonWhitespace, UniversalNewlines};
use crate::comments::visitor::{CommentPlacement, DecoratedComment};
use crate::comments::CommentTextPosition;
use crate::trivia::{SimpleTokenizer, Token, TokenKind};
use ruff_text_size::TextRange;
use rustpython_parser::ast::{Expr, ExprSlice, Ranged};
use std::cmp::Ordering;
/// Implements the custom comment placement logic.
pub(super) fn place_comment<'a>(
comment: DecoratedComment<'a>,
mut comment: DecoratedComment<'a>,
locator: &Locator,
) -> CommentPlacement<'a> {
handle_in_between_except_handlers_or_except_handler_and_else_or_finally_comment(
comment, locator,
)
.or_else(|comment| handle_match_comment(comment, locator))
.or_else(|comment| handle_in_between_bodies_own_line_comment(comment, locator))
.or_else(|comment| handle_in_between_bodies_end_of_line_comment(comment, locator))
.or_else(|comment| handle_trailing_body_comment(comment, locator))
.or_else(handle_trailing_end_of_line_body_comment)
.or_else(|comment| handle_trailing_end_of_line_condition_comment(comment, locator))
.or_else(|comment| {
handle_module_level_own_line_comment_before_class_or_function_comment(comment, locator)
})
.or_else(|comment| handle_positional_only_arguments_separator_comment(comment, locator))
.or_else(|comment| handle_trailing_binary_expression_left_or_operator_comment(comment, locator))
.or_else(handle_leading_function_with_decorators_comment)
.or_else(|comment| handle_dict_unpacking_comment(comment, locator))
static HANDLERS: &[for<'a> fn(DecoratedComment<'a>, &Locator) -> CommentPlacement<'a>] = &[
handle_in_between_except_handlers_or_except_handler_and_else_or_finally_comment,
handle_match_comment,
handle_in_between_bodies_own_line_comment,
handle_in_between_bodies_end_of_line_comment,
handle_trailing_body_comment,
handle_trailing_end_of_line_body_comment,
handle_trailing_end_of_line_condition_comment,
handle_module_level_own_line_comment_before_class_or_function_comment,
handle_arguments_separator_comment,
handle_trailing_binary_expression_left_or_operator_comment,
handle_leading_function_with_decorators_comment,
handle_dict_unpacking_comment,
handle_slice_comments,
handle_attribute_comment,
];
for handler in HANDLERS {
comment = match handler(comment, locator) {
CommentPlacement::Default(comment) => comment,
placement => return placement,
};
}
CommentPlacement::Default(comment)
}
/// Handles leading comments in front of a match case or a trailing comment of the `match` statement.
@@ -49,7 +56,7 @@ fn handle_match_comment<'a>(
locator: &Locator,
) -> CommentPlacement<'a> {
// Must be an own line comment after the last statement in a match case
if comment.text_position().is_end_of_line() || comment.following_node().is_some() {
if comment.line_position().is_end_of_line() || comment.following_node().is_some() {
return CommentPlacement::Default(comment);
}
@@ -147,7 +154,7 @@ fn handle_in_between_except_handlers_or_except_handler_and_else_or_finally_comme
comment: DecoratedComment<'a>,
locator: &Locator,
) -> CommentPlacement<'a> {
if comment.text_position().is_end_of_line() || comment.following_node().is_none() {
if comment.line_position().is_end_of_line() || comment.following_node().is_none() {
return CommentPlacement::Default(comment);
}
@@ -201,7 +208,7 @@ fn handle_in_between_bodies_own_line_comment<'a>(
comment: DecoratedComment<'a>,
locator: &Locator,
) -> CommentPlacement<'a> {
if !comment.text_position().is_own_line() {
if !comment.line_position().is_own_line() {
return CommentPlacement::Default(comment);
}
@@ -310,7 +317,7 @@ fn handle_in_between_bodies_end_of_line_comment<'a>(
comment: DecoratedComment<'a>,
locator: &Locator,
) -> CommentPlacement<'a> {
if !comment.text_position().is_end_of_line() {
if !comment.line_position().is_end_of_line() {
return CommentPlacement::Default(comment);
}
@@ -393,12 +400,16 @@ fn handle_trailing_body_comment<'a>(
comment: DecoratedComment<'a>,
locator: &Locator,
) -> CommentPlacement<'a> {
if comment.text_position().is_end_of_line() {
if comment.line_position().is_end_of_line() {
return CommentPlacement::Default(comment);
}
// Only do something if the preceding node has a body (has indented statements).
let Some(last_child) = comment.preceding_node().and_then(last_child_in_body) else {
let Some(preceding_node) = comment.preceding_node() else {
return CommentPlacement::Default(comment);
};
let Some(last_child) = last_child_in_body(preceding_node) else {
return CommentPlacement::Default(comment);
};
@@ -411,8 +422,24 @@ fn handle_trailing_body_comment<'a>(
// the indent-level doesn't depend on the tab width (the indent level must be the same if the tab width is 1 or 8).
let comment_indentation_len = comment_indentation.len();
// Keep the comment on the entire statement in case it's a trailing comment
// ```python
// if "first if":
// pass
// elif "first elif":
// pass
// # Trailing if comment
// ```
// Here we keep the comment a trailing comment of the `if`
let Some(preceding_node_indentation) = whitespace::indentation_at_offset(locator, preceding_node.start()) else {
return CommentPlacement::Default(comment);
};
if comment_indentation_len == preceding_node_indentation.len() {
return CommentPlacement::Default(comment);
}
let mut current_child = last_child;
let mut parent_body = comment.preceding_node();
let mut parent_body = Some(preceding_node);
let mut grand_parent_body = None;
loop {
@@ -483,9 +510,12 @@ fn handle_trailing_body_comment<'a>(
/// if something.changed:
/// do.stuff() # trailing comment
/// ```
fn handle_trailing_end_of_line_body_comment(comment: DecoratedComment<'_>) -> CommentPlacement<'_> {
fn handle_trailing_end_of_line_body_comment<'a>(
comment: DecoratedComment<'a>,
_locator: &Locator,
) -> CommentPlacement<'a> {
// Must be an end of line comment
if comment.text_position().is_own_line() {
if comment.line_position().is_own_line() {
return CommentPlacement::Default(comment);
}
@@ -526,7 +556,7 @@ fn handle_trailing_end_of_line_condition_comment<'a>(
use ruff_python_ast::prelude::*;
// Must be an end of line comment
if comment.text_position().is_own_line() {
if comment.line_position().is_own_line() {
return CommentPlacement::Default(comment);
}
@@ -602,18 +632,11 @@ fn handle_trailing_end_of_line_condition_comment<'a>(
CommentPlacement::Default(comment)
}
/// Attaches comments for the positional-only arguments separator `/` as trailing comments to the
/// enclosing [`Arguments`] node.
/// Attaches comments for the positional only arguments separator `/` or the keywords only arguments
/// separator `*` as dangling comments to the enclosing [`Arguments`] node.
///
/// ```python
/// def test(
/// a,
/// # Positional arguments only after here
/// /, # trailing positional argument comment.
/// b,
/// ): pass
/// ```
fn handle_positional_only_arguments_separator_comment<'a>(
/// See [`assign_argument_separator_comment_placement`]
fn handle_arguments_separator_comment<'a>(
comment: DecoratedComment<'a>,
locator: &Locator,
) -> CommentPlacement<'a> {
@@ -621,45 +644,19 @@ fn handle_positional_only_arguments_separator_comment<'a>(
return CommentPlacement::Default(comment);
};
// Using the `/` without any leading arguments is a syntax error.
let Some(last_argument_or_default) = comment.preceding_node() else {
return CommentPlacement::Default(comment);
};
let is_last_positional_argument =
are_same_optional(last_argument_or_default, arguments.posonlyargs.last());
if !is_last_positional_argument {
return CommentPlacement::Default(comment);
let (slash, star) = find_argument_separators(locator.contents(), arguments);
let comment_range = comment.slice().range();
let placement = assign_argument_separator_comment_placement(
slash.as_ref(),
star.as_ref(),
comment_range,
comment.line_position(),
);
if placement.is_some() {
return CommentPlacement::dangling(comment.enclosing_node(), comment);
}
let trivia_end = comment
.following_node()
.map_or(arguments.end(), |following| following.start());
let trivia_range = TextRange::new(last_argument_or_default.end(), trivia_end);
if let Some(slash_offset) = find_pos_only_slash_offset(trivia_range, locator) {
let comment_start = comment.slice().range().start();
let is_slash_comment = match comment.text_position() {
CommentTextPosition::EndOfLine => {
let preceding_end_line = locator.line_end(last_argument_or_default.end());
let slash_comments_start = preceding_end_line.min(slash_offset);
comment_start >= slash_comments_start
&& locator.line_end(slash_offset) > comment_start
}
CommentTextPosition::OwnLine => comment_start < slash_offset,
};
if is_slash_comment {
CommentPlacement::dangling(comment.enclosing_node(), comment)
} else {
CommentPlacement::Default(comment)
}
} else {
// Should not happen, but let's go with it
CommentPlacement::Default(comment)
}
CommentPlacement::Default(comment)
}
/// Handles comments between the left side and the operator of a binary expression (trailing comments of the left),
@@ -711,7 +708,7 @@ fn handle_trailing_binary_expression_left_or_operator_comment<'a>(
// )
// ```
CommentPlacement::trailing(AnyNodeRef::from(binary_expression.left.as_ref()), comment)
} else if comment.text_position().is_end_of_line() {
} else if comment.line_position().is_end_of_line() {
// Is the operator on its own line.
if locator.contains_line_break(TextRange::new(
binary_expression.left.end(),
@@ -800,7 +797,7 @@ fn handle_module_level_own_line_comment_before_class_or_function_comment<'a>(
locator: &Locator,
) -> CommentPlacement<'a> {
// Only applies for own line comments on the module level...
if !comment.text_position().is_own_line() || !comment.enclosing_node().is_module() {
if !comment.line_position().is_own_line() || !comment.enclosing_node().is_module() {
return CommentPlacement::Default(comment);
}
@@ -829,33 +826,85 @@ fn handle_module_level_own_line_comment_before_class_or_function_comment<'a>(
}
}
/// Finds the offset of the `/` that separates the positional only and arguments from the other arguments.
/// Returns `None` if the positional only separator `/` isn't present in the specified range.
fn find_pos_only_slash_offset(
between_arguments_range: TextRange,
/// Handles the attaching comments left or right of the colon in a slice as trailing comment of the
/// preceding node or leading comment of the following node respectively.
/// ```python
/// a = "input"[
/// 1 # c
/// # d
/// :2
/// ]
/// ```
fn handle_slice_comments<'a>(
comment: DecoratedComment<'a>,
locator: &Locator,
) -> Option<TextSize> {
let mut tokens =
SimpleTokenizer::new(locator.contents(), between_arguments_range).skip_trivia();
if let Some(comma) = tokens.next() {
debug_assert_eq!(comma.kind(), TokenKind::Comma);
if let Some(maybe_slash) = tokens.next() {
if maybe_slash.kind() == TokenKind::Slash {
return Some(maybe_slash.start());
) -> CommentPlacement<'a> {
let expr_slice = match comment.enclosing_node() {
AnyNodeRef::ExprSlice(expr_slice) => expr_slice,
AnyNodeRef::ExprSubscript(expr_subscript) => {
if expr_subscript.value.end() < expr_subscript.slice.start() {
if let Expr::Slice(expr_slice) = expr_subscript.slice.as_ref() {
expr_slice
} else {
return CommentPlacement::Default(comment);
}
} else {
return CommentPlacement::Default(comment);
}
debug_assert_eq!(
maybe_slash.kind(),
TokenKind::RParen,
"{:?}",
maybe_slash.kind()
);
}
_ => return CommentPlacement::Default(comment),
};
let ExprSlice {
range: _,
lower,
upper,
step,
} = expr_slice;
// Check for `foo[ # comment`, but only if they are on the same line
let after_lbracket = matches!(
first_non_trivia_token_rev(comment.slice().start(), locator.contents()),
Some(Token {
kind: TokenKind::LBracket,
..
})
);
if comment.line_position().is_end_of_line() && after_lbracket {
// Keep comments after the opening bracket there by formatting them outside the
// soft block indent
// ```python
// "a"[ # comment
// 1:
// ]
// ```
debug_assert!(
matches!(comment.enclosing_node(), AnyNodeRef::ExprSubscript(_)),
"{:?}",
comment.enclosing_node()
);
return CommentPlacement::dangling(comment.enclosing_node(), comment);
}
None
let assignment =
assign_comment_in_slice(comment.slice().range(), locator.contents(), expr_slice);
let node = match assignment {
ExprSliceCommentSection::Lower => lower,
ExprSliceCommentSection::Upper => upper,
ExprSliceCommentSection::Step => step,
};
if let Some(node) = node {
if comment.slice().start() < node.start() {
CommentPlacement::leading(node.as_ref().into(), comment)
} else {
// If a trailing comment is an end of line comment that's fine because we have a node
// ahead of it
CommentPlacement::trailing(node.as_ref().into(), comment)
}
} else {
CommentPlacement::dangling(expr_slice.as_any_node_ref(), comment)
}
}
/// Handles own line comments between the last function decorator and the *header* of the function.
@@ -868,7 +917,10 @@ fn find_pos_only_slash_offset(
/// def test():
/// ...
/// ```
fn handle_leading_function_with_decorators_comment(comment: DecoratedComment) -> CommentPlacement {
fn handle_leading_function_with_decorators_comment<'a>(
comment: DecoratedComment<'a>,
_locator: &Locator,
) -> CommentPlacement<'a> {
let is_preceding_decorator = comment
.preceding_node()
.map_or(false, |node| node.is_decorator());
@@ -877,7 +929,7 @@ fn handle_leading_function_with_decorators_comment(comment: DecoratedComment) ->
.following_node()
.map_or(false, |node| node.is_arguments());
if comment.text_position().is_own_line() && is_preceding_decorator && is_following_arguments {
if comment.line_position().is_own_line() && is_preceding_decorator && is_following_arguments {
CommentPlacement::dangling(comment.enclosing_node(), comment)
} else {
CommentPlacement::Default(comment)
@@ -954,6 +1006,43 @@ fn handle_dict_unpacking_comment<'a>(
CommentPlacement::Default(comment)
}
// Own line comments coming after the node are always dangling comments
// ```python
// (
// a
// # trailing a comment
// . # dangling comment
// # or this
// b
// )
// ```
fn handle_attribute_comment<'a>(
comment: DecoratedComment<'a>,
locator: &Locator,
) -> CommentPlacement<'a> {
let Some(attribute) = comment.enclosing_node().expr_attribute() else {
return CommentPlacement::Default(comment);
};
// It must be a comment AFTER the name
if comment.preceding_node().is_none() {
return CommentPlacement::Default(comment);
}
let between_value_and_attr = TextRange::new(attribute.value.end(), attribute.attr.start());
let dot = SimpleTokenizer::new(locator.contents(), between_value_and_attr)
.skip_trivia()
.next()
.expect("Expected the `.` character after the value");
if TextRange::new(dot.end(), attribute.attr.start()).contains(comment.slice().start()) {
CommentPlacement::dangling(attribute.into(), comment)
} else {
CommentPlacement::Default(comment)
}
}
/// Returns `true` if `right` is `Some` and `left` and `right` are referentially equal.
fn are_same_optional<'a, T>(left: AnyNodeRef, right: Option<T>) -> bool
where

View File

@@ -1,6 +1,6 @@
use crate::comments::node_key::NodeRefEqualityKey;
use crate::comments::placement::place_comment;
use crate::comments::{CommentTextPosition, CommentsMap, SourceComment};
use crate::comments::{CommentLinePosition, CommentsMap, SourceComment};
use ruff_formatter::{SourceCode, SourceCodeSlice};
use ruff_python_ast::node::AnyNodeRef;
use ruff_python_ast::prelude::*;
@@ -66,7 +66,7 @@ impl<'a> CommentsVisitor<'a> {
preceding: self.preceding_node,
following: Some(node),
parent: self.parents.iter().rev().nth(1).copied(),
text_position: text_position(*comment_range, self.source_code),
line_position: text_position(*comment_range, self.source_code),
slice: self.source_code.slice(*comment_range),
};
@@ -125,7 +125,7 @@ impl<'a> CommentsVisitor<'a> {
preceding: self.preceding_node,
parent: self.parents.last().copied(),
following: None,
text_position: text_position(*comment_range, self.source_code),
line_position: text_position(*comment_range, self.source_code),
slice: self.source_code.slice(*comment_range),
};
@@ -280,7 +280,7 @@ impl<'ast> PreorderVisitor<'ast> for CommentsVisitor<'ast> {
}
}
fn text_position(comment_range: TextRange, source_code: SourceCode) -> CommentTextPosition {
fn text_position(comment_range: TextRange, source_code: SourceCode) -> CommentLinePosition {
let before = &source_code.as_str()[TextRange::up_to(comment_range.start())];
for c in before.chars().rev() {
@@ -289,11 +289,11 @@ fn text_position(comment_range: TextRange, source_code: SourceCode) -> CommentTe
break;
}
c if is_python_whitespace(c) => continue,
_ => return CommentTextPosition::EndOfLine,
_ => return CommentLinePosition::EndOfLine,
}
}
CommentTextPosition::OwnLine
CommentLinePosition::OwnLine
}
/// A comment decorated with additional information about its surrounding context in the source document.
@@ -305,7 +305,7 @@ pub(super) struct DecoratedComment<'a> {
preceding: Option<AnyNodeRef<'a>>,
following: Option<AnyNodeRef<'a>>,
parent: Option<AnyNodeRef<'a>>,
text_position: CommentTextPosition,
line_position: CommentLinePosition,
slice: SourceCodeSlice,
}
@@ -443,14 +443,14 @@ impl<'a> DecoratedComment<'a> {
}
/// The position of the comment in the text.
pub(super) fn text_position(&self) -> CommentTextPosition {
self.text_position
pub(super) fn line_position(&self) -> CommentLinePosition {
self.line_position
}
}
impl From<DecoratedComment<'_>> for SourceComment {
fn from(decorated: DecoratedComment) -> Self {
Self::new(decorated.slice, decorated.text_position)
Self::new(decorated.slice, decorated.line_position)
}
}
@@ -615,18 +615,6 @@ impl<'a> CommentPlacement<'a> {
comment: comment.into(),
}
}
/// Returns the placement if it isn't [`CommentPlacement::Default`], otherwise calls `f` and returns the result.
#[inline]
pub(super) fn or_else<F>(self, f: F) -> Self
where
F: FnOnce(DecoratedComment<'a>) -> CommentPlacement<'a>,
{
match self {
CommentPlacement::Default(comment) => f(comment),
placement => placement,
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
@@ -659,8 +647,8 @@ impl<'a> CommentsBuilder<'a> {
self.push_dangling_comment(node, comment);
}
CommentPlacement::Default(comment) => {
match comment.text_position() {
CommentTextPosition::EndOfLine => {
match comment.line_position() {
CommentLinePosition::EndOfLine => {
match (comment.preceding_node(), comment.following_node()) {
(Some(preceding), Some(_)) => {
// Attach comments with both preceding and following node to the preceding
@@ -682,7 +670,7 @@ impl<'a> CommentsBuilder<'a> {
}
}
}
CommentTextPosition::OwnLine => {
CommentLinePosition::OwnLine => {
match (comment.preceding_node(), comment.following_node()) {
// Following always wins for a leading comment
// ```python

View File

@@ -0,0 +1,215 @@
//! This module provides helper utilities to format an expression that has a left side, an operator,
//! and a right side (binary like).
use crate::expression::parentheses::Parentheses;
use crate::prelude::*;
use ruff_formatter::{format_args, write};
use rustpython_parser::ast::Expr;
/// Trait to implement a binary like syntax that has a left operand, an operator, and a right operand.
pub(super) trait FormatBinaryLike<'ast> {
/// The type implementing the formatting of the operator.
type FormatOperator: Format<PyFormatContext<'ast>>;
/// Formats the binary like expression to `f`.
fn fmt_binary(
&self,
parentheses: Option<Parentheses>,
f: &mut PyFormatter<'ast, '_>,
) -> FormatResult<()> {
let left = self.left()?;
let operator = self.operator();
let right = self.right()?;
let layout = if parentheses == Some(Parentheses::Custom) {
self.binary_layout()
} else {
BinaryLayout::Default
};
match layout {
BinaryLayout::Default => self.fmt_default(f),
BinaryLayout::ExpandLeft => {
let left = left.format().memoized();
let right = right.format().memoized();
write!(
f,
[best_fitting![
// Everything on a single line
format_args![group(&left), space(), operator, space(), right],
// Break the left over multiple lines, keep the right flat
format_args![
group(&left).should_expand(true),
space(),
operator,
space(),
right
],
// The content doesn't fit, indent the content and break before the operator.
format_args![
text("("),
block_indent(&format_args![
left,
hard_line_break(),
operator,
space(),
right
]),
text(")")
]
]
.with_mode(BestFittingMode::AllLines)]
)
}
BinaryLayout::ExpandRight => {
let left_group = f.group_id("BinaryLeft");
write!(
f,
[
// Wrap the left in a group and gives it an id. The printer first breaks the
// right side if `right` contains any line break because the printer breaks
// sequences of groups from right to left.
// Indents the left side if the group breaks.
group(&format_args![
if_group_breaks(&text("(")),
indent_if_group_breaks(
&format_args![
soft_line_break(),
left.format(),
soft_line_break_or_space(),
operator,
space()
],
left_group
)
])
.with_group_id(Some(left_group)),
// Wrap the right in a group and indents its content but only if the left side breaks
group(&indent_if_group_breaks(&right.format(), left_group)),
// If the left side breaks, insert a hard line break to finish the indent and close the open paren.
if_group_breaks(&format_args![hard_line_break(), text(")")])
.with_group_id(Some(left_group))
]
)
}
BinaryLayout::ExpandRightThenLeft => {
// The formatter expands group-sequences from right to left, and expands both if
// there isn't enough space when expanding only one of them.
write!(
f,
[
group(&left.format()),
space(),
operator,
space(),
group(&right.format())
]
)
}
}
}
/// Determines which binary layout to use.
fn binary_layout(&self) -> BinaryLayout {
if let (Ok(left), Ok(right)) = (self.left(), self.right()) {
BinaryLayout::from_left_right(left, right)
} else {
BinaryLayout::Default
}
}
/// Formats the node according to the default layout.
fn fmt_default(&self, f: &mut PyFormatter<'ast, '_>) -> FormatResult<()>;
/// Returns the left operator
fn left(&self) -> FormatResult<&Expr>;
/// Returns the right operator.
fn right(&self) -> FormatResult<&Expr>;
/// Returns the object that formats the operator.
fn operator(&self) -> Self::FormatOperator;
}
fn can_break_expr(expr: &Expr) -> bool {
use ruff_python_ast::prelude::*;
match expr {
Expr::Tuple(ExprTuple {
elts: expressions, ..
})
| Expr::List(ExprList {
elts: expressions, ..
})
| Expr::Set(ExprSet {
elts: expressions, ..
})
| Expr::Dict(ExprDict {
values: expressions,
..
}) => !expressions.is_empty(),
Expr::Call(ExprCall { args, keywords, .. }) => !(args.is_empty() && keywords.is_empty()),
Expr::ListComp(_) | Expr::SetComp(_) | Expr::DictComp(_) | Expr::GeneratorExp(_) => true,
Expr::UnaryOp(ExprUnaryOp { operand, .. }) => match operand.as_ref() {
Expr::BinOp(_) => true,
_ => can_break_expr(operand.as_ref()),
},
_ => false,
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(super) enum BinaryLayout {
/// Put each operand on their own line if either side expands
Default,
/// Try to expand the left to make it fit. Add parentheses if the left or right don't fit.
///
///```python
/// [
/// a,
/// b
/// ] & c
///```
ExpandLeft,
/// Try to expand the right to make it fix. Add parentheses if the left or right don't fit.
///
/// ```python
/// a & [
/// b,
/// c
/// ]
/// ```
ExpandRight,
/// Both the left and right side can be expanded. Try in the following order:
/// * expand the right side
/// * expand the left side
/// * expand both sides
///
/// to make the expression fit
///
/// ```python
/// [
/// a,
/// b
/// ] & [
/// c,
/// d
/// ]
/// ```
ExpandRightThenLeft,
}
impl BinaryLayout {
pub(super) fn from_left_right(left: &Expr, right: &Expr) -> BinaryLayout {
match (can_break_expr(left), can_break_expr(right)) {
(false, false) => BinaryLayout::Default,
(true, false) => BinaryLayout::ExpandLeft,
(false, true) => BinaryLayout::ExpandRight,
(true, true) => BinaryLayout::ExpandRightThenLeft,
}
}
}

View File

@@ -1,9 +1,9 @@
use crate::comments::Comments;
use crate::comments::{leading_comments, trailing_comments, Comments};
use crate::expression::parentheses::{
default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize,
};
use crate::prelude::*;
use crate::{not_yet_implemented_custom_text, FormatNodeRule};
use crate::FormatNodeRule;
use ruff_formatter::write;
use rustpython_parser::ast::{Constant, Expr, ExprAttribute, ExprConstant};
@@ -15,11 +15,11 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
let ExprAttribute {
value,
range: _,
attr: _,
attr,
ctx: _,
} = item;
let requires_space = matches!(
let needs_parentheses = matches!(
value.as_ref(),
Expr::Constant(ExprConstant {
value: Constant::Int(_) | Constant::Float(_),
@@ -27,16 +27,45 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
})
);
if needs_parentheses {
value.format().with_options(Parenthesize::Always).fmt(f)?;
} else {
value.format().fmt(f)?;
}
let comments = f.context().comments().clone();
if comments.has_trailing_own_line_comments(value.as_ref()) {
hard_line_break().fmt(f)?;
}
let dangling_comments = comments.dangling_comments(item);
let leading_attribute_comments_start =
dangling_comments.partition_point(|comment| comment.line_position().is_end_of_line());
let (trailing_dot_comments, leading_attribute_comments) =
dangling_comments.split_at(leading_attribute_comments_start);
write!(
f,
[
item.value.format(),
requires_space.then_some(space()),
text("."),
not_yet_implemented_custom_text("NOT_IMPLEMENTED_attr")
trailing_comments(trailing_dot_comments),
(!leading_attribute_comments.is_empty()).then_some(hard_line_break()),
leading_comments(leading_attribute_comments),
attr.format()
]
)
}
fn fmt_dangling_comments(
&self,
_node: &ExprAttribute,
_f: &mut PyFormatter,
) -> FormatResult<()> {
// handle in `fmt_fields`
Ok(())
}
}
impl NeedsParentheses for ExprAttribute {
@@ -46,6 +75,9 @@ impl NeedsParentheses for ExprAttribute {
source: &str,
comments: &Comments,
) -> Parentheses {
default_expression_needs_parentheses(self.into(), parenthesize, source, comments)
match default_expression_needs_parentheses(self.into(), parenthesize, source, comments) {
Parentheses::Optional => Parentheses::Never,
parentheses => parentheses,
}
}
}

View File

@@ -1,14 +1,12 @@
use crate::comments::{trailing_comments, Comments};
use crate::expression::binary_like::{BinaryLayout, FormatBinaryLike};
use crate::expression::parentheses::{
default_expression_needs_parentheses, NeedsParentheses, Parenthesize,
};
use crate::expression::Parentheses;
use crate::prelude::*;
use crate::FormatNodeRule;
use ruff_formatter::{
format_args, write, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions,
};
use ruff_python_ast::node::AstNode;
use ruff_formatter::{write, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions};
use rustpython_parser::ast::{
Constant, Expr, ExprAttribute, ExprBinOp, ExprConstant, ExprUnaryOp, Operator, UnaryOp,
};
@@ -29,76 +27,7 @@ impl FormatRuleWithOptions<ExprBinOp, PyFormatContext<'_>> for FormatExprBinOp {
impl FormatNodeRule<ExprBinOp> for FormatExprBinOp {
fn fmt_fields(&self, item: &ExprBinOp, f: &mut PyFormatter) -> FormatResult<()> {
let ExprBinOp {
left,
right,
op,
range: _,
} = item;
let should_break_right = self.parentheses == Some(Parentheses::Custom);
if should_break_right {
let left_group = f.group_id("BinaryLeft");
write!(
f,
[
// Wrap the left in a group and gives it an id. The printer first breaks the
// right side if `right` contains any line break because the printer breaks
// sequences of groups from right to left.
// Indents the left side if the group breaks.
group(&format_args![
if_group_breaks(&text("(")),
indent_if_group_breaks(
&format_args![
soft_line_break(),
left.format(),
soft_line_break_or_space(),
op.format(),
space()
],
left_group
)
])
.with_group_id(Some(left_group)),
// Wrap the right in a group and indents its content but only if the left side breaks
group(&indent_if_group_breaks(&right.format(), left_group)),
// If the left side breaks, insert a hard line break to finish the indent and close the open paren.
if_group_breaks(&format_args![hard_line_break(), text(")")])
.with_group_id(Some(left_group))
]
)
} else {
let comments = f.context().comments().clone();
let operator_comments = comments.dangling_comments(item.as_any_node_ref());
let needs_space = !is_simple_power_expression(item);
let before_operator_space = if needs_space {
soft_line_break_or_space()
} else {
soft_line_break()
};
write!(
f,
[
left.format(),
before_operator_space,
op.format(),
trailing_comments(operator_comments),
]
)?;
// Format the operator on its own line if the right side has any leading comments.
if comments.has_leading_comments(right.as_ref()) {
write!(f, [hard_line_break()])?;
} else if needs_space {
write!(f, [space()])?;
}
write!(f, [group(&right.format())])
}
item.fmt_binary(self.parentheses, f)
}
fn fmt_dangling_comments(&self, _node: &ExprBinOp, _f: &mut PyFormatter) -> FormatResult<()> {
@@ -107,6 +36,60 @@ impl FormatNodeRule<ExprBinOp> for FormatExprBinOp {
}
}
impl<'ast> FormatBinaryLike<'ast> for ExprBinOp {
type FormatOperator = FormatOwnedWithRule<Operator, FormatOperator, PyFormatContext<'ast>>;
fn fmt_default(&self, f: &mut PyFormatter<'ast, '_>) -> FormatResult<()> {
let ExprBinOp {
range: _,
left,
op,
right,
} = self;
let comments = f.context().comments().clone();
let operator_comments = comments.dangling_comments(self);
let needs_space = !is_simple_power_expression(self);
let before_operator_space = if needs_space {
soft_line_break_or_space()
} else {
soft_line_break()
};
write!(
f,
[
left.format(),
before_operator_space,
op.format(),
trailing_comments(operator_comments),
]
)?;
// Format the operator on its own line if the right side has any leading comments.
if comments.has_leading_comments(right.as_ref()) {
write!(f, [hard_line_break()])?;
} else if needs_space {
write!(f, [space()])?;
}
write!(f, [group(&right.format())])
}
fn left(&self) -> FormatResult<&Expr> {
Ok(&self.left)
}
fn right(&self) -> FormatResult<&Expr> {
Ok(&self.right)
}
fn operator(&self) -> Self::FormatOperator {
self.op.into_format()
}
}
const fn is_simple_power_expression(expr: &ExprBinOp) -> bool {
expr.op.is_pow() && is_simple_power_operand(&expr.left) && is_simple_power_operand(&expr.right)
}
@@ -179,42 +162,16 @@ impl NeedsParentheses for ExprBinOp {
) -> Parentheses {
match default_expression_needs_parentheses(self.into(), parenthesize, source, comments) {
Parentheses::Optional => {
if should_binary_break_right_side_first(self) {
Parentheses::Custom
} else {
if self.binary_layout() == BinaryLayout::Default
|| comments.has_leading_comments(self.right.as_ref())
|| comments.has_dangling_comments(self)
{
Parentheses::Optional
} else {
Parentheses::Custom
}
}
parentheses => parentheses,
}
}
}
pub(super) fn should_binary_break_right_side_first(expr: &ExprBinOp) -> bool {
use ruff_python_ast::prelude::*;
if expr.left.is_bin_op_expr() {
false
} else {
match expr.right.as_ref() {
Expr::Tuple(ExprTuple {
elts: expressions, ..
})
| Expr::List(ExprList {
elts: expressions, ..
})
| Expr::Set(ExprSet {
elts: expressions, ..
})
| Expr::Dict(ExprDict {
values: expressions,
..
}) => !expressions.is_empty(),
Expr::Call(ExprCall { args, keywords, .. }) => !args.is_empty() && !keywords.is_empty(),
Expr::ListComp(_) | Expr::SetComp(_) | Expr::DictComp(_) | Expr::GeneratorExp(_) => {
true
}
_ => false,
}
}
}

View File

@@ -1,22 +1,87 @@
use crate::comments::Comments;
use crate::comments::{leading_comments, Comments};
use crate::expression::binary_like::{BinaryLayout, FormatBinaryLike};
use crate::expression::parentheses::{
default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize,
};
use crate::{not_yet_implemented_custom_text, FormatNodeRule, PyFormatter};
use ruff_formatter::{write, Buffer, FormatResult};
use rustpython_parser::ast::ExprBoolOp;
use crate::prelude::*;
use ruff_formatter::{
write, FormatError, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions,
};
use rustpython_parser::ast::{BoolOp, Expr, ExprBoolOp};
#[derive(Default)]
pub struct FormatExprBoolOp;
pub struct FormatExprBoolOp {
parentheses: Option<Parentheses>,
}
impl FormatRuleWithOptions<ExprBoolOp, PyFormatContext<'_>> for FormatExprBoolOp {
type Options = Option<Parentheses>;
fn with_options(mut self, options: Self::Options) -> Self {
self.parentheses = options;
self
}
}
impl FormatNodeRule<ExprBoolOp> for FormatExprBoolOp {
fn fmt_fields(&self, _item: &ExprBoolOp, f: &mut PyFormatter) -> FormatResult<()> {
write!(
f,
[not_yet_implemented_custom_text(
"NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2"
)]
)
fn fmt_fields(&self, item: &ExprBoolOp, f: &mut PyFormatter) -> FormatResult<()> {
item.fmt_binary(self.parentheses, f)
}
}
impl<'ast> FormatBinaryLike<'ast> for ExprBoolOp {
type FormatOperator = FormatOwnedWithRule<BoolOp, FormatBoolOp, PyFormatContext<'ast>>;
fn binary_layout(&self) -> BinaryLayout {
match self.values.as_slice() {
[left, right] => BinaryLayout::from_left_right(left, right),
[..] => BinaryLayout::Default,
}
}
fn fmt_default(&self, f: &mut PyFormatter<'ast, '_>) -> FormatResult<()> {
let ExprBoolOp {
range: _,
op,
values,
} = self;
let mut values = values.iter();
let comments = f.context().comments().clone();
let Some(first) = values.next() else {
return Ok(())
};
write!(f, [group(&first.format())])?;
for value in values {
let leading_value_comments = comments.leading_comments(value);
// Format the expressions leading comments **before** the operator
if leading_value_comments.is_empty() {
write!(f, [soft_line_break_or_space()])?;
} else {
write!(
f,
[hard_line_break(), leading_comments(leading_value_comments)]
)?;
}
write!(f, [op.format(), space(), group(&value.format())])?;
}
Ok(())
}
fn left(&self) -> FormatResult<&Expr> {
self.values.first().ok_or(FormatError::SyntaxError)
}
fn right(&self) -> FormatResult<&Expr> {
self.values.last().ok_or(FormatError::SyntaxError)
}
fn operator(&self) -> Self::FormatOperator {
self.op.into_format()
}
}
@@ -27,6 +92,53 @@ impl NeedsParentheses for ExprBoolOp {
source: &str,
comments: &Comments,
) -> Parentheses {
default_expression_needs_parentheses(self.into(), parenthesize, source, comments)
match default_expression_needs_parentheses(self.into(), parenthesize, source, comments) {
Parentheses::Optional => match self.binary_layout() {
BinaryLayout::Default => Parentheses::Optional,
BinaryLayout::ExpandRight
| BinaryLayout::ExpandLeft
| BinaryLayout::ExpandRightThenLeft
if self
.values
.last()
.map_or(false, |right| comments.has_leading_comments(right)) =>
{
Parentheses::Optional
}
_ => Parentheses::Custom,
},
parentheses => parentheses,
}
}
}
#[derive(Copy, Clone)]
pub struct FormatBoolOp;
impl<'ast> AsFormat<PyFormatContext<'ast>> for BoolOp {
type Format<'a> = FormatRefWithRule<'a, BoolOp, FormatBoolOp, PyFormatContext<'ast>>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(self, FormatBoolOp)
}
}
impl<'ast> IntoFormat<PyFormatContext<'ast>> for BoolOp {
type Format = FormatOwnedWithRule<BoolOp, FormatBoolOp, PyFormatContext<'ast>>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(self, FormatBoolOp)
}
}
impl FormatRule<BoolOp, PyFormatContext<'_>> for FormatBoolOp {
fn fmt(&self, item: &BoolOp, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
let operator = match item {
BoolOp::And => "and",
BoolOp::Or => "or",
};
text(operator).fmt(f)
}
}

View File

@@ -1,4 +1,4 @@
use crate::comments::{dangling_comments, CommentTextPosition, Comments};
use crate::comments::{dangling_comments, CommentLinePosition, Comments};
use crate::expression::parentheses::{
default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize,
};
@@ -30,11 +30,12 @@ impl FormatNodeRule<ExprList> for FormatExprList {
// ```
// In all other cases comments get assigned to a list element
if elts.is_empty() {
let end_of_line_split = dangling
.partition_point(|comment| comment.position() == CommentTextPosition::EndOfLine);
let end_of_line_split = dangling.partition_point(|comment| {
comment.line_position() == CommentLinePosition::EndOfLine
});
debug_assert!(dangling[end_of_line_split..]
.iter()
.all(|comment| comment.position() == CommentTextPosition::OwnLine));
.all(|comment| comment.line_position() == CommentLinePosition::OwnLine));
return write!(
f,
[group(&format_args![

View File

@@ -1,26 +1,264 @@
use crate::comments::{dangling_comments, Comments, SourceComment};
use crate::expression::parentheses::{
default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize,
};
use crate::{not_yet_implemented_custom_text, FormatNodeRule, PyFormatter};
use crate::comments::Comments;
use ruff_formatter::{write, Buffer, FormatResult};
use crate::trivia::Token;
use crate::trivia::{first_non_trivia_token, TokenKind};
use crate::{AsFormat, FormatNodeRule, PyFormatter};
use ruff_formatter::prelude::{hard_line_break, line_suffix_boundary, space, text};
use ruff_formatter::{write, Buffer, Format, FormatError, FormatResult};
use ruff_python_ast::node::AstNode;
use ruff_python_ast::prelude::{Expr, Ranged};
use ruff_text_size::TextRange;
use rustpython_parser::ast::ExprSlice;
#[derive(Default)]
pub struct FormatExprSlice;
impl FormatNodeRule<ExprSlice> for FormatExprSlice {
fn fmt_fields(&self, _item: &ExprSlice, f: &mut PyFormatter) -> FormatResult<()> {
write!(
f,
[not_yet_implemented_custom_text(
"NOT_IMPLEMENTED_start:NOT_IMPLEMENTED_end"
)]
)
/// This implementation deviates from black in that comments are attached to the section of the
/// slice they originate in
fn fmt_fields(&self, item: &ExprSlice, f: &mut PyFormatter) -> FormatResult<()> {
// `[lower:upper:step]`
let ExprSlice {
range,
lower,
upper,
step,
} = item;
let (first_colon, second_colon) =
find_colons(f.context().contents(), *range, lower, upper)?;
// Handle comment placement
// In placements.rs, we marked comment for None nodes a dangling and associated all others
// as leading or dangling wrt to a node. That means we either format a node and only have
// to handle newlines and spacing, or the node is None and we insert the corresponding
// slice of dangling comments
let comments = f.context().comments().clone();
let slice_dangling_comments = comments.dangling_comments(item.as_any_node_ref());
// Put the dangling comments (where the nodes are missing) into buckets
let first_colon_partition_index = slice_dangling_comments
.partition_point(|x| x.slice().start() < first_colon.range.start());
let (dangling_lower_comments, dangling_upper_step_comments) =
slice_dangling_comments.split_at(first_colon_partition_index);
let (dangling_upper_comments, dangling_step_comments) =
if let Some(second_colon) = &second_colon {
let second_colon_partition_index = dangling_upper_step_comments
.partition_point(|x| x.slice().start() < second_colon.range.start());
dangling_upper_step_comments.split_at(second_colon_partition_index)
} else {
// Without a second colon they remaining dangling comments belong between the first
// colon and the closing parentheses
(dangling_upper_step_comments, [].as_slice())
};
// Ensure there a no dangling comments for a node if the node is present
debug_assert!(lower.is_none() || dangling_lower_comments.is_empty());
debug_assert!(upper.is_none() || dangling_upper_comments.is_empty());
debug_assert!(step.is_none() || dangling_step_comments.is_empty());
// Handle spacing around the colon(s)
// https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#slices
let lower_simple = lower.as_ref().map_or(true, |expr| is_simple_expr(expr));
let upper_simple = upper.as_ref().map_or(true, |expr| is_simple_expr(expr));
let step_simple = step.as_ref().map_or(true, |expr| is_simple_expr(expr));
let all_simple = lower_simple && upper_simple && step_simple;
// lower
if let Some(lower) = lower {
write!(f, [lower.format(), line_suffix_boundary()])?;
} else {
dangling_comments(dangling_lower_comments).fmt(f)?;
}
// First colon
// The spacing after the colon depends on both the lhs and the rhs:
// ```
// e00 = x[:]
// e01 = x[:1]
// e02 = x[: a()]
// e10 = x[1:]
// e11 = x[1:1]
// e12 = x[1 : a()]
// e20 = x[a() :]
// e21 = x[a() : 1]
// e22 = x[a() : a()]
// e200 = "e"[a() : :]
// e201 = "e"[a() :: 1]
// e202 = "e"[a() :: a()]
// ```
if !all_simple {
space().fmt(f)?;
}
text(":").fmt(f)?;
// No upper node, no need for a space, e.g. `x[a() :]`
if !all_simple && upper.is_some() {
space().fmt(f)?;
}
// Upper
if let Some(upper) = upper {
let upper_leading_comments = comments.leading_comments(upper.as_ref());
leading_comments_spacing(f, upper_leading_comments)?;
write!(f, [upper.format(), line_suffix_boundary()])?;
} else {
if let Some(first) = dangling_upper_comments.first() {
// Here the spacing for end-of-line comments works but own line comments need
// explicit spacing
if first.line_position().is_own_line() {
hard_line_break().fmt(f)?;
}
}
dangling_comments(dangling_upper_comments).fmt(f)?;
}
// (optionally) step
if second_colon.is_some() {
// Same spacing rules as for the first colon, except for the strange case when the
// second colon exists, but neither upper nor step
// ```
// e200 = "e"[a() : :]
// e201 = "e"[a() :: 1]
// e202 = "e"[a() :: a()]
// ```
if !all_simple && (upper.is_some() || step.is_none()) {
space().fmt(f)?;
}
text(":").fmt(f)?;
// No step node, no need for a space
if !all_simple && step.is_some() {
space().fmt(f)?;
}
if let Some(step) = step {
let step_leading_comments = comments.leading_comments(step.as_ref());
leading_comments_spacing(f, step_leading_comments)?;
step.format().fmt(f)?;
} else {
if !dangling_step_comments.is_empty() {
// Put the colon and comments on their own lines
write!(
f,
[hard_line_break(), dangling_comments(dangling_step_comments)]
)?;
}
}
} else {
debug_assert!(step.is_none(), "step can't exist without a second colon");
}
Ok(())
}
}
/// We're in a slice, so we know there's a first colon, but with have to look into the source
/// to find out whether there is a second one, too, e.g. `[1:2]` and `[1:10:2]`.
///
/// Returns the first and optionally the second colon.
pub(crate) fn find_colons(
contents: &str,
range: TextRange,
lower: &Option<Box<Expr>>,
upper: &Option<Box<Expr>>,
) -> FormatResult<(Token, Option<Token>)> {
let after_lower = lower
.as_ref()
.map_or(range.start(), |lower| lower.range().end());
let first_colon =
first_non_trivia_token(after_lower, contents).ok_or(FormatError::SyntaxError)?;
if first_colon.kind != TokenKind::Colon {
return Err(FormatError::SyntaxError);
}
let after_upper = upper
.as_ref()
.map_or(first_colon.end(), |upper| upper.range().end());
// At least the closing bracket must exist, so there must be a token there
let next_token =
first_non_trivia_token(after_upper, contents).ok_or(FormatError::SyntaxError)?;
let second_colon = if next_token.kind == TokenKind::Colon {
debug_assert!(
next_token.range.start() < range.end(),
"The next token in a slice must either be a colon or the closing bracket"
);
Some(next_token)
} else {
None
};
Ok((first_colon, second_colon))
}
/// Determines whether this expression needs a space around the colon
/// <https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#slices>
fn is_simple_expr(expr: &Expr) -> bool {
matches!(expr, Expr::Constant(_) | Expr::Name(_))
}
pub(crate) enum ExprSliceCommentSection {
Lower,
Upper,
Step,
}
/// Assigns a comment to lower/upper/step in `[lower:upper:step]`.
///
/// ```python
/// "sliceable"[
/// # lower comment
/// :
/// # upper comment
/// :
/// # step comment
/// ]
/// ```
pub(crate) fn assign_comment_in_slice(
comment: TextRange,
contents: &str,
expr_slice: &ExprSlice,
) -> ExprSliceCommentSection {
let ExprSlice {
range,
lower,
upper,
step: _,
} = expr_slice;
let (first_colon, second_colon) = find_colons(contents, *range, lower, upper)
.expect("SyntaxError when trying to parse slice");
if comment.start() < first_colon.range.start() {
ExprSliceCommentSection::Lower
} else {
// We are to the right of the first colon
if let Some(second_colon) = second_colon {
if comment.start() < second_colon.range.start() {
ExprSliceCommentSection::Upper
} else {
ExprSliceCommentSection::Step
}
} else {
// No second colon means there is no step
ExprSliceCommentSection::Upper
}
}
}
/// Manual spacing for the leading comments of upper and step
fn leading_comments_spacing(
f: &mut PyFormatter,
leading_comments: &[SourceComment],
) -> FormatResult<()> {
if let Some(first) = leading_comments.first() {
if first.line_position().is_own_line() {
// Insert a newline after the colon so the comment ends up on its own line
hard_line_break().fmt(f)?;
} else {
// Insert the two spaces between the colon and the end-of-line comment after the colon
write!(f, [space(), space()])?;
}
}
Ok(())
}
impl NeedsParentheses for ExprSlice {
fn needs_parentheses(
&self,

View File

@@ -1,24 +1,52 @@
use crate::comments::{trailing_comments, Comments};
use crate::expression::parentheses::{
default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize,
};
use crate::{not_yet_implemented_custom_text, FormatNodeRule, PyFormatter};
use crate::comments::Comments;
use ruff_formatter::{write, Buffer, FormatResult};
use crate::{AsFormat, FormatNodeRule, PyFormatter};
use ruff_formatter::prelude::{group, soft_block_indent, text};
use ruff_formatter::{format_args, write, Buffer, FormatResult};
use ruff_python_ast::node::AstNode;
use rustpython_parser::ast::ExprSubscript;
#[derive(Default)]
pub struct FormatExprSubscript;
impl FormatNodeRule<ExprSubscript> for FormatExprSubscript {
fn fmt_fields(&self, _item: &ExprSubscript, f: &mut PyFormatter) -> FormatResult<()> {
fn fmt_fields(&self, item: &ExprSubscript, f: &mut PyFormatter) -> FormatResult<()> {
let ExprSubscript {
range: _,
value,
slice,
ctx: _,
} = item;
let comments = f.context().comments().clone();
let dangling_comments = comments.dangling_comments(item.as_any_node_ref());
debug_assert!(
dangling_comments.len() <= 1,
"The subscript expression must have at most a single comment, the one after the bracket"
);
write!(
f,
[not_yet_implemented_custom_text(
"NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]"
)]
[group(&format_args![
value.format(),
text("["),
trailing_comments(dangling_comments),
soft_block_indent(&slice.format()),
text("]")
])]
)
}
fn fmt_dangling_comments(
&self,
_node: &ExprSubscript,
_f: &mut PyFormatter,
) -> FormatResult<()> {
// Handled inside of `fmt_fields`
Ok(())
}
}
impl NeedsParentheses for ExprSubscript {
@@ -28,6 +56,9 @@ impl NeedsParentheses for ExprSubscript {
source: &str,
comments: &Comments,
) -> Parentheses {
default_expression_needs_parentheses(self.into(), parenthesize, source, comments)
match default_expression_needs_parentheses(self.into(), parenthesize, source, comments) {
Parentheses::Optional => Parentheses::Never,
parentheses => parentheses,
}
}
}

View File

@@ -10,13 +10,48 @@ use ruff_formatter::formatter::Formatter;
use ruff_formatter::prelude::{
block_indent, group, if_group_breaks, soft_block_indent, soft_line_break_or_space, text,
};
use ruff_formatter::{format_args, write, Buffer, Format, FormatResult};
use ruff_formatter::{format_args, write, Buffer, Format, FormatResult, FormatRuleWithOptions};
use ruff_python_ast::prelude::{Expr, Ranged};
use ruff_text_size::TextRange;
use rustpython_parser::ast::ExprTuple;
#[derive(Eq, PartialEq, Debug, Default)]
pub enum TupleParentheses {
/// Effectively `None` in `Option<Parentheses>`
#[default]
Default,
/// Effectively `Some(Parentheses)` in `Option<Parentheses>`
Expr(Parentheses),
/// Handle the special case where we remove parentheses even if they were initially present
///
/// Normally, black keeps parentheses, but in the case of loops it formats
/// ```python
/// for (a, b) in x:
/// pass
/// ```
/// to
/// ```python
/// for a, b in x:
/// pass
/// ```
/// Black still does use parentheses in this position if the group breaks or magic trailing
/// comma is used.
StripInsideForLoop,
}
#[derive(Default)]
pub struct FormatExprTuple;
pub struct FormatExprTuple {
parentheses: TupleParentheses,
}
impl FormatRuleWithOptions<ExprTuple, PyFormatContext<'_>> for FormatExprTuple {
type Options = TupleParentheses;
fn with_options(mut self, options: Self::Options) -> Self {
self.parentheses = options;
self
}
}
impl FormatNodeRule<ExprTuple> for FormatExprTuple {
fn fmt_fields(&self, item: &ExprTuple, f: &mut PyFormatter) -> FormatResult<()> {
@@ -74,9 +109,14 @@ impl FormatNodeRule<ExprTuple> for FormatExprTuple {
&text(")"),
]
)?;
} else if is_parenthesized(*range, elts, f) {
// If the tuple has parentheses, keep them. Note that unlike other expr parentheses,
// those are actually part of the range
} else if is_parenthesized(*range, elts, f)
&& self.parentheses != TupleParentheses::StripInsideForLoop
{
// If the tuple has parentheses, we generally want to keep them. The exception are for
// loops, see `TupleParentheses::StripInsideForLoop` doc comment.
//
// Unlike other expression parentheses, tuple parentheses are part of the range of the
// tuple itself.
write!(
f,
[group(&format_args![

View File

@@ -1,17 +1,68 @@
use crate::comments::Comments;
use crate::comments::{trailing_comments, Comments};
use crate::expression::parentheses::{
default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize,
};
use crate::{not_yet_implemented, FormatNodeRule, PyFormatter};
use ruff_formatter::{write, Buffer, FormatResult};
use rustpython_parser::ast::ExprUnaryOp;
use crate::trivia::{SimpleTokenizer, TokenKind};
use crate::{AsFormat, FormatNodeRule, PyFormatter};
use ruff_formatter::prelude::{hard_line_break, space, text};
use ruff_formatter::{Format, FormatContext, FormatResult};
use ruff_python_ast::prelude::UnaryOp;
use ruff_text_size::{TextLen, TextRange};
use rustpython_parser::ast::{ExprUnaryOp, Ranged};
#[derive(Default)]
pub struct FormatExprUnaryOp;
impl FormatNodeRule<ExprUnaryOp> for FormatExprUnaryOp {
fn fmt_fields(&self, item: &ExprUnaryOp, f: &mut PyFormatter) -> FormatResult<()> {
write!(f, [not_yet_implemented(item)])
let ExprUnaryOp {
range: _,
op,
operand,
} = item;
let operator = match op {
UnaryOp::Invert => "~",
UnaryOp::Not => "not",
UnaryOp::UAdd => "+",
UnaryOp::USub => "-",
};
text(operator).fmt(f)?;
let comments = f.context().comments().clone();
// Split off the comments that follow after the operator and format them as trailing comments.
// ```python
// (not # comment
// a)
// ```
let leading_operand_comments = comments.leading_comments(operand.as_ref());
let trailing_operator_comments_end =
leading_operand_comments.partition_point(|p| p.line_position().is_end_of_line());
let (trailing_operator_comments, leading_operand_comments) =
leading_operand_comments.split_at(trailing_operator_comments_end);
if !trailing_operator_comments.is_empty() {
trailing_comments(trailing_operator_comments).fmt(f)?;
}
// Insert a line break if the operand has comments but itself is not parenthesized.
// ```python
// if (
// not
// # comment
// a)
// ```
if !leading_operand_comments.is_empty()
&& !is_operand_parenthesized(item, f.context().source_code().as_str())
{
hard_line_break().fmt(f)?;
} else if op.is_not() {
space().fmt(f)?;
}
operand.format().fmt(f)
}
}
@@ -22,6 +73,37 @@ impl NeedsParentheses for ExprUnaryOp {
source: &str,
comments: &Comments,
) -> Parentheses {
default_expression_needs_parentheses(self.into(), parenthesize, source, comments)
match default_expression_needs_parentheses(self.into(), parenthesize, source, comments) {
Parentheses::Optional => {
// We preserve the parentheses of the operand. It should not be necessary to break this expression.
if is_operand_parenthesized(self, source) {
Parentheses::Never
} else {
Parentheses::Optional
}
}
parentheses => parentheses,
}
}
}
fn is_operand_parenthesized(unary: &ExprUnaryOp, source: &str) -> bool {
let operator_len = match unary.op {
UnaryOp::Invert => '~'.text_len(),
UnaryOp::Not => "not".text_len(),
UnaryOp::UAdd => '+'.text_len(),
UnaryOp::USub => '-'.text_len(),
};
let trivia_range = TextRange::new(unary.range.start() + operator_len, unary.operand.start());
if let Some(token) = SimpleTokenizer::new(source, trivia_range)
.skip_trivia()
.next()
{
debug_assert_eq!(token.kind(), TokenKind::LParen);
true
} else {
false
}
}

View File

@@ -1,5 +1,6 @@
use crate::comments::Comments;
use crate::context::NodeLevel;
use crate::expression::expr_tuple::TupleParentheses;
use crate::expression::parentheses::{NeedsParentheses, Parentheses, Parenthesize};
use crate::prelude::*;
use ruff_formatter::{
@@ -7,6 +8,7 @@ use ruff_formatter::{
};
use rustpython_parser::ast::Expr;
mod binary_like;
pub(crate) mod expr_attribute;
pub(crate) mod expr_await;
pub(crate) mod expr_bin_op;
@@ -59,7 +61,7 @@ impl FormatRule<Expr, PyFormatContext<'_>> for FormatExpr {
);
let format_expr = format_with(|f| match item {
Expr::BoolOp(expr) => expr.format().fmt(f),
Expr::BoolOp(expr) => expr.format().with_options(Some(parentheses)).fmt(f),
Expr::NamedExpr(expr) => expr.format().fmt(f),
Expr::BinOp(expr) => expr.format().with_options(Some(parentheses)).fmt(f),
Expr::UnaryOp(expr) => expr.format().fmt(f),
@@ -84,7 +86,10 @@ impl FormatRule<Expr, PyFormatContext<'_>> for FormatExpr {
Expr::Starred(expr) => expr.format().fmt(f),
Expr::Name(expr) => expr.format().fmt(f),
Expr::List(expr) => expr.format().fmt(f),
Expr::Tuple(expr) => expr.format().fmt(f),
Expr::Tuple(expr) => expr
.format()
.with_options(TupleParentheses::Expr(parentheses))
.fmt(f),
Expr::Slice(expr) => expr.format().fmt(f),
});

View File

@@ -23,8 +23,12 @@ pub(super) fn default_expression_needs_parentheses(
"Should only be called for expressions"
);
#[allow(clippy::if_same_then_else)]
if parenthesize.is_always() {
Parentheses::Always
}
// `Optional` or `Preserve` and expression has parentheses in source code.
if !parenthesize.is_if_breaks() && is_expression_parenthesized(node, source) {
else if !parenthesize.is_if_breaks() && is_expression_parenthesized(node, source) {
Parentheses::Always
}
// `Optional` or `IfBreaks`: Add parentheses if the expression doesn't fit on a line but enforce
@@ -42,7 +46,7 @@ pub(super) fn default_expression_needs_parentheses(
}
/// Configures if the expression should be parenthesized.
#[derive(Copy, Clone, Debug, Default)]
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub enum Parenthesize {
/// Parenthesize the expression if it has parenthesis in the source.
#[default]
@@ -53,14 +57,20 @@ pub enum Parenthesize {
/// Parenthesizes the expression only if it doesn't fit on a line.
IfBreaks,
Always,
}
impl Parenthesize {
const fn is_if_breaks(self) -> bool {
pub(crate) const fn is_always(self) -> bool {
matches!(self, Parenthesize::Always)
}
pub(crate) const fn is_if_breaks(self) -> bool {
matches!(self, Parenthesize::IfBreaks)
}
const fn is_preserve(self) -> bool {
pub(crate) const fn is_preserve(self) -> bool {
matches!(self, Parenthesize::Preserve)
}
}
@@ -70,7 +80,8 @@ impl Parenthesize {
/// whether there are parentheses in the source code or not.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Parentheses {
/// Always create parentheses
/// Always set parentheses regardless if the expression breaks or if they were
/// present in the source.
Always,
/// Only add parentheses when necessary because the expression breaks over multiple lines.

View File

@@ -273,36 +273,7 @@ if True:
let formatted_code = printed.as_code();
let reformatted = match format_module(formatted_code) {
Ok(reformatted) => reformatted,
Err(err) => {
panic!(
"Expected formatted code to be valid syntax: {err}:\
\n---\n{formatted_code}---\n",
);
}
};
if reformatted.as_code() != formatted_code {
let diff = TextDiff::from_lines(formatted_code, reformatted.as_code())
.unified_diff()
.header("Formatted once", "Formatted twice")
.to_string();
panic!(
r#"Reformatting the formatted code a second time resulted in formatting changes.
---
{diff}---
Formatted once:
---
{formatted_code}---
Formatted twice:
---
{}---"#,
reformatted.as_code()
);
}
ensure_stability_when_formatting_twice(formatted_code);
if formatted_code == expected_output {
// Black and Ruff formatting matches. Delete any existing snapshot files because the Black output
@@ -374,6 +345,8 @@ Formatted twice:
let reformatted =
format_module(formatted_code).unwrap_or_else(|err| panic!("Expected formatted code to be valid syntax but it contains syntax errors: {err}\n{formatted_code}"));
ensure_stability_when_formatting_twice(formatted_code);
if reformatted.as_code() != formatted_code {
let diff = TextDiff::from_lines(formatted_code, reformatted.as_code())
.unified_diff()
@@ -406,16 +379,49 @@ Formatted twice:
Ok(())
}
/// Format another time and make sure that there are no changes anymore
fn ensure_stability_when_formatting_twice(formatted_code: &str) {
let reformatted = match format_module(formatted_code) {
Ok(reformatted) => reformatted,
Err(err) => {
panic!(
"Expected formatted code to be valid syntax: {err}:\
\n---\n{formatted_code}---\n",
);
}
};
if reformatted.as_code() != formatted_code {
let diff = TextDiff::from_lines(formatted_code, reformatted.as_code())
.unified_diff()
.header("Formatted once", "Formatted twice")
.to_string();
panic!(
r#"Reformatting the formatted code a second time resulted in formatting changes.
---
{diff}---
Formatted once:
---
{formatted_code}---
Formatted twice:
---
{}---"#,
reformatted.as_code()
);
}
}
/// Use this test to debug the formatting of some snipped
#[ignore]
#[test]
fn quick_test() {
let src = r#"
def foo(
b=3
+ 2 # comment
):
if [
aaaaaa,
BBBB,ccccccccc,ddddddd,eeeeeeeeee,ffffff
] & bbbbbbbbbbbbbbbbbbddddddddddddddddddddddddddddbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:
...
"#;
// Tokenize once

View File

@@ -1,7 +1,6 @@
use crate::prelude::*;
use crate::FormatNodeRule;
use ruff_formatter::write;
use ruff_text_size::{TextLen, TextRange};
use rustpython_parser::ast::Arg;
#[derive(Default)]
@@ -10,21 +9,13 @@ pub struct FormatArg;
impl FormatNodeRule<Arg> for FormatArg {
fn fmt_fields(&self, item: &Arg, f: &mut PyFormatter) -> FormatResult<()> {
let Arg {
range,
range: _,
arg,
annotation,
type_comment: _,
} = item;
write!(
f,
[
// The name of the argument
source_text_slice(
TextRange::at(range.start(), arg.text_len()),
ContainsNewlines::No
)
]
)?;
arg.format().fmt(f)?;
if let Some(annotation) = annotation {
write!(f, [text(":"), space(), annotation.format()])?;

View File

@@ -5,11 +5,15 @@ use rustpython_parser::ast::{Arguments, Ranged};
use ruff_formatter::{format_args, write};
use ruff_python_ast::node::{AnyNodeRef, AstNode};
use crate::comments::{dangling_node_comments, leading_node_comments};
use crate::comments::{
dangling_comments, leading_comments, leading_node_comments, trailing_comments,
CommentLinePosition, SourceComment,
};
use crate::context::NodeLevel;
use crate::prelude::*;
use crate::trivia::{first_non_trivia_token, SimpleTokenizer, Token, TokenKind};
use crate::FormatNodeRule;
use ruff_text_size::{TextRange, TextSize};
#[derive(Default)]
pub struct FormatArguments;
@@ -28,6 +32,10 @@ impl FormatNodeRule<Arguments> for FormatArguments {
let saved_level = f.context().node_level();
f.context_mut().set_node_level(NodeLevel::Expression);
let comments = f.context().comments().clone();
let dangling = comments.dangling_comments(item);
let (slash, star) = find_argument_separators(f.context().contents(), item);
let format_inner = format_with(|f: &mut PyFormatter| {
let separator = format_with(|f| write!(f, [text(","), soft_line_break_or_space()]));
let mut joiner = f.join_with(separator);
@@ -39,9 +47,29 @@ impl FormatNodeRule<Arguments> for FormatArguments {
last_node = Some(arg_with_default.into());
}
if !posonlyargs.is_empty() {
joiner.entry(&text("/"));
}
let slash_comments_end = if posonlyargs.is_empty() {
0
} else {
let slash_comments_end = dangling.partition_point(|comment| {
let assignment = assign_argument_separator_comment_placement(
slash.as_ref(),
star.as_ref(),
comment.slice().range(),
comment.line_position(),
)
.expect("Unexpected dangling comment type in function arguments");
matches!(
assignment,
ArgumentSeparatorCommentLocation::SlashLeading
| ArgumentSeparatorCommentLocation::SlashTrailing
)
});
joiner.entry(&CommentsAroundText {
text: "/",
comments: &dangling[..slash_comments_end],
});
slash_comments_end
};
for arg_with_default in args {
joiner.entry(&arg_with_default.format());
@@ -60,7 +88,26 @@ impl FormatNodeRule<Arguments> for FormatArguments {
]);
last_node = Some(vararg.as_any_node_ref());
} else if !kwonlyargs.is_empty() {
joiner.entry(&text("*"));
// Given very strange comment placement, comments here may not actually have been
// marked as `StarLeading`/`StarTrailing`, but that's fine since we still produce
// a stable formatting in this case
// ```python
// def f42(
// a,
// / # 1
// # 2
// , # 3
// # 4
// * # 5
// , # 6
// c,
// ):
// pass
// ```
joiner.entry(&CommentsAroundText {
text: "*",
comments: &dangling[slash_comments_end..],
});
}
for arg_with_default in kwonlyargs {
@@ -127,7 +174,7 @@ impl FormatNodeRule<Arguments> for FormatArguments {
f,
[
text("("),
block_indent(&dangling_node_comments(item)),
block_indent(&dangling_comments(dangling)),
text(")")
]
)?;
@@ -152,3 +199,367 @@ impl FormatNodeRule<Arguments> for FormatArguments {
Ok(())
}
}
struct CommentsAroundText<'a> {
text: &'static str,
comments: &'a [SourceComment],
}
impl Format<PyFormatContext<'_>> for CommentsAroundText<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
if self.comments.is_empty() {
text(self.text).fmt(f)
} else {
// There might be own line comments in trailing, but those are weird and we can kinda
// ignore them
// ```python
// def f42(
// a,
// # leading comment (own line)
// / # first trailing comment (end-of-line)
// # trailing own line comment
// ,
// c,
// ):
// ```
let (leading, trailing) = self.comments.split_at(
self.comments
.partition_point(|comment| comment.line_position().is_own_line()),
);
write!(
f,
[
leading_comments(leading),
text(self.text),
trailing_comments(trailing)
]
)
}
}
}
/// `/` and `*` in a function signature
///
/// ```text
/// def f(arg_a, /, arg_b, *, arg_c): pass
/// ^ ^ ^ ^ ^ ^ slash preceding end
/// ^ ^ ^ ^ ^ slash (a separator)
/// ^ ^ ^ ^ slash following start
/// ^ ^ ^ star preceding end
/// ^ ^ star (a separator)
/// ^ star following start
/// ```
#[derive(Debug)]
pub(crate) struct ArgumentSeparator {
/// The end of the last node or separator before this separator
pub(crate) preceding_end: TextSize,
/// The range of the separator itself
pub(crate) separator: TextRange,
/// The start of the first node or separator following this separator
pub(crate) following_start: TextSize,
}
/// Finds slash and star in `f(a, /, b, *, c)`
///
/// Returns slash and star
pub(crate) fn find_argument_separators(
contents: &str,
arguments: &Arguments,
) -> (Option<ArgumentSeparator>, Option<ArgumentSeparator>) {
// We only compute preceding_end and token location here since following_start depends on the
// star location, but the star location depends on slash's position
let slash = if let Some(preceding_end) = arguments.posonlyargs.last().map(Ranged::end) {
// ```text
// def f(a1=1, a2=2, /, a3, a4): pass
// ^^^^^^^^^^^ the range (defaults)
// def f(a1, a2, /, a3, a4): pass
// ^^^^^^^^^^^^ the range (no default)
// ```
let range = TextRange::new(preceding_end, arguments.end());
let mut tokens = SimpleTokenizer::new(contents, range).skip_trivia();
let comma = tokens
.next()
.expect("The function definition can't end here");
debug_assert!(comma.kind() == TokenKind::Comma, "{comma:?}");
let slash = tokens
.next()
.expect("The function definition can't end here");
debug_assert!(slash.kind() == TokenKind::Slash, "{slash:?}");
Some((preceding_end, slash.range))
} else {
None
};
// If we have a vararg we have a node that the comments attach to
let star = if arguments.vararg.is_some() {
// When the vararg is present the comments attach there and we don't need to do manual
// formatting
None
} else if let Some(first_keyword_argument) = arguments.kwonlyargs.first() {
// Check in that order:
// * `f(a, /, b, *, c)` and `f(a=1, /, b=2, *, c)`
// * `f(a, /, *, b)`
// * `f(*, b)` (else branch)
let after_arguments = arguments
.args
.last()
.map(|arg| arg.range.end())
.or(slash.map(|(_, slash)| slash.end()));
if let Some(preceding_end) = after_arguments {
let range = TextRange::new(preceding_end, arguments.end());
let mut tokens = SimpleTokenizer::new(contents, range).skip_trivia();
let comma = tokens
.next()
.expect("The function definition can't end here");
debug_assert!(comma.kind() == TokenKind::Comma, "{comma:?}");
let star = tokens
.next()
.expect("The function definition can't end here");
debug_assert!(star.kind() == TokenKind::Star, "{star:?}");
Some(ArgumentSeparator {
preceding_end,
separator: star.range,
following_start: first_keyword_argument.start(),
})
} else {
let mut tokens = SimpleTokenizer::new(contents, arguments.range).skip_trivia();
let lparen = tokens
.next()
.expect("The function definition can't end here");
debug_assert!(lparen.kind() == TokenKind::LParen, "{lparen:?}");
let star = tokens
.next()
.expect("The function definition can't end here");
debug_assert!(star.kind() == TokenKind::Star, "{star:?}");
Some(ArgumentSeparator {
preceding_end: arguments.range.start(),
separator: star.range,
following_start: first_keyword_argument.start(),
})
}
} else {
None
};
// Now that we have star, compute how long slash trailing comments can go
// Check in that order:
// * `f(a, /, b)`
// * `f(a, /, *b)`
// * `f(a, /, *, b)`
// * `f(a, /)`
let slash_following_start = arguments
.args
.first()
.map(Ranged::start)
.or(arguments.vararg.as_ref().map(|first| first.start()))
.or(star.as_ref().map(|star| star.separator.start()))
.unwrap_or(arguments.end());
let slash = slash.map(|(preceding_end, slash)| ArgumentSeparator {
preceding_end,
separator: slash,
following_start: slash_following_start,
});
(slash, star)
}
/// Locates positional only arguments separator `/` or the keywords only arguments
/// separator `*` comments.
///
/// ```python
/// def test(
/// a,
/// # Positional only arguments after here
/// /, # trailing positional argument comment.
/// b,
/// ):
/// pass
/// ```
/// or
/// ```python
/// def f(
/// a="",
/// # Keyword only arguments only after here
/// *, # trailing keyword argument comment.
/// b="",
/// ):
/// pass
/// ```
/// or
/// ```python
/// def f(
/// a,
/// # positional only comment, leading
/// /, # positional only comment, trailing
/// b,
/// # keyword only comment, leading
/// *, # keyword only comment, trailing
/// c,
/// ):
/// pass
/// ```
/// Notably, the following is possible:
/// ```python
/// def f32(
/// a,
/// # positional only comment, leading
/// /, # positional only comment, trailing
/// # keyword only comment, leading
/// *, # keyword only comment, trailing
/// c,
/// ):
/// pass
/// ```
///
/// ## Background
///
/// ```text
/// def f(a1, a2): pass
/// ^^^^^^ arguments (args)
/// ```
/// Use a star to separate keyword only arguments:
/// ```text
/// def f(a1, a2, *, a3, a4): pass
/// ^^^^^^ arguments (args)
/// ^^^^^^ keyword only arguments (kwargs)
/// ```
/// Use a slash to separate positional only arguments. Note that this changes the arguments left
/// of the slash while the star change the arguments right of it:
/// ```text
/// def f(a1, a2, /, a3, a4): pass
/// ^^^^^^ positional only arguments (posonlyargs)
/// ^^^^^^ arguments (args)
/// ```
/// You can combine both:
/// ```text
/// def f(a1, a2, /, a3, a4, *, a5, a6): pass
/// ^^^^^^ positional only arguments (posonlyargs)
/// ^^^^^^ arguments (args)
/// ^^^^^^ keyword only arguments (kwargs)
/// ```
/// They can all have defaults, meaning that the preceding node ends at the default instead of the
/// argument itself:
/// ```text
/// def f(a1=1, a2=2, /, a3=3, a4=4, *, a5=5, a6=6): pass
/// ^ ^ ^ ^ ^ ^ defaults
/// ^^^^^^^^^^ positional only arguments (posonlyargs)
/// ^^^^^^^^^^ arguments (args)
/// ^^^^^^^^^^ keyword only arguments (kwargs)
/// ```
/// An especially difficult case is having no regular arguments, so comments from both slash and
/// star will attach to either a2 or a3 and the next token is incorrect.
/// ```text
/// def f(a1, a2, /, *, a3, a4): pass
/// ^^^^^^ positional only arguments (posonlyargs)
/// ^^^^^^ keyword only arguments (kwargs)
/// ```
pub(crate) fn assign_argument_separator_comment_placement(
slash: Option<&ArgumentSeparator>,
star: Option<&ArgumentSeparator>,
comment_range: TextRange,
text_position: CommentLinePosition,
) -> Option<ArgumentSeparatorCommentLocation> {
if let Some(ArgumentSeparator {
preceding_end,
separator: slash,
following_start,
}) = slash
{
// ```python
// def f(
// # start too early
// a, # not own line
// # this is the one
// /, # too late (handled later)
// b,
// )
// ```
if comment_range.start() > *preceding_end
&& comment_range.start() < slash.start()
&& text_position.is_own_line()
{
return Some(ArgumentSeparatorCommentLocation::SlashLeading);
}
// ```python
// def f(
// a,
// # too early (handled above)
// /, # this is the one
// # not end-of-line
// b,
// )
// ```
if comment_range.start() > slash.end()
&& comment_range.start() < *following_start
&& text_position.is_end_of_line()
{
return Some(ArgumentSeparatorCommentLocation::SlashTrailing);
}
}
if let Some(ArgumentSeparator {
preceding_end,
separator: star,
following_start,
}) = star
{
// ```python
// def f(
// # start too early
// a, # not own line
// # this is the one
// *, # too late (handled later)
// b,
// )
// ```
if comment_range.start() > *preceding_end
&& comment_range.start() < star.start()
&& text_position.is_own_line()
{
return Some(ArgumentSeparatorCommentLocation::StarLeading);
}
// ```python
// def f(
// a,
// # too early (handled above)
// *, # this is the one
// # not end-of-line
// b,
// )
// ```
if comment_range.start() > star.end()
&& comment_range.start() < *following_start
&& text_position.is_end_of_line()
{
return Some(ArgumentSeparatorCommentLocation::StarTrailing);
}
}
None
}
/// ```python
/// def f(
/// a,
/// # before slash
/// /, # after slash
/// b,
/// # before star
/// *, # after star
/// c,
/// ):
/// pass
/// ```
#[derive(Debug)]
pub(crate) enum ArgumentSeparatorCommentLocation {
SlashLeading,
SlashTrailing,
StarLeading,
StarTrailing,
}

View File

@@ -0,0 +1,28 @@
use crate::prelude::*;
use crate::AsFormat;
use ruff_formatter::{FormatOwnedWithRule, FormatRefWithRule};
use rustpython_parser::ast::{Identifier, Ranged};
pub struct FormatIdentifier;
impl FormatRule<Identifier, PyFormatContext<'_>> for FormatIdentifier {
fn fmt(&self, item: &Identifier, f: &mut PyFormatter) -> FormatResult<()> {
source_text_slice(item.range(), ContainsNewlines::No).fmt(f)
}
}
impl<'ast> AsFormat<PyFormatContext<'ast>> for Identifier {
type Format<'a> = FormatRefWithRule<'a, Identifier, FormatIdentifier, PyFormatContext<'ast>>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(self, FormatIdentifier)
}
}
impl<'ast> IntoFormat<PyFormatContext<'ast>> for Identifier {
type Format = FormatOwnedWithRule<Identifier, FormatIdentifier, PyFormatContext<'ast>>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(self, FormatIdentifier)
}
}

View File

@@ -5,6 +5,7 @@ pub(crate) mod arguments;
pub(crate) mod comprehension;
pub(crate) mod decorator;
pub(crate) mod except_handler_except_handler;
pub(crate) mod identifier;
pub(crate) mod keyword;
pub(crate) mod match_case;
pub(crate) mod type_ignore_type_ignore;

View File

@@ -55,27 +55,25 @@ y = 100(no)
+x = NOT_IMPLEMENTED_call()
+x = NOT_IMPLEMENTED_call()
+x = NOT_IMPLEMENTED_call()
+x = 1. .NOT_IMPLEMENTED_attr
+x = 1E+1 .NOT_IMPLEMENTED_attr
+x = 1E-1 .NOT_IMPLEMENTED_attr
+x = (1.).imag
+x = (1E+1).imag
+x = (1E-1).real
+x = NOT_IMPLEMENTED_call()
+x = 123456789.123456789E123456789 .NOT_IMPLEMENTED_attr
+x = (123456789.123456789E123456789).real
+x = NOT_IMPLEMENTED_call()
+x = 123456789J.NOT_IMPLEMENTED_attr
+x = 123456789J.real
+x = NOT_IMPLEMENTED_call()
+x = NOT_IMPLEMENTED_call()
+x = NOT_IMPLEMENTED_call()
+x = 0O777 .NOT_IMPLEMENTED_attr
+x = (0O777).real
+x = NOT_IMPLEMENTED_call()
+x = NOT_YET_IMPLEMENTED_ExprUnaryOp
+x = -100.0000J
-if (10).real:
+if 10 .NOT_IMPLEMENTED_attr:
if (10).real:
...
-y = 100[no]
y = 100[no]
-y = 100(no)
+y = NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+y = NOT_IMPLEMENTED_call()
```
@@ -85,24 +83,24 @@ y = 100(no)
x = NOT_IMPLEMENTED_call()
x = NOT_IMPLEMENTED_call()
x = NOT_IMPLEMENTED_call()
x = 1. .NOT_IMPLEMENTED_attr
x = 1E+1 .NOT_IMPLEMENTED_attr
x = 1E-1 .NOT_IMPLEMENTED_attr
x = (1.).imag
x = (1E+1).imag
x = (1E-1).real
x = NOT_IMPLEMENTED_call()
x = 123456789.123456789E123456789 .NOT_IMPLEMENTED_attr
x = (123456789.123456789E123456789).real
x = NOT_IMPLEMENTED_call()
x = 123456789J.NOT_IMPLEMENTED_attr
x = 123456789J.real
x = NOT_IMPLEMENTED_call()
x = NOT_IMPLEMENTED_call()
x = NOT_IMPLEMENTED_call()
x = 0O777 .NOT_IMPLEMENTED_attr
x = (0O777).real
x = NOT_IMPLEMENTED_call()
x = NOT_YET_IMPLEMENTED_ExprUnaryOp
x = -100.0000J
if 10 .NOT_IMPLEMENTED_attr:
if (10).real:
...
y = NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
y = 100[no]
y = NOT_IMPLEMENTED_call()
```

View File

@@ -19,12 +19,14 @@ lambda x=lambda y={1: 3}: y['x':lambda y: {1: 2}]: x
```diff
--- Black
+++ Ruff
@@ -1,4 +1,3 @@
@@ -1,4 +1,6 @@
-for ((x in {}) or {})["a"] in x:
- pass
+for ((NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right) or {})[
+ "NOT_YET_IMPLEMENTED_STRING"
+] in x:
pass
-pem_spam = lambda l, spam={"x": 3}: not spam.get(l.strip())
-lambda x=lambda y={1: 3}: y["x" : lambda y: {1: 2}]: x
+NOT_YET_IMPLEMENTED_StmtFor
+pem_spam = lambda x: True
+lambda x: True
```
@@ -32,7 +34,10 @@ lambda x=lambda y={1: 3}: y['x':lambda y: {1: 2}]: x
## Ruff Output
```py
NOT_YET_IMPLEMENTED_StmtFor
for ((NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right) or {})[
"NOT_YET_IMPLEMENTED_STRING"
] in x:
pass
pem_spam = lambda x: True
lambda x: True
```

View File

@@ -84,7 +84,7 @@ if True:
```diff
--- Black
+++ Ruff
@@ -1,99 +1,56 @@
@@ -1,61 +1,40 @@
-import core, time, a
+NOT_YET_IMPLEMENTED_StmtImport
@@ -164,12 +164,10 @@ if True:
+NOT_YET_IMPLEMENTED_StmtAssert
# looping over a 1-tuple should also not get wrapped
-for x in (1,):
- pass
-for (x,) in (1,), (2,), (3,):
- pass
+NOT_YET_IMPLEMENTED_StmtFor
+NOT_YET_IMPLEMENTED_StmtFor
for x in (1,):
@@ -63,14 +42,10 @@
for (x,) in (1,), (2,), (3,):
pass
-[
- 1,
@@ -183,13 +181,9 @@ if True:
+NOT_IMPLEMENTED_call()
if True:
- IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = (
- Config.IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING
- | {pylons.controllers.WSGIController}
- )
+ IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = Config.NOT_IMPLEMENTED_attr | {
+ pylons.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr,
+ }
IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = (
@@ -79,21 +54,6 @@
)
if True:
- ec2client.get_waiter("instance_stopped").wait(
@@ -257,8 +251,10 @@ y = {
NOT_YET_IMPLEMENTED_StmtAssert
# looping over a 1-tuple should also not get wrapped
NOT_YET_IMPLEMENTED_StmtFor
NOT_YET_IMPLEMENTED_StmtFor
for x in (1,):
pass
for (x,) in (1,), (2,), (3,):
pass
[1, 2, 3]
@@ -266,9 +262,10 @@ division_result_tuple = (6 / 2,)
NOT_IMPLEMENTED_call()
if True:
IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = Config.NOT_IMPLEMENTED_attr | {
pylons.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr,
}
IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = (
Config.IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING
| {pylons.controllers.WSGIController}
)
if True:
NOT_IMPLEMENTED_call()

View File

@@ -227,7 +227,7 @@ instruction()#comment with bad spacing
]
not_shareables = [
@@ -37,50 +33,51 @@
@@ -37,31 +33,35 @@
# builtin types and objects
type,
object,
@@ -263,47 +263,39 @@ instruction()#comment with bad spacing
def inline_comments_in_brackets_ruin_everything():
if typedargslist:
- parameters.children = [children[0], body, children[-1]] # (1 # )1
- parameters.children = [
- children[0],
+ parameters.NOT_IMPLEMENTED_attr = [
+ NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key], # (1
parameters.children = [
+ children[0], # (1
+ body,
+ NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key], # )1
+ children[-1], # )1
+ ]
+ parameters.NOT_IMPLEMENTED_attr = [
+ NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key],
+ parameters.children = [
children[0],
body,
- children[-1], # type: ignore
+ NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key], # type: ignore
]
else:
- parameters.children = [
- parameters.children[0], # (2 what if this was actually long
+ parameters.NOT_IMPLEMENTED_attr = [
+ NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key], # (2 what if this was actually long
children[-1], # type: ignore
@@ -72,14 +72,18 @@
body,
- parameters.children[-1], # )2
+ NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key], # )2
parameters.children[-1], # )2
]
- parameters.children = [parameters.what_if_this_was_actually_long.children[0], body, parameters.children[-1]] # type: ignore
- if (
- self._proc is not None
- # has the child process finished?
- and self._returncode is None
- # the child process has finished, but the
- # transport hasn't been notified yet?
- and self._proc.poll() is None
- ):
+ parameters.NOT_IMPLEMENTED_attr = [
+ NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key],
+ parameters.children = [
+ parameters.what_if_this_was_actually_long.children[0],
+ body,
+ NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key],
+ parameters.children[-1],
+ ] # type: ignore
+ if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
if (
- self._proc is not None
+ NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
# has the child process finished?
- and self._returncode is None
+ and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
# the child process has finished, but the
# transport hasn't been notified yet?
- and self._proc.poll() is None
+ and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
):
pass
# no newline before or after
short = [
@@ -91,48 +88,14 @@
@@ -91,48 +95,14 @@
]
# no newline after
@@ -357,7 +349,7 @@ instruction()#comment with bad spacing
while True:
if False:
continue
@@ -141,25 +104,13 @@
@@ -141,25 +111,13 @@
# and round and round we go
# let's return
@@ -386,7 +378,7 @@ instruction()#comment with bad spacing
#######################
@@ -167,7 +118,7 @@
@@ -167,7 +125,7 @@
#######################
@@ -458,28 +450,35 @@ else:
# Comment before function.
def inline_comments_in_brackets_ruin_everything():
if typedargslist:
parameters.NOT_IMPLEMENTED_attr = [
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key], # (1
parameters.children = [
children[0], # (1
body,
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key], # )1
children[-1], # )1
]
parameters.NOT_IMPLEMENTED_attr = [
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key],
parameters.children = [
children[0],
body,
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key], # type: ignore
children[-1], # type: ignore
]
else:
parameters.NOT_IMPLEMENTED_attr = [
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key], # (2 what if this was actually long
parameters.children = [
parameters.children[0], # (2 what if this was actually long
body,
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key], # )2
parameters.children[-1], # )2
]
parameters.NOT_IMPLEMENTED_attr = [
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key],
parameters.children = [
parameters.what_if_this_was_actually_long.children[0],
body,
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key],
parameters.children[-1],
] # type: ignore
if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
if (
NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
# has the child process finished?
and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
# the child process has finished, but the
# transport hasn't been notified yet?
and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
):
pass
# no newline before or after
short = [

View File

@@ -61,7 +61,7 @@ def func():
```diff
--- Black
+++ Ruff
@@ -3,46 +3,15 @@
@@ -3,46 +3,17 @@
# %%
def func():
@@ -82,7 +82,7 @@ def func():
- if isinstance(exc_value, MultiError):
+ if NOT_IMPLEMENTED_call():
embedded = []
- for exc in exc_value.exceptions:
for exc in exc_value.exceptions:
- if exc not in _seen:
- embedded.append(
- # This should be left alone (before)
@@ -97,7 +97,8 @@ def func():
- )
- # This should be left alone (after)
- )
+ NOT_YET_IMPLEMENTED_StmtFor
+ if NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
+ NOT_IMPLEMENTED_call()
# everything is fine if the expression isn't nested
- traceback.TracebackException.from_exception(
@@ -128,7 +129,9 @@ def func():
# Capture each of the exceptions in the MultiError along with each of their causes and contexts
if NOT_IMPLEMENTED_call():
embedded = []
NOT_YET_IMPLEMENTED_StmtFor
for exc in exc_value.exceptions:
if NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
NOT_IMPLEMENTED_call()
# everything is fine if the expression isn't nested
NOT_IMPLEMENTED_call()

View File

@@ -86,37 +86,38 @@ if __name__ == "__main__":
```diff
--- Black
+++ Ruff
@@ -1,33 +1,20 @@
@@ -1,6 +1,6 @@
while True:
- if something.changed:
if something.changed:
- do.stuff() # trailing comment
+ if something.NOT_IMPLEMENTED_attr:
+ NOT_IMPLEMENTED_call() # trailing comment
# Comment belongs to the `if` block.
# This one belongs to the `while` block.
# Should this one, too? I guess so.
@@ -8,26 +8,20 @@
# This one is properly standalone now.
-
-for i in range(100):
- # first we do this
- if i % 33 == 0:
- break
- # then we do this
-for i in range(100):
+for i in NOT_IMPLEMENTED_call():
# first we do this
- if i % 33 == 0:
+ if NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
break
# then we do this
- print(i)
- # and finally we loop around
+NOT_YET_IMPLEMENTED_StmtFor
+ NOT_IMPLEMENTED_call()
# and finally we loop around
-with open(some_temp_file) as f:
- data = f.read()
-
+NOT_YET_IMPLEMENTED_StmtWith
-try:
- with open(some_other_file) as w:
- w.write(data)
+NOT_YET_IMPLEMENTED_StmtWith
-
-except OSError:
- print("problems")
+NOT_YET_IMPLEMENTED_StmtTry
@@ -126,7 +127,7 @@ if __name__ == "__main__":
# leading function comment
@@ -42,7 +29,7 @@
@@ -42,7 +36,7 @@
# leading 1
@deco1
# leading 2
@@ -135,7 +136,7 @@ if __name__ == "__main__":
# leading 3
@deco3
def decorated1():
@@ -52,7 +39,7 @@
@@ -52,7 +46,7 @@
# leading 1
@deco1
# leading 2
@@ -144,7 +145,7 @@ if __name__ == "__main__":
# leading function comment
def decorated1():
...
@@ -69,5 +56,5 @@
@@ -69,5 +63,5 @@
...
@@ -158,7 +159,7 @@ if __name__ == "__main__":
```py
while True:
if something.NOT_IMPLEMENTED_attr:
if something.changed:
NOT_IMPLEMENTED_call() # trailing comment
# Comment belongs to the `if` block.
# This one belongs to the `while` block.
@@ -167,7 +168,14 @@ while True:
# This one is properly standalone now.
NOT_YET_IMPLEMENTED_StmtFor
for i in NOT_IMPLEMENTED_call():
# first we do this
if NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
break
# then we do this
NOT_IMPLEMENTED_call()
# and finally we loop around
NOT_YET_IMPLEMENTED_StmtWith

View File

@@ -137,7 +137,7 @@ aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*ite
def f(
@@ -49,10 +49,8 @@
@@ -49,10 +49,11 @@
element = 0 # type: int
another_element = 1 # type: float
another_element_with_long_name = 2 # type: int
@@ -146,16 +146,16 @@ aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*ite
- ) # type: int
- an_element_with_a_long_value = calls() or more_calls() and more() # type: bool
+ another_really_really_long_element_with_a_unnecessarily_long_name_to_describe_what_it_does_enterprise_style = 3 # type: int
+ an_element_with_a_long_value = NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2 # type: bool
+ an_element_with_a_long_value = (
+ NOT_IMPLEMENTED_call()
+ or NOT_IMPLEMENTED_call() and NOT_IMPLEMENTED_call()
+ ) # type: bool
tup = (
another_element,
@@ -84,35 +82,22 @@
@@ -86,33 +87,20 @@
def func(
- a=some_list[0], # type: int
+ a=NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key], # type: int
a=some_list[0], # type: int
): # type: () -> int
- c = call(
- 0.0123,
@@ -255,7 +255,10 @@ def f(
another_element = 1 # type: float
another_element_with_long_name = 2 # type: int
another_really_really_long_element_with_a_unnecessarily_long_name_to_describe_what_it_does_enterprise_style = 3 # type: int
an_element_with_a_long_value = NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2 # type: bool
an_element_with_a_long_value = (
NOT_IMPLEMENTED_call()
or NOT_IMPLEMENTED_call() and NOT_IMPLEMENTED_call()
) # type: bool
tup = (
another_element,
@@ -287,7 +290,7 @@ def f(
def func(
a=NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key], # type: int
a=some_list[0], # type: int
): # type: () -> int
c = NOT_IMPLEMENTED_call()

View File

@@ -109,38 +109,35 @@ async def wat():
```diff
--- Black
+++ Ruff
@@ -4,24 +4,15 @@
@@ -4,21 +4,15 @@
#
# Has many lines. Many, many lines.
# Many, many, many lines.
-"""Module docstring.
+"NOT_YET_IMPLEMENTED_STRING"
-
-Possibly also many, many lines.
-"""
+NOT_YET_IMPLEMENTED_StmtImport
+NOT_YET_IMPLEMENTED_StmtImport
+"NOT_YET_IMPLEMENTED_STRING"
-import os.path
-import sys
-
+NOT_YET_IMPLEMENTED_StmtImport
+NOT_YET_IMPLEMENTED_StmtImport
-import a
-from b.c import X # some noqa comment
-
+NOT_YET_IMPLEMENTED_StmtImport
+NOT_YET_IMPLEMENTED_StmtImportFrom # some noqa comment
-try:
- import fast
-except ImportError:
- import slow as fast
-
+NOT_YET_IMPLEMENTED_StmtImport
+NOT_YET_IMPLEMENTED_StmtImportFrom # some noqa comment
-# Some comment before a function.
+NOT_YET_IMPLEMENTED_StmtTry
y = 1
(
# some strings
@@ -30,67 +21,50 @@
# Some comment before a function.
@@ -30,67 +24,50 @@
def function(default=None):
@@ -177,17 +174,17 @@ async def wat():
# Another comment!
# This time two lines.
-
-
-class Foo:
- """Docstring for class Foo. Example from Sphinx docs."""
-
- #: Doc comment for class attribute Foo.bar.
- #: It can have multiple lines.
- bar = 1
-
- flox = 1.5 #: Doc comment for Foo.flox. One line only.
-
- baz = 2
- """Docstring for class attribute Foo.baz."""
-
@@ -245,6 +242,9 @@ NOT_YET_IMPLEMENTED_StmtImport
NOT_YET_IMPLEMENTED_StmtImportFrom # some noqa comment
NOT_YET_IMPLEMENTED_StmtTry
# Some comment before a function.
y = 1
(
# some strings

View File

@@ -105,7 +105,7 @@ def g():
```diff
--- Black
+++ Ruff
@@ -1,89 +1,70 @@
@@ -1,59 +1,46 @@
-"""Docstring."""
+"NOT_YET_IMPLEMENTED_STRING"
@@ -119,12 +119,9 @@ def g():
+ SPACE = "NOT_YET_IMPLEMENTED_STRING"
+ DOUBLESPACE = "NOT_YET_IMPLEMENTED_STRING"
- t = leaf.type
- p = leaf.parent # trailing comment
- v = leaf.value
+ t = leaf.NOT_IMPLEMENTED_attr
+ p = leaf.NOT_IMPLEMENTED_attr # trailing comment
+ v = leaf.NOT_IMPLEMENTED_attr
t = leaf.type
p = leaf.parent # trailing comment
v = leaf.value
- if t in ALWAYS_NO_SPACE:
+ if NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
@@ -136,14 +133,12 @@ def g():
- assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}"
+ NOT_YET_IMPLEMENTED_StmtAssert
- prev = leaf.prev_sibling
- if not prev:
prev = leaf.prev_sibling
if not prev:
- prevp = preceding_leaf(p)
- if not prevp or prevp.type in OPENING_BRACKETS:
+ prev = leaf.NOT_IMPLEMENTED_attr
+ if NOT_YET_IMPLEMENTED_ExprUnaryOp:
+ prevp = NOT_IMPLEMENTED_call()
+ if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
+ if not prevp or NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
return NO
- if prevp.type == token.EQUAL:
@@ -155,7 +150,7 @@ def g():
- syms.argument,
- }:
+ if NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
+ if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
+ if prevp.parent and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
return NO
- elif prevp.type == token.DOUBLESTAR:
@@ -167,7 +162,7 @@ def g():
- syms.dictsetmaker,
- }:
+ elif NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
+ if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
+ if prevp.parent and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
return NO
@@ -184,12 +179,9 @@ def g():
+ SPACE = "NOT_YET_IMPLEMENTED_STRING"
+ DOUBLESPACE = "NOT_YET_IMPLEMENTED_STRING"
- t = leaf.type
- p = leaf.parent
- v = leaf.value
+ t = leaf.NOT_IMPLEMENTED_attr
+ p = leaf.NOT_IMPLEMENTED_attr
+ v = leaf.NOT_IMPLEMENTED_attr
t = leaf.type
p = leaf.parent
@@ -61,29 +48,23 @@
# Comment because comments
@@ -204,15 +196,13 @@ def g():
- assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}"
+ NOT_YET_IMPLEMENTED_StmtAssert
- prev = leaf.prev_sibling
- if not prev:
prev = leaf.prev_sibling
if not prev:
- prevp = preceding_leaf(p)
+ prev = leaf.NOT_IMPLEMENTED_attr
+ if NOT_YET_IMPLEMENTED_ExprUnaryOp:
+ prevp = NOT_IMPLEMENTED_call()
- if not prevp or prevp.type in OPENING_BRACKETS:
+ if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
+ if not prevp or NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
# Start of the line or a bracketed expression.
# More than one line for the comment.
return NO
@@ -226,7 +216,7 @@ def g():
- syms.argument,
- }:
+ if NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
+ if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
+ if prevp.parent and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
return NO
```
@@ -242,9 +232,9 @@ def f():
SPACE = "NOT_YET_IMPLEMENTED_STRING"
DOUBLESPACE = "NOT_YET_IMPLEMENTED_STRING"
t = leaf.NOT_IMPLEMENTED_attr
p = leaf.NOT_IMPLEMENTED_attr # trailing comment
v = leaf.NOT_IMPLEMENTED_attr
t = leaf.type
p = leaf.parent # trailing comment
v = leaf.value
if NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
pass
@@ -253,18 +243,18 @@ def f():
NOT_YET_IMPLEMENTED_StmtAssert
prev = leaf.NOT_IMPLEMENTED_attr
if NOT_YET_IMPLEMENTED_ExprUnaryOp:
prev = leaf.prev_sibling
if not prev:
prevp = NOT_IMPLEMENTED_call()
if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
if not prevp or NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
return NO
if NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
if prevp.parent and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
return NO
elif NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
if prevp.parent and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
return NO
@@ -277,9 +267,9 @@ def g():
SPACE = "NOT_YET_IMPLEMENTED_STRING"
DOUBLESPACE = "NOT_YET_IMPLEMENTED_STRING"
t = leaf.NOT_IMPLEMENTED_attr
p = leaf.NOT_IMPLEMENTED_attr
v = leaf.NOT_IMPLEMENTED_attr
t = leaf.type
p = leaf.parent
v = leaf.value
# Comment because comments
@@ -291,17 +281,17 @@ def g():
# Another comment because more comments
NOT_YET_IMPLEMENTED_StmtAssert
prev = leaf.NOT_IMPLEMENTED_attr
if NOT_YET_IMPLEMENTED_ExprUnaryOp:
prev = leaf.prev_sibling
if not prev:
prevp = NOT_IMPLEMENTED_call()
if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
if not prevp or NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
# Start of the line or a bracketed expression.
# More than one line for the comment.
return NO
if NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
if prevp.parent and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
return NO
```

View File

@@ -276,45 +276,10 @@ last_call()
Name
None
True
@@ -7,226 +8,225 @@
1
1.0
1j
-True or False
-True or False or None
-True and False
-True and False and None
-(Name1 and Name2) or Name3
-Name1 and Name2 or Name3
-Name1 or (Name2 and Name3)
-Name1 or Name2 and Name3
-(Name1 and Name2) or (Name3 and Name4)
-Name1 and Name2 or Name3 and Name4
-Name1 or (Name2 and Name3) or Name4
-Name1 or Name2 and Name3 or Name4
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
v1 << 2
1 >> v2
1 % finished
1 + v2 - v3 * 4 ^ 5**v6 / 7 // 8
((1 + v2) - (v3 * 4)) ^ (((5**v6) / 7) // 8)
-not great
-~great
-+value
--1
-~int and not v1 ^ 123 + v2 | True
-(~int) and (not ((v1 ^ (123 + v2)) | True))
@@ -30,98 +31,90 @@
-1
~int and not v1 ^ 123 + v2 | True
(~int) and (not ((v1 ^ (123 + v2)) | True))
-+(really ** -(confusing ** ~(operator**-precedence)))
-flags & ~select.EPOLLIN and waiters.write_task is not None
-lambda arg: None
@@ -339,14 +304,8 @@ last_call()
-)
-{"2.7": dead, "3.7": (long_live or die_hard)}
-{"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}}
+NOT_YET_IMPLEMENTED_ExprUnaryOp
+NOT_YET_IMPLEMENTED_ExprUnaryOp
+NOT_YET_IMPLEMENTED_ExprUnaryOp
+NOT_YET_IMPLEMENTED_ExprUnaryOp
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+NOT_YET_IMPLEMENTED_ExprUnaryOp
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
++really ** -confusing ** ~operator**-precedence
+flags & ~select.EPOLLIN and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
+lambda x: True
+lambda x: True
+lambda x: True
@@ -362,15 +321,11 @@ last_call()
+(NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false)
+{
+ "NOT_YET_IMPLEMENTED_STRING": dead,
+ "NOT_YET_IMPLEMENTED_STRING": (
+ NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+ ),
+ "NOT_YET_IMPLEMENTED_STRING": (long_live or die_hard),
+}
+{
+ "NOT_YET_IMPLEMENTED_STRING": dead,
+ "NOT_YET_IMPLEMENTED_STRING": (
+ NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+ ),
+ "NOT_YET_IMPLEMENTED_STRING": (long_live or die_hard),
+ **{"NOT_YET_IMPLEMENTED_STRING": verygood},
+}
{**a, **b, **c}
@@ -384,43 +339,42 @@ last_call()
+ "NOT_YET_IMPLEMENTED_STRING",
+ (NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false),
+}
+NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
+(
+ {"NOT_YET_IMPLEMENTED_STRING": "NOT_YET_IMPLEMENTED_STRING"},
+ (True or False),
+ (+value),
+ "NOT_YET_IMPLEMENTED_STRING",
+ b"NOT_YET_IMPLEMENTED_BYTE_STRING",
+) or None
()
(1,)
(1, 2)
(1, 2, 3)
[]
-[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)]
[
1,
2,
3,
[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)]
-[
- 1,
- 2,
- 3,
-]
-[*a]
-[*range(10)]
-[
- *a,
4,
5,
-]
-[
- 4,
- *a,
- 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ (NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2),
+ (NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2),
+ (NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2),
]
-]
+[1, 2, 3]
+[NOT_YET_IMPLEMENTED_ExprStarred]
+[NOT_YET_IMPLEMENTED_ExprStarred]
+[NOT_YET_IMPLEMENTED_ExprStarred, 4, 5]
+[4, NOT_YET_IMPLEMENTED_ExprStarred, 5]
[
- 4,
- *a,
- 5,
-]
-[
this_is_a_very_long_variable_which_will_force_a_delimiter_split,
element,
another,
@@ -439,6 +393,28 @@ last_call()
-{i: j for i, j in ((1, "a"), (2, "b"), (3, "c"))}
-{a: b * 2 for a, b in dictionary.items()}
-{a: b * -2 for a, b in dictionary.items()}
-{
- k: v
- for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension
-}
-Python3 > Python2 > COBOL
-Life is Life
-call()
-call(arg)
-call(kwarg="hey")
-call(arg, kwarg="hey")
-call(arg, another, kwarg="hey", **kwargs)
-call(
- this_is_a_very_long_variable_which_will_force_a_delimiter_split,
- arg,
- another,
- kwarg="hey",
- **kwargs,
-) # note: no trailing comma pre-3.6
-call(*gidgets[:2])
-call(a, *gidgets[:2])
-call(**self.screen_kwargs)
-call(b, **self.screen_kwargs)
+NOT_YET_IMPLEMENTED_ExprSetComp
+NOT_YET_IMPLEMENTED_ExprSetComp
+NOT_YET_IMPLEMENTED_ExprSetComp
@@ -464,81 +440,16 @@ last_call()
+NOT_IMPLEMENTED_call()
+NOT_IMPLEMENTED_call()
+NOT_IMPLEMENTED_call()
+lukasz.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr
+NOT_IMPLEMENTED_call()
+1 .NOT_IMPLEMENTED_attr
+1.0 .NOT_IMPLEMENTED_attr
+....NOT_IMPLEMENTED_attr
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+NOT_YET_IMPLEMENTED_StmtAnnAssign # type: ignore
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false
{
- k: v
- for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension
+ "NOT_YET_IMPLEMENTED_STRING": dead,
+ "NOT_YET_IMPLEMENTED_STRING": NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2,
}
-Python3 > Python2 > COBOL
-Life is Life
-call()
-call(arg)
-call(kwarg="hey")
-call(arg, kwarg="hey")
-call(arg, another, kwarg="hey", **kwargs)
-call(
- this_is_a_very_long_variable_which_will_force_a_delimiter_split,
- arg,
- another,
- kwarg="hey",
- **kwargs,
-) # note: no trailing comma pre-3.6
-call(*gidgets[:2])
-call(a, *gidgets[:2])
-call(**self.screen_kwargs)
-call(b, **self.screen_kwargs)
-lukasz.langa.pl
lukasz.langa.pl
-call.me(maybe)
-(1).real
-(1.0).real
-....__class__
-list[str]
-dict[str, int]
-tuple[str, ...]
-tuple[str, int, float, dict[str, int]]
-tuple[
+NOT_IMPLEMENTED_call()
(1).real
(1.0).real
....__class__
@@ -130,34 +123,28 @@
tuple[str, ...]
tuple[str, int, float, dict[str, int]]
tuple[
- str,
- int,
- float,
@@ -546,27 +457,12 @@ last_call()
-]
-very_long_variable_name_filters: t.List[
- t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]],
+{
+ "NOT_YET_IMPLEMENTED_STRING",
+ "NOT_YET_IMPLEMENTED_STRING",
+ "NOT_YET_IMPLEMENTED_STRING",
+ "NOT_YET_IMPLEMENTED_STRING",
+ "NOT_YET_IMPLEMENTED_STRING",
+ NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false,
+}
+[
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2,
+ NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2,
+ NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2,
+ (
+ str,
+ int,
+ float,
+ dict[str, int],
+ )
]
-xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore
- sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
@@ -577,36 +473,51 @@ last_call()
-xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(
- sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
-) # type: ignore
-slice[0]
-slice[0:1]
-slice[0:1:2]
-slice[:]
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+NOT_YET_IMPLEMENTED_StmtAnnAssign # type: ignore
slice[0]
slice[0:1]
slice[0:1:2]
slice[:]
-slice[:-1]
-slice[1:]
+slice[ : -1]
slice[1:]
-slice[::-1]
-slice[d :: d + 1]
-slice[:c, c - 1]
-numpy[:, 0:1]
+slice[ :: -1]
slice[d :: d + 1]
slice[:c, c - 1]
numpy[:, 0:1]
-numpy[:, :-1]
-numpy[0, :]
-numpy[:, i]
-numpy[0, :2]
-numpy[:N, 0]
-numpy[:2, :4]
-numpy[2:4, 1:5]
-numpy[4:, 2:]
-numpy[:, (0, 1, 2, 5)]
-numpy[0, [0]]
-numpy[:, [i]]
-numpy[1 : c + 1, c]
-numpy[-(c + 1) :, d]
-numpy[:, l[-2]]
+numpy[:, : -1]
numpy[0, :]
numpy[:, i]
numpy[0, :2]
@@ -171,62 +158,58 @@
numpy[1 : c + 1, c]
numpy[-(c + 1) :, d]
numpy[:, l[-2]]
-numpy[:, ::-1]
-numpy[np.newaxis, :]
+numpy[:, :: -1]
numpy[np.newaxis, :]
-(str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None)
-{"2.7": dead, "3.7": long_live or die_hard}
-{"2.7", "3.6", "3.7", "3.8", "3.9", "4.0" if gilectomy else "3.10"}
-[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C]
+NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false
+{
+ "NOT_YET_IMPLEMENTED_STRING": dead,
+ "NOT_YET_IMPLEMENTED_STRING": long_live or die_hard,
+}
+{
+ "NOT_YET_IMPLEMENTED_STRING",
+ "NOT_YET_IMPLEMENTED_STRING",
+ "NOT_YET_IMPLEMENTED_STRING",
+ "NOT_YET_IMPLEMENTED_STRING",
+ "NOT_YET_IMPLEMENTED_STRING",
+ NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false,
+}
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C]
(SomeName)
SomeName
(Good, Bad, Ugly)
@@ -644,7 +555,13 @@ last_call()
-g = 1, *"ten"
-what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set(
- vars_to_remove
-)
+e = NOT_IMPLEMENTED_call()
+f = 1, NOT_YET_IMPLEMENTED_ExprStarred
+g = 1, NOT_YET_IMPLEMENTED_ExprStarred
+what_is_up_with_those_new_coord_names = (
+ (coord_names + NOT_IMPLEMENTED_call())
+ + NOT_IMPLEMENTED_call()
)
-what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set(
- vars_to_remove
-)
@@ -655,13 +572,7 @@ last_call()
- )
- .order_by(models.Customer.id.asc())
- .all()
+e = NOT_IMPLEMENTED_call()
+f = 1, NOT_YET_IMPLEMENTED_ExprStarred
+g = 1, NOT_YET_IMPLEMENTED_ExprStarred
+what_is_up_with_those_new_coord_names = (
+ (coord_names + NOT_IMPLEMENTED_call())
+ + NOT_IMPLEMENTED_call()
)
-)
-result = (
- session.query(models.Customer.id)
- .filter(
@@ -684,7 +595,7 @@ last_call()
mapping = {
A: 0.25 * (10.0 / 12),
B: 0.1 * (10.0 / 12),
@@ -236,65 +236,35 @@
@@ -236,31 +219,29 @@
def gen():
@@ -711,38 +622,31 @@ last_call()
- force=False
-), "Short message"
-assert parens is TooMany
-for (x,) in (1,), (2,), (3,):
- ...
-for y in ():
+NOT_IMPLEMENTED_call()
+NOT_IMPLEMENTED_call()
+NOT_IMPLEMENTED_call()
+NOT_YET_IMPLEMENTED_StmtAssert
+NOT_YET_IMPLEMENTED_StmtAssert
+NOT_YET_IMPLEMENTED_StmtAssert
+NOT_YET_IMPLEMENTED_StmtFor
+NOT_YET_IMPLEMENTED_StmtFor
+NOT_YET_IMPLEMENTED_StmtFor
+NOT_YET_IMPLEMENTED_StmtFor
+NOT_YET_IMPLEMENTED_StmtFor
+while NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
for (x,) in (1,), (2,), (3,):
...
for y in ():
...
-for z in (i for i in (1, 2, 3)):
- ...
+for z in (i for i in []):
...
-for i in call():
- ...
-for j in 1 + (2 + 3):
- ...
-while this and that:
- ...
-for (
- addr_family,
- addr_type,
- addr_proto,
- addr_canonname,
- addr_sockaddr,
+for i in NOT_IMPLEMENTED_call():
...
for j in 1 + (2 + 3):
...
@@ -272,28 +253,16 @@
addr_proto,
addr_canonname,
addr_sockaddr,
-) in socket.getaddrinfo("google.com", "http"):
- pass
+) in NOT_IMPLEMENTED_call():
pass
-a = (
- aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp
- in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
@@ -759,84 +663,54 @@ last_call()
- aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp
- is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
-)
-if (
+a = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
+a = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
+a = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
+a = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
if (
- threading.current_thread() != threading.main_thread()
- and threading.current_thread() != threading.main_thread()
- or signal.getsignal(signal.SIGINT) != signal.default_int_handler
-):
+NOT_YET_IMPLEMENTED_StmtFor
+a = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
+a = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
+a = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
+a = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
+if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
+ NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
+ and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
+ or NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
):
return True
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
@@ -327,24 +297,44 @@
@@ -327,13 +296,18 @@
):
return True
if (
- ~aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e
- | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n
+ NOT_YET_IMPLEMENTED_ExprUnaryOp
+ + aaaa.NOT_IMPLEMENTED_attr
+ - aaaa.NOT_IMPLEMENTED_attr * aaaa.NOT_IMPLEMENTED_attr / aaaa.NOT_IMPLEMENTED_attr
+ | aaaa.NOT_IMPLEMENTED_attr
+ & aaaa.NOT_IMPLEMENTED_attr % aaaa.NOT_IMPLEMENTED_attr
+ ^ aaaa.NOT_IMPLEMENTED_attr
+ << aaaa.NOT_IMPLEMENTED_attr
+ >> aaaa.NOT_IMPLEMENTED_attr**aaaa.NOT_IMPLEMENTED_attr // aaaa.NOT_IMPLEMENTED_attr
+ ~aaaa.a
+ + aaaa.b
+ - aaaa.c * aaaa.d / aaaa.e
| aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n
):
return True
if (
- ~aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e
- | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h
- ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n
+ NOT_YET_IMPLEMENTED_ExprUnaryOp
+ + aaaaaaaa.NOT_IMPLEMENTED_attr
+ - aaaaaaaa.NOT_IMPLEMENTED_attr
+ @ aaaaaaaa.NOT_IMPLEMENTED_attr
+ / aaaaaaaa.NOT_IMPLEMENTED_attr
+ | aaaaaaaa.NOT_IMPLEMENTED_attr
+ & aaaaaaaa.NOT_IMPLEMENTED_attr % aaaaaaaa.NOT_IMPLEMENTED_attr
+ ^ aaaaaaaa.NOT_IMPLEMENTED_attr
+ << aaaaaaaa.NOT_IMPLEMENTED_attr
+ >> aaaaaaaa.NOT_IMPLEMENTED_attr
+ **aaaaaaaa.NOT_IMPLEMENTED_attr
+ // aaaaaaaa.NOT_IMPLEMENTED_attr
+ ~aaaaaaaa.a
+ + aaaaaaaa.b
+ - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e
+ | aaaaaaaa.f
+ & aaaaaaaa.g % aaaaaaaa.h
^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n
):
return True
if (
- ~aaaaaaaaaaaaaaaa.a
- + aaaaaaaaaaaaaaaa.b
- - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e
@@ -341,7 +315,8 @@
~aaaaaaaaaaaaaaaa.a
+ aaaaaaaaaaaaaaaa.b
- aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e
- | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h
- ^ aaaaaaaaaaaaaaaa.i
- << aaaaaaaaaaaaaaaa.k
- >> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n
+ NOT_YET_IMPLEMENTED_ExprUnaryOp
+ + aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
+ - aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
+ * aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
+ @ aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
+ | aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
+ & aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr % aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
+ ^ aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
+ << aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
+ >> aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
+ **aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
+ // aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
):
return True
(
@@ -363,8 +353,9 @@
bbbb >> bbbb * bbbb
(
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- ^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ ^ bbbb.NOT_IMPLEMENTED_attr
+ & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ | aaaaaaaaaaaaaaaa.f
+ & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h
^ aaaaaaaaaaaaaaaa.i
<< aaaaaaaaaaaaaaaa.k
>> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n
@@ -366,5 +341,5 @@
^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
)
-last_call()
@@ -857,31 +731,31 @@ False
1
1.0
1j
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
True or False
True or False or None
True and False
True and False and None
(Name1 and Name2) or Name3
Name1 and Name2 or Name3
Name1 or (Name2 and Name3)
Name1 or Name2 and Name3
(Name1 and Name2) or (Name3 and Name4)
Name1 and Name2 or Name3 and Name4
Name1 or (Name2 and Name3) or Name4
Name1 or Name2 and Name3 or Name4
v1 << 2
1 >> v2
1 % finished
1 + v2 - v3 * 4 ^ 5**v6 / 7 // 8
((1 + v2) - (v3 * 4)) ^ (((5**v6) / 7) // 8)
NOT_YET_IMPLEMENTED_ExprUnaryOp
NOT_YET_IMPLEMENTED_ExprUnaryOp
NOT_YET_IMPLEMENTED_ExprUnaryOp
NOT_YET_IMPLEMENTED_ExprUnaryOp
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
NOT_YET_IMPLEMENTED_ExprUnaryOp
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
not great
~great
+value
-1
~int and not v1 ^ 123 + v2 | True
(~int) and (not ((v1 ^ (123 + v2)) | True))
+really ** -confusing ** ~operator**-precedence
flags & ~select.EPOLLIN and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
lambda x: True
lambda x: True
lambda x: True
@@ -897,15 +771,11 @@ NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false
(NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false)
{
"NOT_YET_IMPLEMENTED_STRING": dead,
"NOT_YET_IMPLEMENTED_STRING": (
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
),
"NOT_YET_IMPLEMENTED_STRING": (long_live or die_hard),
}
{
"NOT_YET_IMPLEMENTED_STRING": dead,
"NOT_YET_IMPLEMENTED_STRING": (
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
),
"NOT_YET_IMPLEMENTED_STRING": (long_live or die_hard),
**{"NOT_YET_IMPLEMENTED_STRING": verygood},
}
{**a, **b, **c}
@@ -917,26 +787,19 @@ NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false
"NOT_YET_IMPLEMENTED_STRING",
(NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false),
}
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2
(
{"NOT_YET_IMPLEMENTED_STRING": "NOT_YET_IMPLEMENTED_STRING"},
(True or False),
(+value),
"NOT_YET_IMPLEMENTED_STRING",
b"NOT_YET_IMPLEMENTED_BYTE_STRING",
) or None
()
(1,)
(1, 2)
(1, 2, 3)
[]
[
1,
2,
3,
4,
5,
6,
7,
8,
9,
(NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2),
(NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2),
(NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2),
]
[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)]
[1, 2, 3]
[NOT_YET_IMPLEMENTED_ExprStarred]
[NOT_YET_IMPLEMENTED_ExprStarred]
@@ -973,50 +836,57 @@ NOT_IMPLEMENTED_call()
NOT_IMPLEMENTED_call()
NOT_IMPLEMENTED_call()
NOT_IMPLEMENTED_call()
lukasz.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr
lukasz.langa.pl
NOT_IMPLEMENTED_call()
1 .NOT_IMPLEMENTED_attr
1.0 .NOT_IMPLEMENTED_attr
....NOT_IMPLEMENTED_attr
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
(1).real
(1.0).real
....__class__
list[str]
dict[str, int]
tuple[str, ...]
tuple[str, int, float, dict[str, int]]
tuple[
(
str,
int,
float,
dict[str, int],
)
]
NOT_YET_IMPLEMENTED_StmtAnnAssign
NOT_YET_IMPLEMENTED_StmtAnnAssign
NOT_YET_IMPLEMENTED_StmtAnnAssign
NOT_YET_IMPLEMENTED_StmtAnnAssign # type: ignore
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
slice[0]
slice[0:1]
slice[0:1:2]
slice[:]
slice[ : -1]
slice[1:]
slice[ :: -1]
slice[d :: d + 1]
slice[:c, c - 1]
numpy[:, 0:1]
numpy[:, : -1]
numpy[0, :]
numpy[:, i]
numpy[0, :2]
numpy[:N, 0]
numpy[:2, :4]
numpy[2:4, 1:5]
numpy[4:, 2:]
numpy[:, (0, 1, 2, 5)]
numpy[0, [0]]
numpy[:, [i]]
numpy[1 : c + 1, c]
numpy[-(c + 1) :, d]
numpy[:, l[-2]]
numpy[:, :: -1]
numpy[np.newaxis, :]
NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false
{
"NOT_YET_IMPLEMENTED_STRING": dead,
"NOT_YET_IMPLEMENTED_STRING": NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2,
"NOT_YET_IMPLEMENTED_STRING": long_live or die_hard,
}
{
"NOT_YET_IMPLEMENTED_STRING",
@@ -1026,20 +896,7 @@ NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false
"NOT_YET_IMPLEMENTED_STRING",
NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false,
}
[
1,
2,
3,
4,
5,
6,
7,
8,
9,
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2,
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2,
NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2,
]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C]
(SomeName)
SomeName
(Good, Bad, Ugly)
@@ -1101,19 +958,35 @@ NOT_IMPLEMENTED_call()
NOT_YET_IMPLEMENTED_StmtAssert
NOT_YET_IMPLEMENTED_StmtAssert
NOT_YET_IMPLEMENTED_StmtAssert
NOT_YET_IMPLEMENTED_StmtFor
NOT_YET_IMPLEMENTED_StmtFor
NOT_YET_IMPLEMENTED_StmtFor
NOT_YET_IMPLEMENTED_StmtFor
NOT_YET_IMPLEMENTED_StmtFor
while NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
for (x,) in (1,), (2,), (3,):
...
NOT_YET_IMPLEMENTED_StmtFor
for y in ():
...
for z in (i for i in []):
...
for i in NOT_IMPLEMENTED_call():
...
for j in 1 + (2 + 3):
...
while this and that:
...
for (
addr_family,
addr_type,
addr_proto,
addr_canonname,
addr_sockaddr,
) in NOT_IMPLEMENTED_call():
pass
a = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
a = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
a = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
a = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
if (
NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
or NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
):
return True
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
@@ -1146,44 +1019,30 @@ if (
):
return True
if (
NOT_YET_IMPLEMENTED_ExprUnaryOp
+ aaaa.NOT_IMPLEMENTED_attr
- aaaa.NOT_IMPLEMENTED_attr * aaaa.NOT_IMPLEMENTED_attr / aaaa.NOT_IMPLEMENTED_attr
| aaaa.NOT_IMPLEMENTED_attr
& aaaa.NOT_IMPLEMENTED_attr % aaaa.NOT_IMPLEMENTED_attr
^ aaaa.NOT_IMPLEMENTED_attr
<< aaaa.NOT_IMPLEMENTED_attr
>> aaaa.NOT_IMPLEMENTED_attr**aaaa.NOT_IMPLEMENTED_attr // aaaa.NOT_IMPLEMENTED_attr
~aaaa.a
+ aaaa.b
- aaaa.c * aaaa.d / aaaa.e
| aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n
):
return True
if (
NOT_YET_IMPLEMENTED_ExprUnaryOp
+ aaaaaaaa.NOT_IMPLEMENTED_attr
- aaaaaaaa.NOT_IMPLEMENTED_attr
@ aaaaaaaa.NOT_IMPLEMENTED_attr
/ aaaaaaaa.NOT_IMPLEMENTED_attr
| aaaaaaaa.NOT_IMPLEMENTED_attr
& aaaaaaaa.NOT_IMPLEMENTED_attr % aaaaaaaa.NOT_IMPLEMENTED_attr
^ aaaaaaaa.NOT_IMPLEMENTED_attr
<< aaaaaaaa.NOT_IMPLEMENTED_attr
>> aaaaaaaa.NOT_IMPLEMENTED_attr
**aaaaaaaa.NOT_IMPLEMENTED_attr
// aaaaaaaa.NOT_IMPLEMENTED_attr
~aaaaaaaa.a
+ aaaaaaaa.b
- aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e
| aaaaaaaa.f
& aaaaaaaa.g % aaaaaaaa.h
^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n
):
return True
if (
NOT_YET_IMPLEMENTED_ExprUnaryOp
+ aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
- aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
* aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
@ aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
| aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
& aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr % aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
^ aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
<< aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
>> aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
**aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
// aaaaaaaaaaaaaaaa.NOT_IMPLEMENTED_attr
~aaaaaaaaaaaaaaaa.a
+ aaaaaaaaaaaaaaaa.b
- aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e
| aaaaaaaaaaaaaaaa.f
& aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h
^ aaaaaaaaaaaaaaaa.i
<< aaaaaaaaaaaaaaaa.k
>> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n
):
return True
(
@@ -1202,8 +1061,7 @@ aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa
bbbb >> bbbb * bbbb
(
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
^ bbbb.NOT_IMPLEMENTED_attr
& aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
)
NOT_IMPLEMENTED_call()

View File

@@ -134,7 +134,7 @@ elif unformatted:
return True
# yapf: enable
elif b:
@@ -39,49 +21,27 @@
@@ -39,49 +21,29 @@
# Regression test for https://github.com/psf/black/issues/2567.
if True:
# fmt: off
@@ -142,7 +142,9 @@ elif unformatted:
- # fmt: on
- print ( "This won't be formatted" )
- print ( "This won't be formatted either" )
+ NOT_YET_IMPLEMENTED_StmtFor
+ for _ in NOT_IMPLEMENTED_call():
+ # fmt: on
+ NOT_IMPLEMENTED_call()
+ NOT_IMPLEMENTED_call()
else:
- print("This will be formatted")
@@ -220,7 +222,9 @@ def test_func():
# Regression test for https://github.com/psf/black/issues/2567.
if True:
# fmt: off
NOT_YET_IMPLEMENTED_StmtFor
for _ in NOT_IMPLEMENTED_call():
# fmt: on
NOT_IMPLEMENTED_call()
NOT_IMPLEMENTED_call()
else:
NOT_IMPLEMENTED_call()

View File

@@ -222,7 +222,7 @@ d={'a':1,
# Comment 1
# Comment 2
@@ -18,30 +16,51 @@
@@ -18,30 +16,53 @@
# fmt: off
def func_no_args():
@@ -241,7 +241,9 @@ d={'a':1,
+ NOT_YET_IMPLEMENTED_StmtRaise
+ if False:
+ ...
+ NOT_YET_IMPLEMENTED_StmtFor
+ for i in NOT_IMPLEMENTED_call():
+ NOT_IMPLEMENTED_call()
+ continue
+ NOT_IMPLEMENTED_call()
+ return None
+
@@ -251,19 +253,18 @@ d={'a':1,
- async with some_connection() as conn:
- await conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2)
- await asyncio.sleep(1)
-@asyncio.coroutine
+ "NOT_YET_IMPLEMENTED_STRING"
+ NOT_YET_IMPLEMENTED_StmtAsyncWith
+ await NOT_IMPLEMENTED_call()
+
+
@asyncio.coroutine
-@some_decorator(
-with_args=True,
-many_args=[1,2,3]
-)
-def function_signature_stress_test(number:int,no_annotation=None,text:str='default',* ,debug:bool=False,**kwargs) -> str:
- return text[number:-1]
+ "NOT_YET_IMPLEMENTED_STRING"
+ NOT_YET_IMPLEMENTED_StmtAsyncWith
+ await NOT_IMPLEMENTED_call()
+
+
+@asyncio.NOT_IMPLEMENTED_attr
+@NOT_IMPLEMENTED_call()
+def function_signature_stress_test(
+ number: int,
@@ -273,7 +274,7 @@ d={'a':1,
+ debug: bool = False,
+ **kwargs,
+) -> str:
+ return NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+ return text[number : -1]
+
+
# fmt: on
@@ -286,7 +287,7 @@ d={'a':1,
+ c=[],
+ d={},
+ e=True,
+ f=NOT_YET_IMPLEMENTED_ExprUnaryOp,
+ f=-1,
+ g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false,
+ h="NOT_YET_IMPLEMENTED_STRING",
+ i="NOT_YET_IMPLEMENTED_STRING",
@@ -296,15 +297,13 @@ d={'a':1,
def spaces_types(
@@ -50,77 +69,62 @@
c: list = [],
@@ -51,76 +72,71 @@
d: dict = {},
e: bool = True,
- f: int = -1,
f: int = -1,
- g: int = 1 if False else 2,
- h: str = "",
- i: str = r"",
+ f: int = NOT_YET_IMPLEMENTED_ExprUnaryOp,
+ g: int = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false,
+ h: str = "NOT_YET_IMPLEMENTED_STRING",
+ i: str = "NOT_YET_IMPLEMENTED_STRING",
@@ -325,15 +324,22 @@ d={'a':1,
def subscriptlist():
- atom[
- # fmt: off
atom[
# fmt: off
- 'some big and',
- 'complex subscript',
- # fmt: on
- goes + here,
- andhere,
- ]
+ NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+ (
+ "NOT_YET_IMPLEMENTED_STRING",
+ "NOT_YET_IMPLEMENTED_STRING",
+ # fmt: on
+ goes
+ + here,
+ andhere,
+ )
]
def import_as_names():
@@ -392,7 +398,7 @@ d={'a':1,
# fmt: off
# hey, that won't work
@@ -130,13 +134,15 @@
@@ -130,13 +146,13 @@
def on_and_off_broken():
@@ -407,13 +413,11 @@ d={'a':1,
+ this = NOT_IMPLEMENTED_call()
+ and_ = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
+ NOT_IMPLEMENTED_call()
+ (
+ now.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr
+ )
+ now.considers.multiple.fmt.directives.within.one.prefix
# fmt: on
# fmt: off
# ...but comments still get reformatted even though they should not be
@@ -145,80 +151,21 @@
@@ -145,80 +161,21 @@
def long_lines():
if True:
@@ -532,7 +536,9 @@ def func_no_args():
NOT_YET_IMPLEMENTED_StmtRaise
if False:
...
NOT_YET_IMPLEMENTED_StmtFor
for i in NOT_IMPLEMENTED_call():
NOT_IMPLEMENTED_call()
continue
NOT_IMPLEMENTED_call()
return None
@@ -543,7 +549,7 @@ async def coroutine(arg, exec=False):
await NOT_IMPLEMENTED_call()
@asyncio.NOT_IMPLEMENTED_attr
@asyncio.coroutine
@NOT_IMPLEMENTED_call()
def function_signature_stress_test(
number: int,
@@ -553,7 +559,7 @@ def function_signature_stress_test(
debug: bool = False,
**kwargs,
) -> str:
return NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
return text[number : -1]
# fmt: on
@@ -563,7 +569,7 @@ def spaces(
c=[],
d={},
e=True,
f=NOT_YET_IMPLEMENTED_ExprUnaryOp,
f=-1,
g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false,
h="NOT_YET_IMPLEMENTED_STRING",
i="NOT_YET_IMPLEMENTED_STRING",
@@ -578,7 +584,7 @@ def spaces_types(
c: list = [],
d: dict = {},
e: bool = True,
f: int = NOT_YET_IMPLEMENTED_ExprUnaryOp,
f: int = -1,
g: int = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false,
h: str = "NOT_YET_IMPLEMENTED_STRING",
i: str = "NOT_YET_IMPLEMENTED_STRING",
@@ -597,7 +603,17 @@ something = {
def subscriptlist():
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
atom[
# fmt: off
(
"NOT_YET_IMPLEMENTED_STRING",
"NOT_YET_IMPLEMENTED_STRING",
# fmt: on
goes
+ here,
andhere,
)
]
def import_as_names():
@@ -649,9 +665,7 @@ def on_and_off_broken():
this = NOT_IMPLEMENTED_call()
and_ = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
NOT_IMPLEMENTED_call()
(
now.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr.NOT_IMPLEMENTED_attr
)
now.considers.multiple.fmt.directives.within.one.prefix
# fmt: on
# fmt: off
# ...but comments still get reformatted even though they should not be

View File

@@ -22,15 +22,17 @@ else:
```diff
--- Black
+++ Ruff
@@ -1,9 +1,5 @@
@@ -1,9 +1,9 @@
a, b, c = 3, 4, 5
-if (
if (
- a == 3
- and b != 9 # fmt: skip
- and c is not None
-):
+ NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
+ and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right # fmt: skip
+ and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
):
- print("I'm good!")
+if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
+ NOT_IMPLEMENTED_call()
else:
- print("I'm bad")
@@ -41,7 +43,11 @@ else:
```py
a, b, c = 3, 4, 5
if NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2:
if (
NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right # fmt: skip
and NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
):
NOT_IMPLEMENTED_call()
else:
NOT_IMPLEMENTED_call()

View File

@@ -75,7 +75,7 @@ async def test_async_with():
```diff
--- Black
+++ Ruff
@@ -1,62 +1,46 @@
@@ -1,62 +1,47 @@
# Make sure a leading comment is not removed.
-def some_func( unformatted, args ): # fmt: skip
- print("I am some_func")
@@ -99,13 +99,13 @@ async def test_async_with():
- def some_method( self, unformatted, args ): # fmt: skip
- print("I am some_method")
- return 0
-
+NOT_YET_IMPLEMENTED_StmtClassDef
- async def some_async_method( self, unformatted, args ): # fmt: skip
- print("I am some_async_method")
- await asyncio.sleep(1)
+NOT_YET_IMPLEMENTED_StmtClassDef
-
# Make sure a leading comment is not removed.
-if unformatted_call( args ): # fmt: skip
- print("First branch")
@@ -130,7 +130,8 @@ async def test_async_with():
-for i in some_iter( unformatted, args ): # fmt: skip
- print("Do something")
+NOT_YET_IMPLEMENTED_StmtFor # fmt: skip
+for i in NOT_IMPLEMENTED_call(): # fmt: skip
+ NOT_IMPLEMENTED_call()
async def test_async_for():
@@ -193,7 +194,8 @@ while NOT_IMPLEMENTED_call(): # fmt: skip
NOT_IMPLEMENTED_call()
NOT_YET_IMPLEMENTED_StmtFor # fmt: skip
for i in NOT_IMPLEMENTED_call(): # fmt: skip
NOT_IMPLEMENTED_call()
async def test_async_for():

View File

@@ -116,17 +116,17 @@ def __await__(): return (yield)
+NOT_YET_IMPLEMENTED_StmtImport
-from third_party import X, Y, Z
-
-from library import some_connection, some_decorator
+NOT_YET_IMPLEMENTED_StmtImportFrom
-from library import some_connection, some_decorator
-
-f"trigger 3.6 mode"
+NOT_YET_IMPLEMENTED_StmtImportFrom
+NOT_YET_IMPLEMENTED_ExprJoinedStr
def func_no_args():
@@ -14,39 +13,46 @@
@@ -14,39 +13,48 @@
b
c
if True:
@@ -136,9 +136,10 @@ def __await__(): return (yield)
...
- for i in range(10):
- print(i)
- continue
+ for i in NOT_IMPLEMENTED_call():
+ NOT_IMPLEMENTED_call()
continue
- exec("new-style exec", {}, {})
+ NOT_YET_IMPLEMENTED_StmtFor
+ NOT_IMPLEMENTED_call()
return None
@@ -153,9 +154,8 @@ def __await__(): return (yield)
+ await NOT_IMPLEMENTED_call()
-@asyncio.coroutine
@asyncio.coroutine
-@some_decorator(with_args=True, many_args=[1, 2, 3])
+@asyncio.NOT_IMPLEMENTED_attr
+@NOT_IMPLEMENTED_call()
def function_signature_stress_test(
number: int,
@@ -167,7 +167,7 @@ def __await__(): return (yield)
**kwargs,
) -> str:
- return text[number:-1]
+ return NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+ return text[number : -1]
-def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""):
@@ -179,7 +179,7 @@ def __await__(): return (yield)
+ c=[],
+ d={},
+ e=True,
+ f=NOT_YET_IMPLEMENTED_ExprUnaryOp,
+ f=-1,
+ g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false,
+ h="NOT_YET_IMPLEMENTED_STRING",
+ i="NOT_YET_IMPLEMENTED_STRING",
@@ -189,15 +189,13 @@ def __await__(): return (yield)
def spaces_types(
@@ -55,71 +61,27 @@
c: list = [],
@@ -56,70 +64,26 @@
d: dict = {},
e: bool = True,
- f: int = -1,
f: int = -1,
- g: int = 1 if False else 2,
- h: str = "",
- i: str = r"",
+ f: int = NOT_YET_IMPLEMENTED_ExprUnaryOp,
+ g: int = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false,
+ h: str = "NOT_YET_IMPLEMENTED_STRING",
+ i: str = "NOT_YET_IMPLEMENTED_STRING",
@@ -271,7 +269,7 @@ def __await__(): return (yield)
def trailing_comma():
@@ -135,14 +97,8 @@
@@ -135,14 +99,8 @@
a,
**kwargs,
) -> A:
@@ -311,7 +309,9 @@ def func_no_args():
NOT_YET_IMPLEMENTED_StmtRaise
if False:
...
NOT_YET_IMPLEMENTED_StmtFor
for i in NOT_IMPLEMENTED_call():
NOT_IMPLEMENTED_call()
continue
NOT_IMPLEMENTED_call()
return None
@@ -322,7 +322,7 @@ async def coroutine(arg, exec=False):
await NOT_IMPLEMENTED_call()
@asyncio.NOT_IMPLEMENTED_attr
@asyncio.coroutine
@NOT_IMPLEMENTED_call()
def function_signature_stress_test(
number: int,
@@ -332,7 +332,7 @@ def function_signature_stress_test(
debug: bool = False,
**kwargs,
) -> str:
return NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
return text[number : -1]
def spaces(
@@ -341,7 +341,7 @@ def spaces(
c=[],
d={},
e=True,
f=NOT_YET_IMPLEMENTED_ExprUnaryOp,
f=-1,
g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false,
h="NOT_YET_IMPLEMENTED_STRING",
i="NOT_YET_IMPLEMENTED_STRING",
@@ -356,7 +356,7 @@ def spaces_types(
c: list = [],
d: dict = {},
e: bool = True,
f: int = NOT_YET_IMPLEMENTED_ExprUnaryOp,
f: int = -1,
g: int = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false,
h: str = "NOT_YET_IMPLEMENTED_STRING",
i: str = "NOT_YET_IMPLEMENTED_STRING",

View File

@@ -94,7 +94,7 @@ some_module.some_function(
}
tup = (
1,
@@ -24,45 +24,18 @@
@@ -24,45 +24,23 @@
def f(
a: int = 1,
):
@@ -106,7 +106,9 @@ some_module.some_function(
- call2(
- arg=[1, 2, 3],
- )
- x = {
+ NOT_IMPLEMENTED_call()
+ NOT_IMPLEMENTED_call()
x = {
- "a": 1,
- "b": 2,
- }["a"]
@@ -123,9 +125,11 @@ some_module.some_function(
- "h": 8,
- }["a"]
- ):
+ NOT_IMPLEMENTED_call()
+ NOT_IMPLEMENTED_call()
+ x = NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+ "NOT_YET_IMPLEMENTED_STRING": 1,
+ "NOT_YET_IMPLEMENTED_STRING": 2,
+ }[
+ "NOT_YET_IMPLEMENTED_STRING"
+ ]
+ if NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
pass
@@ -133,7 +137,7 @@ some_module.some_function(
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
- Set["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]
-):
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]:
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set["NOT_YET_IMPLEMENTED_STRING"]:
json = {
- "k": {
- "k2": {
@@ -148,7 +152,7 @@ some_module.some_function(
}
@@ -80,35 +53,16 @@
@@ -80,35 +58,16 @@
pass
@@ -221,12 +225,17 @@ def f(
):
NOT_IMPLEMENTED_call()
NOT_IMPLEMENTED_call()
x = NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
x = {
"NOT_YET_IMPLEMENTED_STRING": 1,
"NOT_YET_IMPLEMENTED_STRING": 2,
}[
"NOT_YET_IMPLEMENTED_STRING"
]
if NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right:
pass
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]:
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set["NOT_YET_IMPLEMENTED_STRING"]:
json = {
"NOT_YET_IMPLEMENTED_STRING": {
"NOT_YET_IMPLEMENTED_STRING": {"NOT_YET_IMPLEMENTED_STRING": [1]},

View File

@@ -62,7 +62,7 @@ __all__ = (
```diff
--- Black
+++ Ruff
@@ -1,64 +1,42 @@
@@ -1,54 +1,32 @@
-"""The asyncio package, tracking PEP 3156."""
+"NOT_YET_IMPLEMENTED_STRING"
@@ -136,27 +136,7 @@ __all__ = (
+NOT_YET_IMPLEMENTED_StmtImportFrom
__all__ = (
- base_events.__all__
- + coroutines.__all__
- + events.__all__
- + futures.__all__
- + locks.__all__
- + protocols.__all__
- + runners.__all__
- + queues.__all__
- + streams.__all__
- + tasks.__all__
+ base_events.NOT_IMPLEMENTED_attr
+ + coroutines.NOT_IMPLEMENTED_attr
+ + events.NOT_IMPLEMENTED_attr
+ + futures.NOT_IMPLEMENTED_attr
+ + locks.NOT_IMPLEMENTED_attr
+ + protocols.NOT_IMPLEMENTED_attr
+ + runners.NOT_IMPLEMENTED_attr
+ + queues.NOT_IMPLEMENTED_attr
+ + streams.NOT_IMPLEMENTED_attr
+ + tasks.NOT_IMPLEMENTED_attr
)
base_events.__all__
```
## Ruff Output
@@ -193,16 +173,16 @@ NOT_YET_IMPLEMENTED_StmtImportFrom
NOT_YET_IMPLEMENTED_StmtImportFrom
__all__ = (
base_events.NOT_IMPLEMENTED_attr
+ coroutines.NOT_IMPLEMENTED_attr
+ events.NOT_IMPLEMENTED_attr
+ futures.NOT_IMPLEMENTED_attr
+ locks.NOT_IMPLEMENTED_attr
+ protocols.NOT_IMPLEMENTED_attr
+ runners.NOT_IMPLEMENTED_attr
+ queues.NOT_IMPLEMENTED_attr
+ streams.NOT_IMPLEMENTED_attr
+ tasks.NOT_IMPLEMENTED_attr
base_events.__all__
+ coroutines.__all__
+ events.__all__
+ futures.__all__
+ locks.__all__
+ protocols.__all__
+ runners.__all__
+ queues.__all__
+ streams.__all__
+ tasks.__all__
)
```

View File

@@ -25,25 +25,28 @@ list_of_types = [tuple[int,],]
```diff
--- Black
+++ Ruff
@@ -1,22 +1,12 @@
@@ -1,22 +1,17 @@
# We should not treat the trailing comma
# in a single-element subscript.
-a: tuple[int,]
-b = tuple[int,]
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+b = NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+b = tuple[(int,)]
# The magic comma still applies to multi-element subscripts.
-c: tuple[
- int,
- int,
-]
-d = tuple[
- int,
- int,
-]
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+d = NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
d = tuple[
- int,
- int,
+ (
+ int,
+ int,
+ )
]
# Magic commas still work as expected for non-subscripts.
-small_list = [
@@ -53,7 +56,7 @@ list_of_types = [tuple[int,],]
- tuple[int,],
-]
+small_list = [1]
+list_of_types = [NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]]
+list_of_types = [tuple[(int,)]]
```
## Ruff Output
@@ -62,15 +65,20 @@ list_of_types = [tuple[int,],]
# We should not treat the trailing comma
# in a single-element subscript.
NOT_YET_IMPLEMENTED_StmtAnnAssign
b = NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
b = tuple[(int,)]
# The magic comma still applies to multi-element subscripts.
NOT_YET_IMPLEMENTED_StmtAnnAssign
d = NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
d = tuple[
(
int,
int,
)
]
# Magic commas still work as expected for non-subscripts.
small_list = [1]
list_of_types = [NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]]
list_of_types = [tuple[(int,)]]
```
## Black Output

View File

@@ -76,32 +76,28 @@ return np.divide(
```diff
--- Black
+++ Ruff
@@ -11,53 +11,46 @@
{**a, **b, **c}
@@ -12,52 +12,45 @@
-a = 5**~4
a = 5**~4
-b = 5 ** f()
-c = -(5**2)
+b = 5 ** NOT_IMPLEMENTED_call()
c = -(5**2)
-d = 5 ** f["hi"]
-e = lazy(lambda **kwargs: 5)
-f = f() ** 5
-g = a.b**c.d
+d = 5 ** f["NOT_YET_IMPLEMENTED_STRING"]
+e = NOT_IMPLEMENTED_call()
+f = NOT_IMPLEMENTED_call() ** 5
g = a.b**c.d
-h = 5 ** funcs.f()
-i = funcs.f() ** 5
-j = super().name ** 5
-k = [(2**idx, value) for idx, value in pairs]
-l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001)
+a = 5**NOT_YET_IMPLEMENTED_ExprUnaryOp
+b = 5 ** NOT_IMPLEMENTED_call()
+c = NOT_YET_IMPLEMENTED_ExprUnaryOp
+d = 5 ** NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+e = NOT_IMPLEMENTED_call()
+f = NOT_IMPLEMENTED_call() ** 5
+g = a.NOT_IMPLEMENTED_attr**c.NOT_IMPLEMENTED_attr
+h = 5 ** NOT_IMPLEMENTED_call()
+i = NOT_IMPLEMENTED_call() ** 5
+j = NOT_IMPLEMENTED_call().NOT_IMPLEMENTED_attr ** 5
+j = NOT_IMPLEMENTED_call().name ** 5
+k = [i for i in []]
+l = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
m = [([2**63], [1, 2**63])]
@@ -115,28 +111,25 @@ return np.divide(
+q = [i for i in []]
r = x**y
-a = 5.0**~4.0
a = 5.0**~4.0
-b = 5.0 ** f()
-c = -(5.0**2.0)
+b = 5.0 ** NOT_IMPLEMENTED_call()
c = -(5.0**2.0)
-d = 5.0 ** f["hi"]
-e = lazy(lambda **kwargs: 5)
-f = f() ** 5.0
-g = a.b**c.d
+d = 5.0 ** f["NOT_YET_IMPLEMENTED_STRING"]
+e = NOT_IMPLEMENTED_call()
+f = NOT_IMPLEMENTED_call() ** 5.0
g = a.b**c.d
-h = 5.0 ** funcs.f()
-i = funcs.f() ** 5.0
-j = super().name ** 5.0
-k = [(2.0**idx, value) for idx, value in pairs]
-l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001)
+a = 5.0**NOT_YET_IMPLEMENTED_ExprUnaryOp
+b = 5.0 ** NOT_IMPLEMENTED_call()
+c = NOT_YET_IMPLEMENTED_ExprUnaryOp
+d = 5.0 ** NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
+e = NOT_IMPLEMENTED_call()
+f = NOT_IMPLEMENTED_call() ** 5.0
+g = a.NOT_IMPLEMENTED_attr**c.NOT_IMPLEMENTED_attr
+h = 5.0 ** NOT_IMPLEMENTED_call()
+i = NOT_IMPLEMENTED_call() ** 5.0
+j = NOT_IMPLEMENTED_call().NOT_IMPLEMENTED_attr ** 5.0
+j = NOT_IMPLEMENTED_call().name ** 5.0
+k = [i for i in []]
+l = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
m = [([2.0**63.0], [1.0, 2**63.0])]
@@ -183,16 +176,16 @@ def function_dont_replace_spaces():
{**a, **b, **c}
a = 5**NOT_YET_IMPLEMENTED_ExprUnaryOp
a = 5**~4
b = 5 ** NOT_IMPLEMENTED_call()
c = NOT_YET_IMPLEMENTED_ExprUnaryOp
d = 5 ** NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
c = -(5**2)
d = 5 ** f["NOT_YET_IMPLEMENTED_STRING"]
e = NOT_IMPLEMENTED_call()
f = NOT_IMPLEMENTED_call() ** 5
g = a.NOT_IMPLEMENTED_attr**c.NOT_IMPLEMENTED_attr
g = a.b**c.d
h = 5 ** NOT_IMPLEMENTED_call()
i = NOT_IMPLEMENTED_call() ** 5
j = NOT_IMPLEMENTED_call().NOT_IMPLEMENTED_attr ** 5
j = NOT_IMPLEMENTED_call().name ** 5
k = [i for i in []]
l = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
m = [([2**63], [1, 2**63])]
@@ -202,16 +195,16 @@ p = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value for key, value in NOT_
q = [i for i in []]
r = x**y
a = 5.0**NOT_YET_IMPLEMENTED_ExprUnaryOp
a = 5.0**~4.0
b = 5.0 ** NOT_IMPLEMENTED_call()
c = NOT_YET_IMPLEMENTED_ExprUnaryOp
d = 5.0 ** NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]
c = -(5.0**2.0)
d = 5.0 ** f["NOT_YET_IMPLEMENTED_STRING"]
e = NOT_IMPLEMENTED_call()
f = NOT_IMPLEMENTED_call() ** 5.0
g = a.NOT_IMPLEMENTED_attr**c.NOT_IMPLEMENTED_attr
g = a.b**c.d
h = 5.0 ** NOT_IMPLEMENTED_call()
i = NOT_IMPLEMENTED_call() ** 5.0
j = NOT_IMPLEMENTED_call().NOT_IMPLEMENTED_attr ** 5.0
j = NOT_IMPLEMENTED_call().name ** 5.0
k = [i for i in []]
l = NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right
m = [([2.0**63.0], [1.0, 2**63.0])]

View File

@@ -40,7 +40,7 @@ xxxxxxxxx_yyy_zzzzzzzz[xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxx
-xxxxxxxxx_yyy_zzzzzzzz[
- xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxxx=1)
-] = 1
+NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key] = 1
+xxxxxxxxx_yyy_zzzzzzzz[NOT_IMPLEMENTED_call(), NOT_IMPLEMENTED_call()] = 1
```
## Ruff Output
@@ -61,7 +61,7 @@ xxxxxxxxx_yyy_zzzzzzzz[xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxx
# Make when when the left side of assignment plus the opening paren "... = (" is
# exactly line length limit + 1, it won't be split like that.
NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key] = 1
xxxxxxxxx_yyy_zzzzzzzz[NOT_IMPLEMENTED_call(), NOT_IMPLEMENTED_call()] = 1
```
## Black Output

View File

@@ -48,28 +48,31 @@ except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.ov
```diff
--- Black
+++ Ruff
@@ -1,42 +1,9 @@
@@ -1,42 +1,17 @@
# These brackets are redundant, therefore remove.
-try:
- a.something
-except AttributeError as err:
- raise err
-
-# This is tuple of exceptions.
-# Although this could be replaced with just the exception,
-# we do not remove brackets to preserve AST.
+NOT_YET_IMPLEMENTED_StmtTry
# This is tuple of exceptions.
# Although this could be replaced with just the exception,
# we do not remove brackets to preserve AST.
-try:
- a.something
-except (AttributeError,) as err:
- raise err
-
-# This is a tuple of exceptions. Do not remove brackets.
+NOT_YET_IMPLEMENTED_StmtTry
# This is a tuple of exceptions. Do not remove brackets.
-try:
- a.something
-except (AttributeError, ValueError) as err:
- raise err
-
-# Test long variants.
+NOT_YET_IMPLEMENTED_StmtTry
# Test long variants.
-try:
- a.something
-except (
@@ -77,9 +80,6 @@ except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.ov
-) as err:
- raise err
+NOT_YET_IMPLEMENTED_StmtTry
+NOT_YET_IMPLEMENTED_StmtTry
+NOT_YET_IMPLEMENTED_StmtTry
+NOT_YET_IMPLEMENTED_StmtTry
-try:
- a.something
@@ -104,8 +104,16 @@ except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.ov
```py
# These brackets are redundant, therefore remove.
NOT_YET_IMPLEMENTED_StmtTry
# This is tuple of exceptions.
# Although this could be replaced with just the exception,
# we do not remove brackets to preserve AST.
NOT_YET_IMPLEMENTED_StmtTry
# This is a tuple of exceptions. Do not remove brackets.
NOT_YET_IMPLEMENTED_StmtTry
# Test long variants.
NOT_YET_IMPLEMENTED_StmtTry
NOT_YET_IMPLEMENTED_StmtTry

View File

@@ -32,25 +32,28 @@ for (((((k, v))))) in d.items():
```diff
--- Black
+++ Ruff
@@ -1,27 +1,13 @@
@@ -1,27 +1,22 @@
# Only remove tuple brackets after `for`
-for k, v in d.items():
- print(k, v)
+NOT_YET_IMPLEMENTED_StmtFor
+for k, v in NOT_IMPLEMENTED_call():
+ NOT_IMPLEMENTED_call()
# Don't touch tuple brackets after `in`
-for module in (core, _unicodefun):
for module in (core, _unicodefun):
- if hasattr(module, "_verify_python3_env"):
- module._verify_python3_env = lambda: None
+NOT_YET_IMPLEMENTED_StmtFor
+ if NOT_IMPLEMENTED_call():
+ module._verify_python3_env = lambda x: True
# Brackets remain for long for loop lines
-for (
- why_would_anyone_choose_to_name_a_loop_variable_with_a_name_this_long,
- i_dont_know_but_we_should_still_check_the_behaviour_if_they_do,
for (
why_would_anyone_choose_to_name_a_loop_variable_with_a_name_this_long,
i_dont_know_but_we_should_still_check_the_behaviour_if_they_do,
-) in d.items():
- print(k, v)
+NOT_YET_IMPLEMENTED_StmtFor
+) in NOT_IMPLEMENTED_call():
+ NOT_IMPLEMENTED_call()
-for (
- k,
@@ -59,30 +62,41 @@ for (((((k, v))))) in d.items():
- dfkasdjfldsjflkdsjflkdsjfdslkfjldsjfgkjdshgkljjdsfldgkhsdofudsfudsofajdslkfjdslkfjldisfjdffjsdlkfjdlkjjkdflskadjldkfjsalkfjdasj.items()
-):
- print(k, v)
+NOT_YET_IMPLEMENTED_StmtFor
+for k, v in NOT_IMPLEMENTED_call():
+ NOT_IMPLEMENTED_call()
# Test deeply nested brackets
-for k, v in d.items():
- print(k, v)
+NOT_YET_IMPLEMENTED_StmtFor
+for k, v in NOT_IMPLEMENTED_call():
+ NOT_IMPLEMENTED_call()
```
## Ruff Output
```py
# Only remove tuple brackets after `for`
NOT_YET_IMPLEMENTED_StmtFor
for k, v in NOT_IMPLEMENTED_call():
NOT_IMPLEMENTED_call()
# Don't touch tuple brackets after `in`
NOT_YET_IMPLEMENTED_StmtFor
for module in (core, _unicodefun):
if NOT_IMPLEMENTED_call():
module._verify_python3_env = lambda x: True
# Brackets remain for long for loop lines
NOT_YET_IMPLEMENTED_StmtFor
for (
why_would_anyone_choose_to_name_a_loop_variable_with_a_name_this_long,
i_dont_know_but_we_should_still_check_the_behaviour_if_they_do,
) in NOT_IMPLEMENTED_call():
NOT_IMPLEMENTED_call()
NOT_YET_IMPLEMENTED_StmtFor
for k, v in NOT_IMPLEMENTED_call():
NOT_IMPLEMENTED_call()
# Test deeply nested brackets
NOT_YET_IMPLEMENTED_StmtFor
for k, v in NOT_IMPLEMENTED_call():
NOT_IMPLEMENTED_call()
```
## Black Output

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