Compare commits

...

453 Commits

Author SHA1 Message Date
Charlie Marsh
b8baeb3e03 Omit jemalloc on FreeBSD 2023-10-24 00:48:26 -04:00
Zanie Blue
802616aac0 Add compatibility test for ruff-lsp to CI (#8016)
Adds a CI job which runs `ruff-lsp` tests against the current Ruff
build.

Avoids rebuilding Ruff at the cost of running _after_ the cargo tests
have finished. Might be worth the rebuild to get earlier feedback but I
don't expect it to fail often?

xref https://github.com/astral-sh/ruff-lsp/pull/286

## Test plan

Verified use of the development version by inspecting version output in
CI; supported by https://github.com/astral-sh/ruff-lsp/pull/289 and
#8034
2023-10-23 15:44:51 -05:00
Weijie Guo
7100e12cc3 add auto-fix for E225,226,227,228 (#8136)
## Summary

Introduce auto fix for `E225`,`E226`,`E227`,`E228`. This partially
address https://github.com/astral-sh/ruff/issues/8121.

## Test Plan

Already covered.
2023-10-23 19:00:42 +00:00
Weijie Guo
5a95b25aa8 add auto-fix for E252 (#8142)
## Summary

Introduce auto fix for `E252`. This partially address #8121.

## Test Plan

Already covered.
2023-10-23 18:57:58 +00:00
Weijie Guo
833814384a add auto-fix for E275 (#8133)
## Summary

First time contribute to `ruff`, so If there are low-level errors,
please forgive me. 🙇

Introduce auto fix for `E275`, this partially address #8121.

## Test Plan

Already coverd.
2023-10-23 18:57:23 +00:00
Weijie Guo
39e45aa06f add auto-fix for E273,274 (#8144)
## Summary

Introduce auto fix for `E273` and `E274`. This partially address #8120.

## Test Plan

Already covered.
2023-10-23 16:31:08 +00:00
Weijie Guo
92baa3591d add auto-fix for E223,224,242 (#8143)
## Summary

Introduce auto fix for `E223`, `E224`, `E242`. This partially address
#8120.

## Test Plan

Already covered.
2023-10-23 16:21:32 +00:00
Simon Boehm
0e96482085 Fix dead link in README (#8146) 2023-10-23 15:56:28 +00:00
Harutaka Kawamura
b9bff6f5d1 [SIM112] Ignore https_proxy, http_proxy, and no_proxy (#8140)
<!--
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

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

Close #8123 

## Test Plan

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

New test cases

---------

Signed-off-by: harupy <hkawamura0130@gmail.com>
2023-10-23 08:48:36 -05:00
gouzil
2401e91ab9 [docs] fix extend-unsafe-fixes and extend-safe-fixes example error (#8139) 2023-10-23 11:36:19 +00:00
Micha Reiser
6199590072 Avoid loading files for cached format results (#8134) 2023-10-23 12:29:13 +01:00
Micha Reiser
08519e22e4 Warn about incompatible formatter options (#8088) 2023-10-23 11:04:20 +01:00
dependabot[bot]
c704674190 Bump serde_with from 3.3.0 to 3.4.0 (#8130)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 09:05:52 +00:00
dependabot[bot]
2db96067aa Bump strum_macros from 0.25.2 to 0.25.3 (#8129)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 10:04:40 +01:00
dependabot[bot]
8cd09c88d3 Bump codspeed-criterion-compat from 2.2.0 to 2.3.0 (#8128)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 10:04:19 +01:00
dependabot[bot]
ce9bd19885 Bump tracing from 0.1.39 to 0.1.40 (#8127)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 10:04:08 +01:00
dependabot[bot]
764304faf1 Bump thiserror from 1.0.49 to 1.0.50 (#8126)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 10:03:50 +01:00
Micha Reiser
6fc35dd075 Add caching to formatter (#8089) 2023-10-23 09:43:08 +01:00
Harutaka Kawamura
c0710a1dd4 Remove unnecessary mutable variable has_parameters (#8124) 2023-10-23 11:39:33 +05:30
Micha Reiser
2c2ebf952a Rust 1.73 (#8007) 2023-10-23 02:12:25 +00:00
Charlie Marsh
d6a4283003 Fix range of unparenthesized tuple subject in match statement (#8101)
## Summary

This was just a bug in the parser ranges, probably since it was
initially implemented. Given `match n % 3, n % 5: ...`, the "subject"
(i.e., the tuple of two binary operators) was using the entire range of
the `match` statement.

Closes https://github.com/astral-sh/ruff/issues/8091.

## Test Plan

`cargo test`
2023-10-22 19:58:33 -04:00
Charlie Marsh
95702e408f Respect parenthesized generators in has_own_parentheses (#8100)
## Summary

When analyzing:

```python
if "root" not in (
    long_tree_name_tree.split("/")[0]
    for long_tree_name_tree in really_really_long_variable_name
):
    msg = "Could not find root. Please try a different forest."
    raise ValueError(msg)
```

We missed that the generator expression is parenthesized, because the
parentheses are _part_ of the generator -- so
`is_expression_parenthesized` returns `False`. We needed to take into
account that generators and tuples may or may not be parenthesized when
determining whether we can omit parentheses while splitting an
expression.

Closes https://github.com/astral-sh/ruff/issues/8090.

## Test Plan

No changes in similarity.

Before:

| project | similarity index | total files | changed files |

|----------------|------------------:|------------------:|------------------:|
| cpython | 0.75803 | 1799 | 1647 |
| django | 0.99983 | 2772 | 34 |
| home-assistant | 0.99953 | 10596 | 186 |
| poetry | 0.99891 | 317 | 17 |
| transformers | 0.99966 | 2657 | 330 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99978 | 3669 | 20 |
| warehouse | 0.99977 | 654 | 13 |
| zulip | 0.99970 | 1459 | 22 |

After:

| project | similarity index | total files | changed files |

|----------------|------------------:|------------------:|------------------:|
| cpython | 0.75803 | 1799 | 1647 |
| django | 0.99983 | 2772 | 34 |
| home-assistant | 0.99953 | 10596 | 186 |
| poetry | 0.99891 | 317 | 17 |
| transformers | 0.99966 | 2657 | 330 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99978 | 3669 | 20 |
| warehouse | 0.99977 | 654 | 13 |
| zulip | 0.99970 | 1459 | 22 |
2023-10-22 19:58:25 -04:00
Harutaka Kawamura
bcaac9693b Refactor get_mark_decorators to return a marker name (#8116) 2023-10-22 10:03:36 -04:00
Harutaka Kawamura
d6f59e4131 Fix typo (pytext -> pytest) (#8117) 2023-10-22 10:02:22 -04:00
Harutaka Kawamura
66e6388776 [flake8-import-conventions] Add links to options (#8115) 2023-10-22 00:14:47 -04:00
Charlie Marsh
8472a7e50f Add fix for E261 (#8114)
Closes https://github.com/astral-sh/ruff/issues/8068.
2023-10-22 00:41:05 +00:00
Charlie Marsh
7586091437 Include backports.strenum in deprecated-imports (#8113)
Closes https://github.com/astral-sh/ruff/issues/8102.
2023-10-21 23:13:03 +00:00
Charlie Marsh
4e07a65c15 Detect sys.version_info slices in outdated-version-block (#8112)
## Summary

Given `sys.version_info[:2] >= (3,0)`, we should treat this equivalently
to `sys.version_info >= (3,0)`.

Closes https://github.com/astral-sh/ruff/issues/8095.
2023-10-21 23:08:17 +00:00
Charlie Marsh
00fd324c6f Improve magic-value-comparison example in docs (#8111)
Closes https://github.com/astral-sh/ruff/issues/8109.
2023-10-21 23:05:43 +00:00
Claudio Jolowicz
2414f23abb Upgrade mutable-argument-defaults to unsafe (#8108) 2023-10-21 15:29:46 -04:00
Charlie Marsh
e0f9dbcd10 Update versions in format benchmark script (#8110) 2023-10-21 18:39:31 +00:00
Andrey
6ddb0fa950 Update hook description in README.md (#8103)
## Summary

- Use the latest hook version for formatting
- Join hooks together, since they are located in the same repo
2023-10-21 10:56:20 -05:00
Charlie Marsh
df807ff912 Allow is and is not for direct type comparisons (#7905)
## Summary

This PR updates our E721 implementation and semantics to match the
updated `pycodestyle` logic, which I think is an improvement.
Specifically, we now allow `type(obj) is int` for exact type
comparisons, which were previously impossible. So now, we're largely
just linting against code like `type(obj) == int`.

This change is gated to preview mode.

Closes https://github.com/astral-sh/ruff/issues/7904.

## Test Plan

Updated the test fixture and ensured parity with latest Flake8.
2023-10-20 23:27:12 +00:00
Charlie Marsh
f6d6200aae Rework the documentation to incorporate the Ruff formatter (#7732)
## Summary

This PR updates our documentation for the upcoming formatter release.

Broadly, the documentation is now structured as follows:

- Overview
- Tutorial
- Installing Ruff
- The Ruff Linter
    - Overview
    - `ruff check`
    - Rule selection
    - Error suppression
    - Exit codes
- The Ruff Formatter
    - Overview
    - `ruff format`
    - Philosophy
    - Configuration
    - Format suppression
    - Exit codes
    - Black compatibility
        - Known deviations
- Configuring Ruff
    - pyproject.toml
    - File discovery
    - Configuration discovery
    - CLI
    - Shell autocompletion
- Preview
- Rules
- Settings
- Integrations
    - `pre-commit`
    - VS Code
    - LSP
    - PyCharm
    - GitHub Actions
- FAQ
- Contributing

The major changes include:

- Removing the "Usage" section from the docs, and instead folding that
information into "Integrations" and the new Linter and Formatter
sections.
- Breaking up "Configuration" into "Configuring Ruff" (for generic
configuration), and new Linter- and Formatter-specific sections.
- Updating all example configurations to use `[tool.ruff.lint]` and
`[tool.ruff.format]`.

My suggestion is to pull and build the docs locally, and review by
reading them in the browser rather than trying to parse all the code
changes.

Closes https://github.com/astral-sh/ruff/issues/7235.

Closes https://github.com/astral-sh/ruff/issues/7647.
2023-10-20 23:08:26 +00:00
Flowrey
fa556d1c74 Make SIM401 catch ternary operations (#7415)
## Summary

Make SIM401 rules to catch ternary operations when preview is enabled.

Fixes #7288.

## Test Plan

Tested against `SIM401.py` fixtures.
2023-10-20 20:47:00 +00:00
Zanie Blue
860ffb9549 Add ruff version with long version display (#8034)
Adds a new `ruff version` sub-command which displays long version
information in the style of `cargo` and `rustc`. We include the number
of commits since the last release tag if its a development build, in the
style of Python's versioneer.

```
❯ ruff version
ruff 0.1.0+14 (947940e91 2023-10-18)
```

```
❯ ruff version --output-format json
{
  "version": "0.1.0",
  "commit_info": {
    "short_commit_hash": "947940e91",
    "commit_hash": "947940e91269f20f6b3f8f8c7c63f8e914680e80",
    "commit_date": "2023-10-18",
    "last_tag": "v0.1.0",
    "commits_since_last_tag": 14
  }
}%
```

```
❯ cargo version
cargo 1.72.1 (103a7ff2e 2023-08-15)
```
## Test plan

I've tested this manually locally, but want to at least add unit tests
for the message formatting. We'd also want to check the next release to
ensure the information is correct.

I checked build behavior with a detached head and branches.

## Future work

We could include rustc and cargo versions from the build, the current
Python version, and other diagnostic information for bug reports.

The `--version` and `-V` output is unchanged. However, we could update
it to display the long ruff version without the rust and cargo versions
(this is what cargo does). We'll need to be careful to ensure this does
not break downstream packages which parse our version string.

```
❯ ruff --version
ruff 0.1.0
```

The LSP should be updated to use `ruff version --output-format json`
instead of parsing `ruff --version`.
2023-10-20 14:07:41 -05:00
Steve C
90ebea86a4 [pylint] - implement non-ascii-name (C2401) (#8038)
## Summary

Adds [`non-ascii-name` /
`C2401`](https://pylint.pycqa.org/en/latest/user_guide/messages/convention/non-ascii-name.html)

See #970

## Test Plan

`cargo test` and manually
2023-10-20 18:53:51 +00:00
Steve C
7a5f98835a [pylint] - Implement non-ascii-module-import (C2403) (#8056)
## Summary

Adds [`non-ascii-module-import` /
`C2403`](https://pylint.pycqa.org/en/latest/user_guide/messages/convention/non-ascii-module-import.html)

See #970

## Test Plan

`cargo test` and manually
2023-10-20 18:06:22 +00:00
Zanie Blue
348b649b5c Update fix for literal-membership (PLR6201) to be unsafe (#8097)
Closes https://github.com/astral-sh/ruff/issues/8096
2023-10-20 12:38:00 -05:00
Charlie Marsh
ae41d6f30a Move remaining lambda rule to deferred pass (#8098) 2023-10-20 17:37:42 +00:00
Clément Schreiner
b1072049bf [pylint] Implement unnecessary-lambda (W0108) (#7953)
This is my first PR and I'm new at rust, so feel free to ask me to
rewrite everything if needed ;)

The rule must be called after deferred lambas have been visited because
of the last check (whether the lambda parameters are used in the body of
the function that's being called). I didn't know where to do it, so I
did what I could to be able to work on the rule itself:

 - added a `ruff_linter::checkers::ast::analyze::lambda` module
 - build a vec of visited lambdas in `visit_deferred_lambdas`
 - call `analyze::lambda` on the vec after they all have been visited
 
Building that vec of visited lambdas was necessary so that bindings
could be properly resolved in the case of nested lambdas.

Note that there is an open issue in pylint for some false positives, do
we need to fix that before merging the rule?
https://github.com/pylint-dev/pylint/issues/8192

Also, I did not provide any fixes (yet), maybe we want do avoid merging
new rules without fixes?

## Summary

Checks for lambdas whose body is a function call on the same arguments
as the lambda itself.

### Bad

```python
df.apply(lambda x: str(x))
```

### Good

```python
df.apply(str)
```

## Test Plan

Added unit test and snapshot.
Manually compared pylint and ruff output on pylint's test cases.

## References

- [pylint
documentation](https://pylint.readthedocs.io/en/latest/user_guide/messages/warning/unnecessary-lambda.html)
- [pylint
implementation](https://github.com/pylint-dev/pylint/blob/main/pylint/checkers/base/basic_checker.py#L521-L587)
 - https://github.com/astral-sh/ruff/issues/970
2023-10-20 17:25:24 +00:00
Valeriy Savchenko
bc49492085 [refurb] Implement read-whole-file [FURB101] (#7682)
## Summary

This PR is part of a bigger effort of re-implementing `refurb` rules
#1348. It adds support for
[FURB101](https://github.com/dosisod/refurb/blob/master/refurb/checks/pathlib/read_text.py)

## Test Plan

I included a new test + checked that all other tests pass.
2023-10-20 16:22:38 +00:00
Philipp A
c8464c3a90 Fix message for too-many-arguments lint (#8092)
## Summary

The lint checks for number of arguments in a function *definition*, but
the message says “function *call*”

## Test Plan

See what breaks and change the tests
2023-10-20 12:17:19 -04:00
Dhruv Manilawala
f158536fbb Fix "Preview features" heading level in CHANGELOG (#8086) 2023-10-20 09:57:24 +05:30
Micha Reiser
1dd264b019 Fix Options JSON schema description (#8081) 2023-10-20 00:25:32 +00:00
Micha Reiser
a525f09008 Add #[automatically_derived] to derived impls (#8080) 2023-10-20 00:12:27 +00:00
Charlie Marsh
256b98ab9a Avoid if-else simplification for TYPE_CHECKING blocks (#8072)
Closes https://github.com/astral-sh/ruff/issues/8071.
2023-10-19 19:15:54 -04:00
Micha Reiser
962472da96 Change line-ending default to auto (#8057) 2023-10-20 00:13:11 +01:00
Charlie Marsh
a00c445580 Avoid false-positive print separator diagnostic with starred argument (#8079)
Given `print(*a_list_with_elements, sep="\n")`, we can't remove the
separator (unlike in `print(a, sep="\n")`), since we don't know how many
arguments were provided.

Closes https://github.com/astral-sh/ruff/issues/8078.
2023-10-19 22:30:13 +00:00
Zanie Blue
0e58433715 Fix changelog links for 0.1.1 (#8077)
[Rendered](https://github.com/astral-sh/ruff/blob/zanie/changelog-links/CHANGELOG.md)
2023-10-19 16:19:17 -05:00
Zanie Blue
22cf451d51 Release 0.1.1 (#8073)
- Add changelog entry for 0.1.1
- Bump version to 0.1.1
- Require preview for fix added in #7967 
- Allow duplicate headings in changelog (markdownlint setting)
2023-10-19 20:49:53 +00:00
Dhruv Manilawala
ec1be60dcb Remove leftover constant tuple reference (#8062)
This PR removes the leftover reference to the tuple variant in
`Constant`.
2023-10-19 17:50:45 +00:00
Zanie Blue
a327b4da87 sequence -> iterable in tutorial (#8067)
Very minor follow to https://github.com/astral-sh/ruff/pull/8066/
2023-10-19 12:25:45 -05:00
Charlie Marsh
cdc5e2fb58 Update tutorial to match revised Ruff defaults (#8066)
## Summary

We don't enable E501 by default, but `line-length` is a useful example
for configuration, so we now set `--extend-select` in the tutorial with
a note to that effect.

I've also updated all the outputs to match the latest CLI behavior, and
changed the example from `List` to `Sequence` because `List` now spits
out two diagnostics (one for the import, one for the usage), which IMO
is confusing for beginners.
2023-10-19 12:26:59 -04:00
Jacob Coffee
b5d3caf033 chore: add code style badge for ruff format (#7878)
<!--
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

- Adds a badge for code style - ruff, in the same vein of [code style -
black](https://img.shields.io/badge/code%20style-black-black)


[example](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FJacobCoffee%2Fbfb02a83c8da3cbf53f7772f2cee02ec%2Fraw%2Facb94daa3aedecda67e2c7d8c5aec9765db0734d%2Fformat-badge.json)

![badge
example](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FJacobCoffee%2Fbfb02a83c8da3cbf53f7772f2cee02ec%2Fraw%2Facb94daa3aedecda67e2c7d8c5aec9765db0734d%2Fformat-badge.json)

https://gist.github.com/JacobCoffee/bfb02a83c8da3cbf53f7772f2cee02ec
2023-10-19 08:54:02 -05:00
konsti
8f9753f58e Comments outside expression parentheses (#7873)
<!--
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

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

I've removed automatic dangling comment formatting, we're doing manual
dangling comment formatting everywhere anyway (the
assert-all-comments-formatted ensures this) and dangling comments would
break the formatting there.

## Test Plan

New test file.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2023-10-19 09:24:11 +00:00
konsti
67b043482a Use pass over ellipsis in non-function/class contexts (#8049)
Split out of #8044: In preview style, ellipsis are also collapsed in
non-stub files. This should only affect function/class contexts since
for other statements stub are generally not used. I've updated our tests
to use `pass` instead to reflect this, which makes tracking the preview
style changes much easier.
2023-10-19 11:11:17 +02:00
Steve C
693f957b90 [pylint] - implement global-at-module-level (W0604) (#8058)
## Summary

Implements
[`global-at-module-level`/`W0604`](https://pylint.pycqa.org/en/latest/user_guide/messages/warning/global-at-module-level.html)

See #970

## Test Plan

`cargo test` and manually
2023-10-19 04:48:27 +00:00
Micha Reiser
a85ed309ea Respect #(deprecated) attribute in configuration options (#8035) 2023-10-19 01:07:36 +00:00
Charlie Marsh
2e225d7538 Accept --target-version in the format CLI (#8055)
## Summary

This doesn't affect behavior _yet_ (see:
https://github.com/astral-sh/ruff/issues/7234), but it will be needed in
the future, and it's surprising to users that it doesn't exist.

Closes https://github.com/astral-sh/ruff/issues/8051.
2023-10-18 20:14:20 -04:00
Micha Reiser
4786abac7a Respect tab-size setting in formatter (#8006) 2023-10-19 00:48:14 +01:00
Micha Reiser
46d5db56cc Document lint.preview and format.preview (#8032)
Co-authored-by: Zanie Blue <contact@zanie.dev>
2023-10-18 23:30:30 +00:00
Charlie Marsh
2729c4cacd Skip over parentheses when detecting in keyword (#8054)
## Summary

Given an expression like `[x for (x) in y]`, we weren't skipping over
parentheses when searching for the `in` between `(x)` and `y`.

Closes https://github.com/astral-sh/ruff/issues/8053.
2023-10-18 19:13:58 -04:00
Tony Lykke
b2d1fcf7b2 add instructions on line-level suppression to file-level suppression warning (#8052)
## Summary

In #6157 a warning was introduced when users use `ruff: noqa`
suppression in-line instead of at the file-level. I had this trigger
today after forgetting about it, and the warning is an excellent
improvement.

I knew immediately what the issue was because I raised it previously,
but on reading the warning I'm not sure it would be so obvious to all
users. This PR extends the error with a short sentence explaining that
line-level suppression should omit the `ruff:` prefix.

## Test Plan

Not sure it's necessary for such a trivial change :)
2023-10-18 18:46:59 -04:00
Charlie Marsh
78d172aad7 Remove Python 2-only methods from URLOpen audit (#8047)
These were removed from Bandit on `main` as they don't exist in Python
3.
2023-10-18 14:49:54 +00:00
Charlie Marsh
13d6c8237a Avoid flagging HTTP and HTTPS literals in urllib-open (#8046)
Closes https://github.com/astral-sh/ruff/issues/8040.
2023-10-18 14:36:06 +00:00
konsti
51aa73f405 Add --diff option ruff format (#7937)
**Summary** `ruff format --diff` is similar to `ruff format --check`,
but we don't only error with the list of file that would be formatted,
but also show a diff between the unformatted input and the formatted
output.

```console
$ ruff format --diff scratch.py scratch.pyi scratch.ipynb
warning: `ruff format` is not yet stable, and subject to change in future versions.
--- scratch.ipynb
+++ scratch.ipynb
@@ -1,3 +1,4 @@
 import numpy
-maths = (numpy.arange(100)**2).sum()
-stats= numpy.asarray([1,2,3,4]).median()
+
+maths = (numpy.arange(100) ** 2).sum()
+stats = numpy.asarray([1, 2, 3, 4]).median()
--- scratch.py
+++ scratch.py
@@ -1,3 +1,3 @@
 x = 1
-y=2
+y = 2
 z = 3
2 files would be reformatted, 1 file left unchanged
```

With `--diff`, the summary message gets printed to stderr to allow e.g.
`ruff format --diff . > format.patch`.

At the moment, jupyter notebooks are formatted as code diffs, while
everything else is a real diff that could be applied. This means that
the diffs containing jupyter notebooks are not real diffs and can't be
applied. We could change this to json diffs, but they are hard to read.
We could also split the diff option into a human diff option, where we
deviate from the machine readable diff constraints, and a proper machine
readable, appliable diff output that you can pipe into other tools.

To make the tests work, the results (and errors, if any) are sorted
before printing them. Previously, the print order was random, i.e. two
identical runs could have different output.

Open question: Should this go into the markdown docs? Or will this be
subsumed by the integration of the formatter into `ruff check`?

**Test plan** Fixtures for the change and no change cases, including a
jupyter notebook and for file input and stdin.

Fixes #7231

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2023-10-18 11:55:05 +00:00
konsti
0c3123e07e Insert newline after nested function or class statements (#7946)
**Summary** Insert a newline after nested function and class
definitions, unless there is a trailing own line comment.

We need to e.g. format
```python
if platform.system() == "Linux":
    if sys.version > (3, 10):
        def f():
            print("old")
    else:
        def f():
            print("new")
    f()
```
as
```python
if platform.system() == "Linux":
    if sys.version > (3, 10):

        def f():
            print("old")

    else:

        def f():
            print("new")

    f()
```
even though `f()` is directly preceded by an if statement, not a
function or class definition. See the comments and fixtures for trailing
own line comment handling.

**Test Plan** I checked that the new content of `newlines.py` matches
black's formatting.

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2023-10-18 09:45:58 +00:00
Steve C
dda4ceda71 add autofix for D301 (#7970)
## Summary

Add fix for `D301`

## Test Plan

`cargo test` and manually
2023-10-18 02:19:29 +00:00
Charlie Marsh
195c000f5a Avoid failed assertion when showing fixes from stdin (#8029)
## Summary

When linting, we store a map from file path to fixes, which we then use
to show a fix summary in the printer.

In the printer, we assume that if the map is non-empty, then we have at
least one fix. But this isn't enforced by the fix struct, since you can
have an entry from (file path) to (empty fix table). In practice, this
only bites us when linting from `stdin`, since when linting across
multiple files, we have an `AddAssign` on `Diagnostics` that avoids
adding empty entries to the map. When linting from `stdin`, we create
the map directly, and so it _is_ possible to have a non-empty map that
doesn't contain any fixes, leading to a panic.

This PR introduces a dedicated struct to make these constraints part of
the formal interface.

Closes https://github.com/astral-sh/ruff/issues/8027.

## Test Plan

`cargo test` (notice two failures are removed)
2023-10-17 21:50:39 -04:00
Charlie Marsh
a62c735f9e Lazily evaluate all PEP 695 type alias values (#8033)
<!--
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

In https://github.com/astral-sh/ruff/pull/7968, I introduced a
regression whereby we started to treat imports used _only_ in type
annotation bounds (with `__future__` annotations) as unused.

The root of the issue is that I started using `visit_annotation` for
these bounds. So we'd queue up the bound in the list of deferred type
parameters, then when visiting, we'd further queue it up in the list of
deferred type annotations... Which we'd then never visit, since deferred
type annotations are visited _before_ deferred type parameters.

Anyway, the better solution here is to use a dedicated flag for these,
since they have slightly different behavior than type annotations.

I've also fixed what I _think_ is a bug whereby we previously failed to
resolve `Callable` in:

```python
type RecordCallback[R: Record] = Callable[[R], None]

from collections.abc import Callable
```

IIUC, the values in type aliases should be evaluated lazily, like type
parameters.

Closes https://github.com/astral-sh/ruff/issues/8017.

## Test Plan

`cargo test`
2023-10-17 21:50:26 -04:00
Micha Reiser
94b4bb0f57 Add lint.preview (#8002) 2023-10-18 01:26:37 +00:00
Micha Reiser
fe485d791c Add [format|lint].exclude options (#8000) 2023-10-18 01:15:25 +00:00
Charlie Marsh
d685107638 Move {AnyNodeRef, AstNode} to ruff_python_ast crate root (#8030)
This is a do-over of https://github.com/astral-sh/ruff/pull/8011, which
I accidentally merged into a non-`main` branch. Sorry!
2023-10-18 00:01:18 +00:00
Ahmed Ashraf
d85950ce5a Update rule B005 docs (#8028)
## Summary

Rule B005 of flake8-bugbear docs has a typo in one of the examples that
leads to a confusion in the correctness of `.strip()` method


![image](https://github.com/astral-sh/ruff/assets/104530599/b4e19751-558e-4ebb-b82f-25c321ddc32b)

```python
# Wrong output (used in docs) 
"text.txt".strip(".txt")  # "ex" 

# Correct output
"text.txt".strip(".txt")  # "e"
```
2023-10-17 18:32:39 -04:00
Eiko Wagenknecht
88c0106421 [docs] fix typo (#8013)
## Summary

Fix a typo in the docs for quote style.

> a = "a string without any quotes"
> b = "It's monday morning"
> Ruff will change a to use single quotes when using quote-style =
"single". However, a will be unchanged, as converting to single quotes
would require the inner ' to be escaped, which leads to less readable
code: 'It\'s monday morning'.

It should read "However, **b** will be unchanged".

## Test Plan

N/A.
2023-10-17 14:16:28 +00:00
Zanie Blue
f60aa85471 Update GitHub actions example in docs to use --output-format (#8014) 2023-10-17 09:13:24 -05:00
Charlie Marsh
d942a777d7 Avoid flagging bad-dunder-method-name for _ (#8015)
This is almost certainly _not_ an accidentally mistyped dunder method.
Closes https://github.com/astral-sh/ruff/issues/8005.
2023-10-17 10:13:04 -04:00
Steve C
8a529925b3 Add autofix for D300 (#7967)
## Summary

Add fix for `D300`

## Test Plan

`cargo test` and manually
2023-10-17 09:37:46 -04:00
dependabot[bot]
dc6b4ad2b4 Bump tracing from 0.1.37 to 0.1.39 (#7978)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2023-10-17 07:46:53 +00:00
Steve C
21ea290d6a [pylint] Implement PLR0916 (too-many-boolean-expressions) (#7975)
## Summary

Add
[R0916](https://pylint.readthedocs.io/en/latest/user_guide/messages/refactor/too-many-boolean-expressions.html),
no autofix available.

See: #970 

## Test Plan

`cargo test` and manually.
2023-10-17 04:44:25 +00:00
Steve C
5da0f9111e implement PLR6201 with autofix (#7973)
## Summary

Implements
[R6201](https://pylint.readthedocs.io/en/latest/user_guide/messages/refactor/use-set-for-membership.html)
along with the autofix!

See #970 

## Test Plan

`cargo test`, and manually
2023-10-17 04:15:21 +00:00
Charlie Marsh
cb6d74c27b Use set bracket replacement for iteration-over-set (#8001) 2023-10-17 04:12:05 +00:00
Sven Hager
73049df3ed Implement the pylint rule PLW1514 (unspecified-encoding) (#7939)
## Summary

Implemented the pylint rule W1514 ( unspecified-encoding).
See also:
https://pylint.readthedocs.io/en/latest/user_guide/messages/warning/unspecified-encoding.html


## Test Plan

Tested it with the submitted test case.
Additionally, we tested the new ruff rule (PLW1514) on our proprietary
Python code base.
2023-10-17 03:49:48 +00:00
Clément Schreiner
bf0e5788ef [pylint] Implement misplaced-bare-raise (E0704) (#7961)
## Summary

### What it does
This rule triggers an error when a bare raise statement is not in an
except or finally block.
### Why is this bad?
If raise statement is not in an except or finally block, there is no
active exception to
re-raise, so it will fail with a `RuntimeError` exception.
### Example
```python
def validate_positive(x):
   if x <= 0:
       raise
```
Use instead:
```python
def validate_positive(x):
   if x <= 0:
       raise ValueError(f"{x} is not positive")
```

## Test Plan

Added unit test and snapshot.
Manually compared ruff and pylint outputs on pylint's tests.

## References

- [pylint
documentation](https://pylint.pycqa.org/en/stable/user_guide/messages/error/misplaced-bare-raise.html)
- [pylint
implementation](https://github.com/pylint-dev/pylint/blob/main/pylint/checkers/exceptions.py#L339)
2023-10-17 03:07:46 +00:00
Zanie Blue
4113d65836 Rename RuleGroup::Unspecified to Stable (#7991)
Should help with #7989 and seems more accurate for our new model
2023-10-16 14:53:27 -05:00
Clément Schreiner
4c2c9bf7e0 [docs] Clarify that new rules should be added to RuleGroup::Preview. (#7989)
In the contributing page, clarify that new rules must be added to
`RuleGroup::Preview` when mapping their code.
2023-10-16 15:14:09 -04:00
Zanie Blue
172ac2c9a2 Add entry for #7987 to 0.1.0 changelog (#7988) 2023-10-16 18:48:06 +00:00
Charlie Marsh
cac9754455 Update fix safety FAQ to reflect --unsafe-fixes (#7969) 2023-10-16 13:34:55 -05:00
Charlie Marsh
134def0119 Allow sunder names from enum.Enum (#7987)
Closes https://github.com/astral-sh/ruff/issues/7971.
2023-10-16 18:11:14 +00:00
Zanie Blue
1fabaca5de Bump version to 0.1.0 (#7931)
[Rendered
changelog](https://github.com/astral-sh/ruff/blob/release/010/CHANGELOG.md)
2023-10-16 13:06:48 -05:00
Zanie Blue
523f542dbd Remove support for providing output format via format option (#7984)
See the provided breaking changes note for details.

Removes support for the deprecated `--format`option in the `ruff check`
CLI, `format` inference as `output-format` in the configuration file,
and the `RUFF_FORMAT` environment variable.

The error message for use of `format` in the configuration file could be
better, but would require some awkward serde wrappers and it seems hard
to present the correct schema to the user still.
2023-10-16 13:06:12 -05:00
Charlie Marsh
ee7575eb5a Bump regex to 1.10.2 (#7985)
Recreating https://github.com/astral-sh/ruff/pull/7980 with regex's
latest fix.
2023-10-16 13:03:04 -04:00
Charlie Marsh
84f7391cc5 Use Cow in printf rewrite rule (#7986)
Small thing that bothered me when looking into the regex update.
2023-10-16 16:47:03 +00:00
dependabot[bot]
7da4e28a98 Bump aho-corasick from 1.1.1 to 1.1.2 (#7979) 2023-10-16 09:33:22 -04:00
dependabot[bot]
5718df638f Bump cloudflare/wrangler-action from 3.2.0 to 3.3.1 (#7982) 2023-10-16 09:32:15 -04:00
konsti
4bb4cd3b37 Update and extend formatter ecosystem checks (#7981)
**Summary** Adds home-assistant, a project with 10k files, and poetry,
which uses preview style, to the ecosystem checks.

Update all revisions to latest main.

Old:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76047 | 1789 | 1632 |
| django | 0.99983 | 2760 | 36 |
| transformers | 0.99963 | 2587 | 319 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99967 | 648 | 15 |
| zulip | 0.99972 | 1437 | 21 |


New:

| project | similarity index | total files | changed files |

|----------------|------------------:|------------------:|------------------:|
| cpython | 0.76382 | 1799 | 1436 |
| django | 0.99983 | 2772 | 31 |
| home-assistant | 0.99950 | 10596 | 165 |
| poetry | 0.99944 | 317 | 8 |
| transformers | 0.99961 | 2657 | 295 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99974 | 3669 | 19 |
| warehouse | 0.99971 | 654 | 13 |
| zulip | 0.99972 | 1459 | 13 |
2023-10-16 13:24:42 +02:00
konsti
620426de7a Use released unicode_name2 1.2.0 (#7983)
We can remove the git dependency (again). See
https://github.com/progval/unicode_names2/pull/34#issuecomment-1763141541

I'll run the release pipeline before merging.
2023-10-16 11:02:14 +02:00
dependabot[bot]
84ec66a22c Bump semver from 1.0.19 to 1.0.20 (#7977)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-16 08:44:58 +00:00
dependabot[bot]
e58ffa9a7a Bump insta from 1.33.0 to 1.34.0 (#7976)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-16 08:44:38 +00:00
Charlie Marsh
aa6846c78c Add trailing zero between dot and exponential (#7956)
Closes https://github.com/astral-sh/ruff/issues/7952.
2023-10-15 21:42:00 -04:00
Charlie Marsh
3d03e75a9d Force parentheses for power operations in unary expressions (#7955)
## Summary

E.g., given `-10**100`, reformat as `-(10**100)`.

Black special cases this (https://github.com/psf/black/pull/909) and
it's currently a deviation.

Closes https://github.com/astral-sh/ruff/issues/7951.
2023-10-15 21:41:50 -04:00
Charlie Marsh
b6e75e58c9 Treat type aliases as typing-only expressions (#7968)
## Summary

Given `type RecordOrThings = Record | int | str`, the right-hand side
won't be evaluated at runtime. Same goes for `Record` in `type
RecordCallback[R: Record] = Callable[[R], None]`. This PR modifies the
visitation logic to treat them as typing-only.

Closes https://github.com/astral-sh/ruff/issues/7966.
2023-10-16 00:09:37 +00:00
Charlie Marsh
8061894af6 Resolve cache-dir relative to project root (#7962)
## Summary

Unlike other filepath-based settings, the `cache-dir` wasn't being
resolved relative to the project root, when specified as an absolute
path.

Closes https://github.com/astral-sh/ruff/issues/7958.
2023-10-14 19:00:23 +00:00
Victor Hugo Gomes
e261eb7461 Fix false positive in PLR6301 (#7933)
## Summary

Don't report a diagnostic if the method contains a `super()` call.

Closes #6961

## Test Plan

`cargo test`
2023-10-14 14:55:38 -04:00
Charlie Marsh
bd06cbe0c5 Respect subscripted base classes in type-checking rules (#7954)
Closes https://github.com/astral-sh/ruff/issues/7945.
2023-10-13 19:44:16 +00:00
Zanie Blue
ddffadb4b0 When only unsafe fixes are available, include note that no fixes are available first (#7950)
I believe this is a bit clearer.

When no fixes are available (safe _and_ unsafe) we will not include a
message at all.
2023-10-13 12:43:13 -05:00
Zanie Blue
8255e4ed6c Revert "add autofix for PYI030" (#7943)
This reverts commit #7880 (d8c0360fc7)
which does not perform the correct fix per
https://github.com/astral-sh/ruff/pull/7934
2023-10-13 09:24:47 -05:00
konsti
60ca6885b1 Fix i686 host builds (#7935)
See https://github.com/progval/unicode_names2/pull/34 for a detailed
explanation.

Fixes
https://github.com/astral-sh/ruff/actions/runs/6500014395/job/17659540717
and should unblock https://github.com/astral-sh/astral-sh/pull/44
2023-10-13 10:24:58 +02:00
Zanie Blue
889117ea87 Fix handling of Applicability::Display fixes when generating summary messages (#7932)
We were including `Display` fixes in the summary counts for unapplicable
fixes resulting in incorrect prompts to the user that a fix could be
enabled.
2023-10-12 20:33:31 -05:00
Jake Park
c03a693ebc [pylint] Implement consider-using-ternary (R1706) (#7811)
This is my first PR. Please feel free to give me any feedback for even
small drawbacks.

## Summary

Checks if pre-python 2.5 ternary syntax is used.

Before
```python
x, y = 1, 2
maximum = x >= y and x or y  # [consider-using-ternary]
```

After
```python
x, y = 1, 2
maximum = x if x >= y else y
```

References: 

[pylint](https://pylint.pycqa.org/en/latest/user_guide/messages/refactor/consider-using-ternary.html)
#970 
[and_or_ternary distinction
logic](https://github.com/pylint-dev/pylint/blob/main/pylint/checkers/refactoring/refactoring_checker.py#L1813)

## Test Plan

Unit test, python file, snapshot added.
2023-10-13 01:29:19 +00:00
Harutaka Kawamura
6f9c317aa5 Simplify key in dct and dct[key] to dct.get(key) (#7895)
## Summary

Close #5933

## Test Plan

`cargo test`
2023-10-13 01:08:52 +00:00
Dhruv Manilawala
66179af4f1 Add cell field to JSON output format (#7664)
## Summary

This PR adds a new `cell` field to the JSON output format which
indicates the Notebook cell this diagnostic (and fix) belongs to. It
also updates the location for the diagnostic and fixes as per the
`NotebookIndex`. It will be used in the VSCode extension to display the
diagnostic in the correct cell.

The diagnostic and edit start and end source locations are translated
for the notebook as per the `NotebookIndex`. The end source location for
an edit needs some special handling.

### Edit end location

To understand this, the following context is required:

1. Visible lines in Jupyter Notebook vs JSON array strings: The newline
is part of the string in the JSON format. This means that if there are 3
visible lines in a cell where the last line is empty then the JSON would
contain 2 strings in the source array, both ending with a newline:

**JSON format:**
```json
[
	"# first line\n",
	"# second line\n",
]
```

**Notebook view:**
```python
1 # first line
2 # second line
3
```

2. If an edit needs to remove an entire line including the newline, then
the end location would be the start of the next row.

To remove a statement in the following code:
```python
import os
```

The edit would be:
```
start: row 1, col 1
end: row 2, col 1
```

Now, here's where the problem lies. The notebook index doesn't have any
information for row 2 because it doesn't exists in the actual notebook.
The newline was added by Ruff to concatenate the source code and it's
removed before writing back. But, the edit is computed looking at that
newline.

This means that while translating the end location for an edit belong to
a Notebook, we need to check if both the start and end location belongs
to the same cell. If not, then the end location should be the first
character of the next row and if so, translate that back to the last
character of the previous row. Taking the above example, the translated
location for Notebook would be:
```
start: row 1, col 1
end: row 1, col 10
```

## Test Plan

Add test cases for notebook output in the JSON format and update
existing snapshots.
2023-10-13 01:06:02 +00:00
Steve C
1e184e69f3 Add autofix for PYI055 (#7886) 2023-10-13 00:56:34 +00:00
Dhruv Manilawala
f08a5f67eb Add test for Notebook text output (#7925)
## Summary

This PR adds test cases for the Notebook output in text format.

## Test Plan

Update test snapshots.
2023-10-13 06:24:12 +05:30
Dhruv Manilawala
cd564c4200 Use OneIndexed in NotebookIndex (#7921)
## Summary

This PR refactors the `NotebookIndex` struct to use `OneIndexed` to make
the
intent of the code clearer.

## Test Plan

Update the existing test case and run `cargo test` to verify the change.

- [x] Verify `--diff` output
- [x] Verify the diagnostics output
- [x] Verify `--show-source` output
2023-10-13 06:23:49 +05:30
Zanie Blue
c1fdb9c46d Add unsafe fixes entry to breaking changes (#7930) 2023-10-12 17:35:39 -05:00
Zanie Blue
48b256bd94 Add breaking changes entry #7900 (#7928) 2023-10-12 16:01:50 +00:00
konsti
3944c42d4c Format comment before parameter default correctly (#7870)
**Summary** Handle comment before the default values of function
parameters correctly by inserting a line break instead of space after
the equals sign where required.

```python
def f(
    a = # parameter trailing comment; needs line break
    1,
    b =
    # default leading comment; needs line break
    2,
    c = ( # the default leading can only be end-of-line with parentheses; no line break
        3
    ),
    d = (
        # own line leading comment with parentheses; no line break
        4
    )
)
```

Fixes #7603

**Test Plan** Added the different cases and one more complex case as
fixtures.
2023-10-12 17:50:12 +02:00
Zanie Blue
cb06b7956c Add versioning policy to documentation (#7923)
Most of the content adapted from
https://github.com/astral-sh/ruff/discussions/6998
2023-10-12 10:42:35 -05:00
Dhruv Manilawala
4454fbf7e5 Fix E251 false positive inside f-strings (#7894)
## Summary

This PR fixes the bug where the rule `E251` was being triggered on a equal token
inside a f-string which was used in the context of debug expressions.

For example, the following was being flagged before the fix:

```python
print(f"{foo = }")
```

But, now it is not. This leads to false negatives such as:

```python
print(f"{foo(a = 1)}")
```

One solution would be to know if the opened parentheses was inside a f-string or
not. If it was then we can continue flagging until it's closed. If not, then we
should not flag it.

## Test Plan

Add new test cases and check that they don't raise any false positives.

fixes: #7882
2023-10-12 05:26:39 +00:00
Zanie Blue
b243840e4b Add an example of an unsafe fix (#7924)
Per review in #7901 adds an example of an unsafe fix.
2023-10-11 21:14:34 +00:00
Zanie Blue
23bbe7336a Fix false negative in outdated-version-block when using greater than comparisons (#7920)
Closes #7902
2023-10-11 14:33:43 -05:00
Steve C
a71c4dfabb fix edge case with PIE804 (#7922)
## Summary

`foo(**{})` was an overlooked edge case for `PIE804` which introduced a
crash within the Fix, introduced in #7884.

I've made it so that `foo(**{})` turns into `foo()` when applied with
`--fix`, but is that desired/expected? 🤔 Should we just ignore instead?

## Test Plan

`cargo test`
2023-10-11 15:05:49 -04:00
Zanie Blue
81275d12e9 Add documentation for fixes (#7901)
Adds documentation for using `ruff check . --fix`

Uses the draft of the "Automatic fixes" section from
https://github.com/astral-sh/ruff/pull/7732 and adds documentation for
unsafe fixes, applicability levels, and
https://github.com/astral-sh/ruff/pull/7841

I enabled admonitions because they're nice. We should use them more.
2023-10-11 16:41:17 +00:00
Zanie Blue
40cad44f4a Drop formatting specific rules from the default set (#7900)
Closes https://github.com/astral-sh/ruff/issues/7572

Drops formatting specific rules from the default rule set as they
conflict with formatters in general (and in particular, conflict with
our formatter). Most of these rules are in preview, but the removal of
`line-too-long` and `mixed-spaces-and-tabs` is a change to the stable
rule set.

## Example

The following no longer raises `E501`
```
echo "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = 1" | ruff check -
```
2023-10-11 11:29:34 -05:00
Charlie Marsh
c38617fa27 Remove per-diagnostic check for fixability (#7919)
## Summary

Throughout the codebase, we have this pattern:

```rust
let mut diagnostic = ...
if checker.patch(Rule::UnusedVariable) {
    // Do the fix.
}
diagnostics.push(diagnostic)
```

This was helpful when we computed fixes lazily; however, we now compute
fixes eagerly, and this is _only_ used to ensure that we don't generate
fixes for rules marked as unfixable.

We often forget to add this, and it leads to bugs in enforcing
`--unfixable`.

This PR instead removes all of these checks, moving the responsibility
of enforcing `--unfixable` up to `check_path`. This is similar to how
@zanieb handled the `--extend-unsafe` logic: we post-process the
diagnostics to remove any fixes that should be ignored.
2023-10-11 16:09:47 +00:00
Steve C
1835d7bb45 add autofix for PLR1714 (#7910)
## Summary

Add autofix for `PLR1714` using tuples.

If added complexity is desired, we can lean into the `set` part by doing
some kind of builtin check on all of the comparator elements for
starters, since we otherwise don't know if something's hashable.

## Test Plan

`cargo test`, and manually.
2023-10-11 14:42:21 +00:00
Harutaka Kawamura
f670f9b22c [SIM115] Allow open followed by close (#7916) 2023-10-11 13:53:48 +00:00
Charlie Marsh
7a072cc2ea Respect --unfixable in ISC rules (#7917)
Closes https://github.com/astral-sh/ruff/issues/7909.
2023-10-11 13:40:24 +00:00
Harutaka Kawamura
8c4b5d3c90 Visit pattern match guard as a boolean test (#7911) 2023-10-11 09:26:10 -04:00
konsti
ec9d5cddd6 Less scary ruff format message (#7867) 2023-10-11 11:46:41 +00:00
konsti
0f759af3cf Remove spaces from import statements (#7859)
**Summary** Remove spaces from import statements such as 

```python
import tqdm .  tqdm
from tqdm .    auto import tqdm
```

See also #7760 for a better solution.

**Test Plan** New fixtures
2023-10-11 11:35:41 +00:00
konsti
644011fb14 Formatter quoting for f-strings with triple quotes (#7826)
**Summary** Quoting of f-strings can change if they are triple quoted
and only contain single quotes inside.

Fixes #6841

**Test Plan** New fixtures

---------

Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2023-10-11 11:30:34 +00:00
T-256
a1ee6d28ce UP018: Improve Fix message (#7913)
<!--
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

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

## Test Plan

<!-- How was it tested? -->
2023-10-11 09:41:51 +00:00
Steve C
826868da5b add autofix for PIE804 (#7884) 2023-10-11 01:07:34 +00:00
Steve C
5986ff748a add autofix for PLC0208 (#7887) 2023-10-11 00:48:10 +00:00
Zanie Blue
739a8aa10e Add settings for promoting and demoting fixes (#7841)
Adds two configuration-file only settings `extend-safe-fixes` and
`extend-unsafe-fixes` which can be used to promote and demote the
applicability of fixes for rules.

Fixes with `Never` applicability cannot be promoted.
2023-10-10 20:04:21 +00:00
Charlie Marsh
090c1a4a19 Avoid converting f-strings within Django gettext calls (#7898)
## Summary

Django's `gettext` doesn't support f-strings, so we should avoid
translating `.format` calls in those cases.

Closes https://github.com/astral-sh/ruff/issues/7891.
2023-10-10 16:31:09 +00:00
Zanie Blue
2b95d3832b Update fix summary message in check --diff to include unsafe fix hints (#7790)
Requires #7769 

Updates the CLI display for `ruff check --fix` to hint availability of
unsafe fixes.

 ```
❯ ruff check example.py --select F601,T201 --diff --no-cache
No errors fixed (1 fix available with `--unsafe-fixes`).
```

```
❯ ruff check example.py --select F601,T201,W292 --diff --no-cache
--- example.py
+++ example.py
@@ -1,2 +1,2 @@
 x = {'a': 1, 'a': 1}
-print(('foo'))
+print(('foo'))
\ No newline at end of file

Would fix 1 error (1 additional fix available with `--unsafe-fixes`).
```
```
❯ ruff check example.py --select F601,T201,W292 --diff --no-cache
--unsafe-fixes
--- example.py
+++ example.py
@@ -1,2 +1,2 @@
-x = {'a': 1}
-print(('foo'))
+x = {'a': 1, 'a': 1}
+print(('foo'))
\ No newline at end of file

Would fix 2 errors.
```
2023-10-10 10:50:35 -05:00
Philipp A
d412e8ef74 [docs] Default to following the system dark/light mode (#7888)
## Summary

This fixes the theme toggle so it has the most useful setting on by
default: following the system dark/light mode

See
https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/#automatic-light-dark-mode

## Test Plan

It follows the official docs exactly. If the docs get published for each
PR branch, we can also check there if it works as intended.

---------

Co-authored-by: konstin <konstin@mailbox.org>
2023-10-10 15:25:49 +00:00
Dhruv Manilawala
46e45bdf19 Upgrade LibCST to 1.1.0 (#7896)
This PR updates the `libcst` crate version to `1.1.0`. The published
version contains support for 3.12:
https://github.com/Instagram/LibCST/releases/tag/v1.1.0
2023-10-10 14:58:03 +00:00
Charlie Marsh
a3e8e77172 Allow bindings to be created and referenced within annotations (#7885)
## Summary

Given:

```python
baz: Annotated[
    str,
    [qux for qux in foo],
]
```

We treat `baz` as `BindingKind::Annotation`, to ensure that references
to `baz` are marked as unbound. However, we were _also_ treating `qux`
as `BindingKind::Annotation`, which meant that the load in the
comprehension _also_ errored.

Closes https://github.com/astral-sh/ruff/issues/7879.
2023-10-10 03:51:00 +00:00
Charlie Marsh
ec7395ba69 Promote some rules to "always" fixable (#7840)
## Summary

This PR upgrades some rules from "sometimes" to "always" fixes, now that
we're getting ready to ship support in the CLI. The focus here was on
identifying rules for which the diagnostic itself is high-confidence,
and the fix itself is too (assuming that the diagnostic is correct).
This is _unlike_ rules that _may_ be a false positive, like those that
(e.g.) assume an object is a dictionary when you call `.values()` on it.

Specifically, I upgraded:

- A bunch of rules that only apply to `.pyi` files.
- Rules that rewrite deprecated imports or aliases.
- Some other misc. rules, like: `empty-print-string`, `unused-noqa`,
`getattr-with-constant`.

Open to feedback on any of these.
2023-10-10 03:30:46 +00:00
Steve C
d8c0360fc7 add autofix for PYI030 (#7880)
## Summary

Adds autofix to `PYI030`

Closes #7854. 

Unsure if the cloning method I chose is the best solution here, feel
free to suggest alternatives!

## Test Plan

`cargo test` as well as manually
2023-10-10 03:15:13 +00:00
Henry Schreiner
097b654ba7 fix(schema): restore limit on line-length (#7883)
## Summary

Restores functionality of #7875 but in the correct place. Closes #7877.

~~I couldn't figure out how to get cargo fmt to work, so hopefully
that's run in CI.~~ Nevermind, figured it out.

## Test Plan

Can see output of json.
2023-10-09 23:04:08 -04:00
Charlie Marsh
d54cabd276 Remove min and max range on line-length JSON schema (#7875)
## Summary

This was introduced in https://github.com/astral-sh/ruff/pull/7412, but
SchemaStore doesn't accept it. I manually edited the JSON schema last
time, then tried to fix this, then gave up -- so removing for now.

(See, e.g., https://github.com/SchemaStore/schemastore/pull/3278, which
failed prior to removing the min and max.)
2023-10-09 15:36:43 -04:00
Harutaka Kawamura
7faa43108f New rule: Prevent assignment expressions in assert statements (#7856) 2023-10-09 19:35:11 +00:00
Charlie Marsh
74b00c9b91 Fix commented-out coalesce keyword (#7876)
See:
https://github.com/astral-sh/ruff/pull/7874#issuecomment-1753498994.
2023-10-09 19:11:45 +00:00
Charlie Marsh
97e944003b Add sqlalchemy methods to boolean-trap exclusion list (#7874)
Closes https://github.com/astral-sh/ruff/issues/7869.
2023-10-09 18:50:51 +00:00
Alex Waygood
016e16254a Update UP038 docs to note that it results in slower code (#7872)
See discussion in #7871. I tried to use language similar to the existing
performance warnings in the `flake8-use-pathlib` docs, e.g.
https://docs.astral.sh/ruff/rules/os-path-abspath/#os-path-abspath-pth100
2023-10-09 11:45:14 -05:00
Charlie Marsh
61a41334a3 Show custom message for Path.joinpath with starred arguments (#7852)
Closes https://github.com/astral-sh/ruff/issues/7833.
2023-10-09 12:04:35 +00:00
dependabot[bot]
74971617a1 Bump cloudflare/wrangler-action from 3.1.1 to 3.2.0 (#7863) 2023-10-09 07:44:51 -04:00
dependabot[bot]
5c68c89566 Bump similar from 2.2.1 to 2.3.0 (#7862) 2023-10-09 07:44:42 -04:00
dependabot[bot]
8923eb19e0 Bump regex from 1.9.5 to 1.9.6 (#7861) 2023-10-09 07:44:35 -04:00
dependabot[bot]
dad70fff99 Bump syn from 2.0.37 to 2.0.38 (#7860) 2023-10-09 07:44:28 -04:00
dependabot[bot]
b72c94b3d1 Bump proc-macro2 from 1.0.67 to 1.0.69 (#7864) 2023-10-09 07:44:20 -04:00
dependabot[bot]
b4b296dca3 Bump clap from 4.4.5 to 4.4.6 (#7865) 2023-10-09 07:44:12 -04:00
Dhruv Manilawala
43883b7a15 Disallow f-strings in match pattern literal (#7857)
## Summary

This PR fixes a bug to disallow f-strings in match pattern literal.

```
literal_pattern ::=  signed_number
                     | signed_number "+" NUMBER
                     | signed_number "-" NUMBER
                     | strings
                     | "None"
                     | "True"
                     | "False"
                     | signed_number: NUMBER | "-" NUMBER
```

Source:
https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-literal_pattern

Also,

```console
$ python /tmp/t.py
  File "/tmp/t.py", line 4
    case "hello " f"{name}":
         ^^^^^^^^^^^^^^^^^^
SyntaxError: patterns may only match literals and attribute lookups
```

## Test Plan

Update existing test case and accordingly the snapshots. Also, add a new
test case to verify that the parser does raise an error.
2023-10-09 10:11:08 +00:00
bluthej
38f512d588 Fix diff (old and new were reversed) (#7855)
## Summary

Fixes #7853.

The old and new source files were reversed in the call to
`TextDiff::from_lines`, so the diff output of the CLI was also reversed.

## Test Plan

Two snapshots were updated in the process, so any reversal should be
caught :)
2023-10-09 12:58:13 +05:30
Zanie Blue
2d6557a51b Only show warnings for empty preview selectors when enabling rules (#7842)
Closes https://github.com/astral-sh/ruff/issues/7491

Users found it confusing that warnings were displayed when ignoring a
preview rule (which has no effect without `--preview`). While we could
retain the warning with different messaging, I've opted to remove it for
now. With this pull request, we will only warn on `--select` and
`--extend-select` but not `--fixable`, `--unfixable`, `--ignore`, or
`--extend-fixable`.
2023-10-08 11:14:37 -05:00
Simon Høxbro Hansen
2ba5677700 Improvements to RUF015 (#7848)
## Summary

Resolves https://github.com/astral-sh/ruff/issues/7618. 

The list of builtin iterator is not exhaustive.

## Test Plan

`cargo test`

``` python
a = [1, 2]

examples = [
    enumerate(a),
    filter(lambda x: x, a),
    map(int, a),
    reversed(a),
    zip(a),
    iter(a),
]

for example in examples:
    print(next(example))
```
2023-10-08 14:49:45 +00:00
Tom Kuson
62f1ee08e7 [refurb] Implement single-item-membership-test (FURB171) (#7815)
## Summary

Implement
[`no-single-item-in`](https://github.com/dosisod/refurb/blob/master/refurb/checks/iterable/no_single_item_in.py)
as `single-item-membership-test` (`FURB171`).

Uses the helper function `generate_comparison` from the `pycodestyle`
implementations; this function should probably be moved, but I am not
sure where at the moment.

Update: moved it to `ruff_python_ast::helpers`.

Related to #1348.

## Test Plan

`cargo test`
2023-10-08 14:08:47 +00:00
Chris Pryer
bdd925c0f2 Use workspace tracing in ruff_formatter crate (#7849)
I noticed that `tracing::instrument` wasn't available with only the
`"std"` feature enabled when trying to run `cargo doc -p
ruff_formatter`.

I could be misunderstanding something, but I couldn't even run the tests
for the crate.

```
ruff on  ruff-formatter-tracing [$] is 📦 v0.0.292 via 🦀 v1.72.0 
❯ cargo test -p ruff_formatter          
   Compiling ruff_formatter v0.0.0 (/Users/chrispryer/github/ruff/crates/ruff_formatter)
error[E0433]: failed to resolve: could not find `instrument` in `tracing`
   --> crates/ruff_formatter/src/printer/mod.rs:57:16
    |
57  |     #[tracing::instrument(name = "Printer::print", skip_all)]
    |                ^^^^^^^^^^ could not find `instrument` in `tracing`
    |
note: found an item that was configured out
   --> /Users/chrispryer/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tracing-0.1.37/src/lib.rs:959:29
    |
959 | pub use tracing_attributes::instrument;
    |                             ^^^^^^^^^^
    = note: the item is gated behind the `attributes` feature

For more information about this error, try `rustc --explain E0433`.
error: could not compile `ruff_formatter` (lib) due to previous error
warning: build failed, waiting for other jobs to finish...
error: could not compile `ruff_formatter` (lib test) due to previous error
```

Maybe the idea is to keep this crate minimal, but I figured I'd at least
point this out.
2023-10-08 09:50:10 -04:00
konsti
dd36a2516e Make serde a default feature of ruff_python_formatter (#7825)
This makes `cargo test -p ruff_python_formatter` actually run the tests
again
2023-10-08 09:47:13 -04:00
Chris Pryer
b6c9cf1c5b Update ruff_python_formatter generate.py comment (#7850)
I believe Docs.md is old.
2023-10-07 20:56:07 -04:00
Tom Kuson
805fd1bc93 Document reimplemented-starmap performance effects (#7846)
## Summary

Document the performance effects of `itertools.starmap`, including that
it is actually slower than comprehensions in Python 3.12.

Closes #7771.

## Test Plan

`python scripts/check_docs_formatted.py`
2023-10-07 09:27:02 -04:00
Zanie Blue
0fc76ba276 Rename applicability levels to Safe, Unsafe, and Display (#7843)
After working with the previous change in
https://github.com/astral-sh/ruff/pull/7821 I found the names a bit
unclear and their relationship with the user-facing API muddied. Since
the applicability is exposed to the user directly in our JSON output, I
think it's important that these names align with our configuration
options. I've replaced `Manual` or `Never` with `Display` which captures
our intent for these fixes (only for display). Here, we create room for
future levels, such as `HasPlaceholders`, which wouldn't fit into the
`Always`/`Sometimes`/`Never` levels.

Unlike https://github.com/astral-sh/ruff/pull/7819, this retains the
flat enum structure which is easier to work with.
2023-10-06 20:50:05 -05:00
Zanie Blue
4b537d1297 Update non-pep695-type-alias to require --unsafe-fixes outside of stub files (#7836)
Closes https://github.com/astral-sh/ruff/issues/6434
2023-10-06 14:56:40 -05:00
Zanie Blue
3c25d261fe Write summary messages to stderr when fixing via stdin (instead of omitting them) (#7838)
Previously we just omitted diagnostic summaries when using `--fix` or
`--diff` with a stdin file. Now, we still write the summaries to stderr
instead of the main writer (which is generally stdout but could be
changed by `--output-file`).
2023-10-06 12:11:03 -05:00
Zanie Blue
4f95df1b6d Fixup use of deprecated --format option in warning (#7837) 2023-10-06 16:10:48 +00:00
Zanie Blue
22e18741bd Update CLI to respect fix applicability (#7769)
Rebase of https://github.com/astral-sh/ruff/pull/5119 authored by
@evanrittenhouse with additional refinements.

## Changes

- Adds `--unsafe-fixes` / `--no-unsafe-fixes` flags to `ruff check`
- Violations with unsafe fixes are not shown as fixable unless opted-in
- Fix applicability is respected now
    - `Applicability::Never` fixes are no longer applied
    - `Applicability::Sometimes` fixes require opt-in
    - `Applicability::Always` fixes are unchanged
- Hints for availability of `--unsafe-fixes` added to `ruff check`
output

## Examples

Check hints at hidden unsafe fixes
```
❯ ruff check example.py --no-cache --select F601,W292
example.py:1:14: F601 Dictionary key literal `'a'` repeated
example.py:2:15: W292 [*] No newline at end of file
Found 2 errors.
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
```

We could add an indicator for which violations have hidden fixes in the
future.

Check treats unsafe fixes as applicable with opt-in
```
❯ ruff check example.py --no-cache --select F601,W292 --unsafe-fixes
example.py:1:14: F601 [*] Dictionary key literal `'a'` repeated
example.py:2:15: W292 [*] No newline at end of file
Found 2 errors.
[*] 2 fixable with the --fix option.
```

Also can be enabled in the config file

```
❯ cat ruff.toml
unsafe-fixes = true
```

And opted-out per invocation

```
❯ ruff check example.py --no-cache --select F601,W292 --no-unsafe-fixes
example.py:1:14: F601 Dictionary key literal `'a'` repeated
example.py:2:15: W292 [*] No newline at end of file
Found 2 errors.
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
```

Diff does not include unsafe fixes
```
❯ ruff check example.py --no-cache --select F601,W292 --diff
--- example.py
+++ example.py
@@ -1,2 +1,2 @@
 x = {'a': 1, 'a': 1}
-print(('foo'))
+print(('foo'))
\ No newline at end of file

Would fix 1 error.
```

Unless there is opt-in
```
❯ ruff check example.py --no-cache --select F601,W292 --diff --unsafe-fixes
--- example.py
+++ example.py
@@ -1,2 +1,2 @@
-x = {'a': 1}
-print(('foo'))
+x = {'a': 1, 'a': 1}
+print(('foo'))
\ No newline at end of file

Would fix 2 errors.
```

https://github.com/astral-sh/ruff/pull/7790 will improve the diff
messages following this pull request

Similarly, `--fix` and `--fix-only` require the `--unsafe-fixes` flag to
apply unsafe fixes.

## Related

Replaces #5119
Closes https://github.com/astral-sh/ruff/issues/4185
Closes https://github.com/astral-sh/ruff/issues/7214
Closes https://github.com/astral-sh/ruff/issues/4845
Closes https://github.com/astral-sh/ruff/issues/3863
Addresses https://github.com/astral-sh/ruff/issues/6835
Addresses https://github.com/astral-sh/ruff/issues/7019
Needs follow-up https://github.com/astral-sh/ruff/issues/6962
Needs follow-up https://github.com/astral-sh/ruff/issues/4845
Needs follow-up https://github.com/astral-sh/ruff/issues/7436
Needs follow-up https://github.com/astral-sh/ruff/issues/7025
Needs follow-up https://github.com/astral-sh/ruff/issues/6434
Follow-up #7790 
Follow-up https://github.com/astral-sh/ruff/pull/7792

---------

Co-authored-by: Evan Rittenhouse <evanrittenhouse@gmail.com>
2023-10-06 03:41:43 +00:00
Zanie Blue
e8d2cbc3f6 Fix invalid code in FURB177 example (#7832) 2023-10-05 19:25:10 -05:00
Timo Brembeck
1dd5deb53d Fix typo in docs of PLR6301 (#7831)
## Summary
The example code for [PLR6301
(no-self-use)](https://docs.astral.sh/ruff/rules/no-self-use/#example)
contains f-strings without placeholder expressions, which is discouraged
according to [F541
(f-string-missing-placeholders)](https://docs.astral.sh/ruff/rules/f-string-missing-placeholders/).
For such a trivial change, I didn't open a separate issue.
2023-10-05 21:16:43 +00:00
Zanie Blue
b64f403dc2 Rename applicability levels to always, sometimes, and never (#7821)
Following much discussion for #4181 at
https://github.com/astral-sh/ruff/pull/5119,
https://github.com/astral-sh/ruff/discussions/5476, #7769,
https://github.com/astral-sh/ruff/pull/7819, and in
[Discord](https://discord.com/channels/1039017663004942429/1082324250112823306/1159144114231709746),
this pull request changes `Applicability` from using `Automatic`,
`Suggested`, and `Manual` to `Always`, `Sometimes`, and `Never`.

Also removes `Applicability::Unspecified` (replacing #7792).
2023-10-05 13:43:46 -05:00
Zanie Blue
7dc9887ab9 Remove unused empty file (#7830) 2023-10-05 13:35:50 -05:00
Dhruv Manilawala
709abd534a Fix lexing single-quoted f-string with multi-line format spec (#7787)
## Summary

Reported at https://github.com/python/cpython/issues/110259

## Test Plan

Add test cases for the fix and update the snapshots
2023-10-05 23:12:09 +05:30
Dhruv Manilawala
27def479bd Remove unused ts directive (#7829)
See: https://github.com/astral-sh/ruff/actions/runs/6421876406/job/17437188754
2023-10-05 17:11:05 +00:00
konsti
1eac457c1b Fix typo (#7828) 2023-10-05 16:56:11 +00:00
Charlie Marsh
609a78b13e Add trailing comment deviation to README (#7827)
Closes https://github.com/astral-sh/ruff/issues/7823.
2023-10-05 16:01:40 +00:00
Dhruv Manilawala
17fba99ed4 Report precise location for invalid conversion flag (#7809)
## Summary

This PR updates the parser definition to use the precise location when reporting
an invalid f-string conversion flag error.

Taking the following example code:
```python
f"{foo!x}"
```

On earlier version,
```
Error: f-string: invalid conversion character at byte offset 6
```

Now,
```
Error: f-string: invalid conversion character at byte offset 7
```

This becomes more useful when there's whitespace between `!` and the flag value
although that is not valid but we can't detect that now.

## Test Plan

As mentioned above.
2023-10-05 17:46:14 +05:30
Dhruv Manilawala
adb6580270 Fix playground Quick Fix action (#7824)
## Summary

This PR fixes the playground code action to start working again. It seems that
the field name was changed from `edit` to `textEdit` in some version.

Resources:
- https://microsoft.github.io/monaco-editor/docs.html#interfaces/languages.IWorkspaceTextEdit.html
- https://stackoverflow.com/a/71742764

## Test Plan

Tested it out running locally.
2023-10-05 11:41:18 +05:30
Cosmo
76fcf63052 Correct error in tuple example in ruff formatter docs (#7822)
## Summary

The fourth element should be "d" instead of "c" in the tuple example in
the ruff formatter docs.

## Test Plan

N/A
2023-10-04 22:51:17 +00:00
Charlie Marsh
90de108bfa Split up ast_if.rs into distinct rule files (#7820)
These node-centric rule files are too hard to navigate. Better to have a
single file per rule as we do elsewhere.
2023-10-04 19:39:05 +00:00
Charlie Marsh
ad265fa6bc Allow f-string modifications in line-shrinking cases (#7818)
## Summary

This PR resolves an issue raised in
https://github.com/astral-sh/ruff/discussions/7810, whereby we don't fix
an f-string that exceeds the line length _even if_ the resultant code is
_shorter_ than the current code.

As part of this change, I've also refactored and extracted some common
logic we use around "ensuring a fix isn't breaking the line length
rules".

## Test Plan

`cargo test`
2023-10-04 15:24:07 -04:00
Charlie Marsh
59c00b5298 Use a dedicated struct for "nested if" rule (#7817)
Internal refactor -- finding this rule hard to understand.
2023-10-04 18:18:59 +00:00
Charlie Marsh
a0c846f9bd Consider nursery rules to be in-preview for ruff rule (#7812)
## Summary

We treat these rules as `preview` elsewhere, so adding `preview: false`
to the JSON and such seems like an error.

Closes https://github.com/astral-sh/ruff/issues/7804.
2023-10-04 11:12:43 -04:00
Charlie Marsh
bb87f75b0c Move diffing logic into SourceKind::diff (#7813) 2023-10-04 15:08:53 +00:00
Charlie Marsh
e674e87d1b Show per-cell diffs when analyzing notebooks over stdin (#7789)
## Summary

The implementation here differs from the non-`stdin` version -- this is
now more consistent.

## Test Plan

```
❯ cat Untitled.ipynb | cargo run -p ruff_cli -- check --stdin-filename Untitled.ipynb --diff -n
    Finished dev [unoptimized + debuginfo] target(s) in 0.11s
     Running `target/debug/ruff check --stdin-filename Untitled.ipynb --diff -n`
--- Untitled.ipynb:cell 2
+++ Untitled.ipynb:cell 2
@@ -1 +0,0 @@
-import os
--- Untitled.ipynb:cell 4
+++ Untitled.ipynb:cell 4
@@ -1 +0,0 @@
-import sys
```
2023-10-04 13:58:07 +00:00
Jelle Zijlstra
600471e45f Fix SIM110 with a yield in the condition (#7801)
And allow "await" in the loop iterable.

Fixes #7800
2023-10-04 08:59:19 -04:00
Dhruv Manilawala
a1509dfc7c Use correct start location for class/function clause header (#7802)
## Summary

This PR fixes the bug where the formatter would panic if a class/function with
decorators had a suppression comment.

The fix is to use to correct start location to find the `async`/`def`/`class`
keyword when decorators are present which is the end of the last
decorator.

## Test Plan

Add test cases for the fix and update the snapshots.
2023-10-04 07:55:01 +00:00
Jelle Zijlstra
7b4fb4fb5d Fix issues with SIM101 (adjacent isinstance() calls) (#7798)
- Only trigger for immediately adjacent isinstance() calls with the same
target
- Preserve order of or conditions

Two existing tests changed:
- One was incorrectly reordering the or conditions, and is now correct.
- Another was combining two non-adjacent isinstance() calls. It's safe
enough in that example,
but this isn't safe to do in general, and it feels low-value to come up
with a heuristic for
when it is safe, so it seems better to not combine the calls in that
case.

Fixes https://github.com/astral-sh/ruff/issues/7797
2023-10-04 04:42:13 +00:00
Zanie Blue
5d49d268a0 Fix publish of playground (#7791)
Same as https://github.com/astral-sh/ruff/pull/7304

Closes https://github.com/astral-sh/ruff/issues/7779
2023-10-03 14:33:09 -05:00
Charlie Marsh
f71c80af68 Show changed files when running under --check (#7788)
## Summary

We now list each changed file when running with `--check`.

Closes https://github.com/astral-sh/ruff/issues/7782.

## Test Plan

```
❯ cargo run -p ruff_cli -- format foo.py --check
   Compiling ruff_cli v0.0.292 (/Users/crmarsh/workspace/ruff/crates/ruff_cli)
rgo +    Finished dev [unoptimized + debuginfo] target(s) in 1.41s
     Running `target/debug/ruff format foo.py --check`
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
Would reformat: foo.py
1 file would be reformatted
```
2023-10-03 18:50:06 +00:00
Charlie Marsh
90c259beb9 Respect msgspec.Struct default-copy semantics (#7786)
## Summary

The carve-out we have in `RUF012` for Pydantic classes also applies to
`msgspec.Struct`.

Closes https://github.com/astral-sh/ruff/issues/7785.
2023-10-03 16:51:25 +00:00
Tom Kuson
37d21c0d54 Check sequence type before triggering unnecessary-enumerate (FURB148) len suggestion (#7781)
## Summary

Check that the sequence type is a list, set, dict, or tuple before
recommending replacing the `enumerate(...)` call with `range(len(...))`.
Document behaviour so users are aware of the type inference limitation
leading to false negatives.

Closes #7656.
2023-10-03 14:39:14 +00:00
Dhruv Manilawala
69b8136463 Avoid curly brace escape in f-string format spec (#7780)
## Summary

This PR fixes a bug in the lexer for f-string format spec where it would
consider the `{{` (double curly braces) as an escape pattern.

This is not the case as evident by the
[PEP](https://peps.python.org/pep-0701/#how-to-produce-these-new-tokens)
as well but I missed the part:

> [..]
> * **If in “format specifier mode” (see step 3), an opening brace ({)
or a closing brace (}).**
> * If not in “format specifier mode” (see step 3), an opening brace ({)
or a closing brace (}) that is not immediately followed by another
opening/closing brace.

## Test Plan

Add a test case to verify the fix and update the snapshot.

fixes: #7778
2023-10-03 19:38:03 +05:30
Charlie Marsh
c040fac12f Preserve trailing comments in C414 fixes (#7775)
Closes https://github.com/astral-sh/ruff/issues/7772.
2023-10-03 04:36:51 +00:00
Charlie Marsh
a6ebbf21c3 Fix documented examples for unnecessary-subscript-reversal (#7774)
## Summary

Two of the three listed examples were wrong: one was semantically
incorrect, another was _correct_ but not actually within the scope of
the rule.

Good motivation for us to start linting documentation examples :)

Closes https://github.com/astral-sh/ruff/issues/7773.
2023-10-03 04:18:49 +00:00
Tom Kuson
e129f77bcf Extend reimplemented-starmap (FURB140) to catch calls with a single and starred argument (#7768) 2023-10-02 21:38:05 -04:00
konsti
3ccd1d580d Use crates.io unicode_names2 0.6.0 (#6478)
Update `unicode_names2` to the crates.io release 0.6.0, removing a git
dependency.
2023-10-02 18:17:38 -04:00
Charlie Marsh
f872c3bf0f Document one-call chaining deviation (#7767)
## Summary

I missed this in the prior pass.

Closes https://github.com/astral-sh/ruff/issues/7051.
2023-10-02 21:46:04 +00:00
Colton Berry
55fa887099 Change crlf to cr-lf in docs (#7766)
## Summary
This change fixes an error in the documentation where cr-lf was
displayed as crlf which if you tried to enter into the configuration
file running ruff would break.

## Test Plan
I ran the tests locally and I ran the documentation server locally and
verified the edit

### [Documentation
Site](https://docs.astral.sh/ruff/settings/#format-line-ending)

![image](https://github.com/astral-sh/ruff/assets/50351006/8e63e49c-63ff-4027-a583-537c710e1305)

### Local

![image](https://github.com/astral-sh/ruff/assets/50351006/8845a235-8b2c-4157-99c8-908ee8f039b3)
2023-10-02 21:09:11 +00:00
Charlie Marsh
c6d0bdd572 Bump Ruff version to v0.0.292 (#7761) 2023-10-02 12:14:47 -04:00
Charlie Marsh
75f759ed55 Upgrade LibCST to support Python 3.12 (#7764)
## Summary

We'll revert back to the crates.io release once it's up-to-date, but
better to get this out now that Python 3.12 is released.

## Test Plan

`cargo test`
2023-10-02 12:14:35 -04:00
Charlie Marsh
6b99f5e3e6 Re-add formatter to GitHub release notes (#7763)
We may choose to omit these manually, but we probably want to include
_some_ of them, so it's annoying for them to be filtered out.
2023-10-02 15:10:22 +00:00
Charlie Marsh
97c092a102 Add formatter TOML configuration to the README (#7762)
## Summary

This section is dated -- we now support configuration.
2023-10-02 15:07:02 +00:00
Charlie Marsh
bdf285225d Enable formatting for Jupyter notebooks (#7749)
## Summary

This PR enables `ruff format` to format Jupyter notebooks.

Most of the work is contained in a new `format_source` method that
formats a generic `SourceKind`, then returns `Some(transformed)` if the
source required formatting, or `None` otherwise.

Closes https://github.com/astral-sh/ruff/issues/7598.

## Test Plan

Ran `cat foo.py | cargo run -p ruff_cli -- format --stdin-filename
Untitled.ipynb`; verified that the console showed a reasonable error:

```console
warning: Failed to read notebook Untitled.ipynb: Expected a Jupyter Notebook, which must be internally stored as JSON, but this file isn't valid JSON: EOF while parsing a value at line 1 column 0
```

Ran `cat Untitled.ipynb | cargo run -p ruff_cli -- format
--stdin-filename Untitled.ipynb`; verified that the JSON output
contained formatted source code.
2023-10-02 14:44:18 +00:00
konsti
0961f008b8 Rename FixKind to FixAvailability (#7658)
**Summary** `FixKind` feels to generic, i suggest renaming it to
something like `FixAvailibility`.

Commands used:

```bash
rg FixKind --files-with-matches | xargs sed -i 's/FixKind/FixAvailability/g'
rg fix_kind --files-with-matches | xargs sed -i 's/fix_kind/fix_availability/g'
rg FIX_KIND --files-with-matches | xargs sed -i 's/FIX_KIND/FIX_AVAILABILITY/g'
cargo fmt
```

`rg -i "fix.kind"` doesn't show any matches anymore.
2023-10-02 14:38:25 +00:00
Charlie Marsh
ebdfcee87f Write full Jupyter notebook to stdout (#7748)
## Summary

When writing back notebooks via `stdout`, we need to write back the
entire JSON content, not _just_ the fixed source code. Otherwise,
writing the output _back_ to the file will yield an invalid notebook.

Closes https://github.com/astral-sh/ruff/issues/7747

## Test Plan

`cargo test`
2023-10-02 14:20:13 +00:00
Charlie Marsh
c71ff7eae1 Avoid printing continuations within import identifiers (#7744)
## Summary

It turns out that _some_ identifiers can contain newlines --
specifically, dot-delimited import identifiers, like:
```python
import foo\
    .bar
```

At present, we print all identifiers verbatim, which causes us to retain
the `\` in the formatted output. This also leads to violating some debug
assertions (see the linked issue, though that's a symptom of this
formatting failure).

This PR adds detection for import identifiers that contain newlines, and
formats them via `text` (slow) rather than `source_code_slice` (fast) in
those cases.

Closes https://github.com/astral-sh/ruff/issues/7734.

## Test Plan

`cargo test`
2023-10-02 09:51:07 -04:00
dependabot[bot]
0df27375ba Bump memchr from 2.6.3 to 2.6.4 (#7758) 2023-10-02 09:50:22 -04:00
dependabot[bot]
c82d0503a8 Bump thiserror from 1.0.48 to 1.0.49 (#7757) 2023-10-02 09:50:06 -04:00
dependabot[bot]
7d7e0824af Bump ureq from 2.7.1 to 2.8.0 (#7756) 2023-10-02 09:49:59 -04:00
dependabot[bot]
8d1d5b8d80 Bump pep440_rs from 0.3.11 to 0.3.12 (#7755) 2023-10-02 09:49:50 -04:00
dependabot[bot]
9ba5bc26f6 Bump insta from 1.32.0 to 1.33.0 (#7754) 2023-10-02 09:49:43 -04:00
konsti
13748dd27c Use locator.slice(range) over locator.contents()[range] (#7759)
**Summary** Refactoring inspired by
https://github.com/astral-sh/ruff/pull/7741#discussion_r1342168033
2023-10-02 09:07:32 +00:00
konsti
f70e8a7524 Fix PLE251 rules with f-string escaping (#7741)
**Summary** The `value` of the `FStringMiddle` for `f"""}}ab"""` is
`}ab`, i.e. the curly brace escaping is decoded. If we iterate over
string this gives us false indices causing exploding fixes for PLE251
rules (PLE2510, PLE2512, PLE2513, PLE2514, PLE2515). Instead, we now use
the source range.

Handles
https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106
Handles
https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998256

**Test Plan** Minimized fuzzing cases as fixtures.
2023-10-02 08:43:39 +00:00
Charlie Marsh
1df8101b9e Require documentation for all lint rules (#7751)
## Summary

Now that all rules have documentation, we can enforce the requirement in
our tests.
2023-10-02 01:49:07 +00:00
jan Apisu
6a4437ea81 Add documentation for remaining undocumented lint rules (#7750) 2023-10-02 00:56:33 +00:00
Charlie Marsh
4d2de898e3 Decrease PEP 593 error to a debug warning (#7745)
## Summary

There's no way for users to fix this warning if they're intentionally
using an "invalid" PEP 593 annotation, as is the case in CPython. This
is a symptom of having warnings that aren't themselves diagnostics. If
we want this to be user-facing, we should add a diagnostic for it!

## Test Plan

Ran `cargo run -p ruff_cli -- check foo.py -n` on:

```python
from typing import Annotated

Annotated[int]
```
2023-10-01 14:40:36 -04:00
Charlie Marsh
d8a6279fe5 Remove string allocation in relative import formatting (#7743) 2023-10-01 18:15:43 +00:00
Charlie Marsh
2838f7af98 Skip all bracketed expressions when locating comparison ops (#7740)
Closes https://github.com/astral-sh/ruff/issues/7737.
2023-10-01 14:57:40 +00:00
Charlie Marsh
1cf3b5676f Perform insertions before replacements (#7739)
## Summary

If we have, e.g.:

```python
sum((
            factor.dims for factor in bases
        ), [])
```

We generate three edits: two insertions (for the `operator` and
`functools` imports), and then one replacement (for the `sum` call
itself). We need to ensure that the insertions come before the
replacement; otherwise, the edits will appear overlapping and
out-of-order.

Closes https://github.com/astral-sh/ruff/issues/7718.
2023-10-01 14:53:54 +00:00
Dhruv Manilawala
e91ffe3e93 Consume the escaped Windows newline (\r\n) for FStringMiddle (#7722)
## Summary

This PR fixes a bug where if a Windows newline (`\r\n`) character was
escaped, then only the `\r` was consumed and not `\n` leading to an
unterminated string error.

## Test Plan

Add new test cases to check the newline escapes.

fixes: #7632
2023-10-01 07:58:20 +05:30
Dhruv Manilawala
e72d617f4b Remove escaped mac/windows eol from AST string value (#7724)
## Summary

This PR fixes the bug where the value of a string node type includes the
escaped mac/windows newline character.

Note that the token value still includes them, it's only removed when
parsing the string content.

## Test Plan

Add new test cases for the string node type to check that the escapes
aren't being included in the string value.

fixes: #7723
2023-10-01 07:37:59 +05:30
Charlie Marsh
488ec54d21 Add Python 3.12 support to FAQ (#7729) 2023-10-01 00:41:16 +00:00
Charlie Marsh
c782770e90 Add consistent period in options documentation (#7725) 2023-09-30 23:07:30 +00:00
Charlie Marsh
1646939383 Ignore overlong pragma comments when enforcing linter line length (#7692)
## Summary

This PR modifies the `line-too-long` and `doc-line-too-long` rules to
ignore lines that are too long due to the presence of a pragma comment
(e.g., `# type: ignore` or `# noqa`). That is, if a line only exceeds
the limit due to the pragma comment, it will no longer be flagged as
"too long". This behavior mirrors that of the formatter, thus ensuring
that we don't flag lines under E501 that the formatter would otherwise
avoid wrapping.

As a concrete example, given a line length of 88, the following would
_no longer_ be considered an E501 violation:

```python
# The string literal is 88 characters, including quotes.
"shape:shape:shape:shape:shape:shape:shape:shape:shape:shape:shape:shape:shape:shape:sh"  # type: ignore
```

This, however, would:

```python
# The string literal is 89 characters, including quotes.
"shape:shape:shape:shape:shape:shape:shape:shape:shape:shape:shape:shape:shape:shape:sha"  # type: ignore
```

In addition to mirroring the formatter, this also means that adding a
pragma comment (like `# noqa`) won't _cause_ additional violations to
appear (namely, E501). It's very common for users to add a `# type:
ignore` or similar to a line, only to find that they then have to add a
suppression comment _after_ it that was required before, as in `# type:
ignore # noqa: E501`.

Closes https://github.com/astral-sh/ruff/issues/7471.

## Test Plan

`cargo test`
2023-09-29 23:26:52 +00:00
Dhruv Manilawala
b519b56e81 Compute NotebookIndex for Diagnostics on stdin (#7663)
## Summary

This PR fixes the bug where the `NotebookIndex` was not being computed
when
using stdin as the input source.

## Test Plan

On `main`, the diagnostic output won't include the cell number when
using stdin
while it'll be included after this fix.

### `main`

```console
$ cat ~/playground/ruff/notebooks/test.ipynb | cargo run --bin ruff -- check --isolated --no-cache - --stdin-filename ~/playground/ruff/notebooks/test.ipynb
/Users/dhruv/playground/ruff/notebooks/test.ipynb:2:8: F401 [*] `math` imported but unused
/Users/dhruv/playground/ruff/notebooks/test.ipynb:7:8: F811 Redefinition of unused `random` from line 1
/Users/dhruv/playground/ruff/notebooks/test.ipynb:8:8: F401 [*] `pprint` imported but unused
/Users/dhruv/playground/ruff/notebooks/test.ipynb:12:4: F632 [*] Use `==` to compare constant literals
/Users/dhruv/playground/ruff/notebooks/test.ipynb:13:38: F632 [*] Use `==` to compare constant literals
Found 5 errors.
[*] 4 potentially fixable with the --fix option.
```

### `dhruv/notebook-index-stdin`

```console
$ cat ~/playground/ruff/notebooks/test.ipynb | cargo run --bin ruff -- check --isolated --no-cache - --stdin-filename ~/playground/ruff/notebooks/test.ipynb       
/Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 3:2:8: F401 [*] `math` imported but unused
/Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 5:1:8: F811 Redefinition of unused `random` from line 1
/Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 5:2:8: F401 [*] `pprint` imported but unused
/Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 6:2:4: F632 [*] Use `==` to compare constant literals
/Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 6:3:38: F632 [*] Use `==` to compare constant literals
Found 5 errors.
[*] 4 potentially fixable with the --fix option.
```
2023-09-29 20:37:41 +00:00
Charlie Marsh
8c8988ea40 Improve performance of commented-out-code (~50-80%) (#7706)
## Summary

This PR implements a variety of optimizations to improve performance of
the Eradicate rule, which always shows up in all-rules benchmarks and
bothers me. (These improvements are not hugely important, but it was
kind of a fun Friday thing to spent a bit of time on.)

The improvements include:

- Doing cheaper work first (checking for some explicit substrings
upfront).
- Using `aho-corasick` to speed an exact substring search.
- Merging multiple regular expressions using a `RegexSet`.
- Removing some unnecessary `\s*` and other pieces from the regular
expressions (since we already trim strings before matching on them).

## Test Plan

I benchmarked this function in a standalone crate using a variety of
cases. Criterion reports that this version is up to 80% faster, and
almost every case is at least 50% faster:

```
Eradicate/Detection/# Warn if we are installing over top of an existing installation. This can
                        time:   [101.84 ns 102.32 ns 102.82 ns]
                        change: [-77.166% -77.062% -76.943%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 3 outliers among 100 measurements (3.00%)
  3 (3.00%) high mild
Eradicate/Detection/#from foo import eradicate
                        time:   [74.872 ns 75.096 ns 75.314 ns]
                        change: [-84.180% -84.131% -84.079%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 1 outliers among 100 measurements (1.00%)
  1 (1.00%) high mild
Eradicate/Detection/# encoding: utf8
                        time:   [46.522 ns 46.862 ns 47.237 ns]
                        change: [-29.408% -28.918% -28.471%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 7 outliers among 100 measurements (7.00%)
  6 (6.00%) high mild
  1 (1.00%) high severe
Eradicate/Detection/# Issue #999
                        time:   [16.942 ns 16.994 ns 17.058 ns]
                        change: [-57.243% -57.064% -56.815%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 3 outliers among 100 measurements (3.00%)
  2 (2.00%) high mild
  1 (1.00%) high severe
Eradicate/Detection/# type: ignore
                        time:   [43.074 ns 43.163 ns 43.262 ns]
                        change: [-17.614% -17.390% -17.152%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
  3 (3.00%) high mild
  2 (2.00%) high severe
Eradicate/Detection/# user_content_type, _ = TimelineEvent.objects.using(db_alias).get_or_create(
                        time:   [209.40 ns 209.81 ns 210.23 ns]
                        change: [-32.806% -32.630% -32.470%] (p = 0.00 < 0.05)
                        Performance has improved.
Eradicate/Detection/# this is = to that :(
                        time:   [72.659 ns 73.068 ns 73.473 ns]
                        change: [-68.884% -68.775% -68.655%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 9 outliers among 100 measurements (9.00%)
  7 (7.00%) high mild
  2 (2.00%) high severe
Eradicate/Detection/#except Exception:
                        time:   [92.063 ns 92.366 ns 92.691 ns]
                        change: [-64.204% -64.052% -63.909%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 4 outliers among 100 measurements (4.00%)
  2 (2.00%) high mild
  2 (2.00%) high severe
Eradicate/Detection/#print(1)
                        time:   [68.359 ns 68.537 ns 68.725 ns]
                        change: [-72.424% -72.356% -72.278%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 2 outliers among 100 measurements (2.00%)
  1 (1.00%) low mild
  1 (1.00%) high mild
Eradicate/Detection/#'key': 1 + 1,
                        time:   [79.604 ns 79.865 ns 80.135 ns]
                        change: [-69.787% -69.667% -69.549%] (p = 0.00 < 0.05)
                        Performance has improved.
```
2023-09-29 20:13:12 +00:00
Charlie Marsh
e9f8b91eb5 Preserve parentheses in quadratic-list-summation (#7719)
Closes https://github.com/astral-sh/ruff/issues/7718.
2023-09-29 20:04:56 +00:00
Charlie Marsh
b5280061f8 Use fixed source code for parser context (#7717)
## Summary

The parser now uses the raw source code as global context and slices
into it to parse debug text. It turns out we were always passing in the
_old_ source code, so when code was fixed, we were making invalid
accesses. This PR modifies the call to use the _fixed_ source code,
which will always be consistent with the tokens.

Closes https://github.com/astral-sh/ruff/issues/7711.

## Test Plan

`cargo test`
2023-09-29 14:10:32 -04:00
Charlie Marsh
b42a8972bf Use Expr::is_* methods in more matches (#7714) 2023-09-29 17:28:50 +00:00
Charlie Marsh
bb65fb8486 Document next round of intentional formatter deviations (#7679)
## Summary

Based on today's triage with @MichaReiser.

Closes https://github.com/astral-sh/ruff/issues/7652.
Closes https://github.com/astral-sh/ruff/issues/7320.
Closes https://github.com/astral-sh/ruff/issues/7052.
Closes https://github.com/astral-sh/ruff/issues/7314.
Closes https://github.com/astral-sh/ruff/issues/7317.
Closes https://github.com/astral-sh/ruff/issues/7323.
Closes https://github.com/astral-sh/ruff/issues/7320.
Closes https://github.com/astral-sh/ruff/issues/7315.
2023-09-29 17:27:30 +00:00
Charlie Marsh
253fbb665f Track fix isolation in unnecessary-pass (#7715)
## Summary

This wasn't necessary in the past, since we _only_ applied this rule to
bodies that contained two statements, one of which was a `pass`. Now
that it applies to any `pass` in a block with multiple statements, we
can run into situations in which we remove both passes, and so need to
apply the fixes in isolation.

See:
https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741107573.
2023-09-29 17:23:04 +00:00
Zanie Blue
974262ad2c Update pyproject authors, maintainers, supported Python version labels (#7713) 2023-09-29 17:04:10 +00:00
Tom Kuson
dc51d03866 Fix documentation for no-return-argument-annotation-in-stub [PYI050] (#7708)
## Summary

The markdown documentation was present, but in the wrong place, so was
not displaying on the website. I moved it and added some references.

Related to #2646.

## Test Plan

`python scripts/check_docs_formatted.py`
2023-09-29 12:48:51 -04:00
Zanie Blue
614a19cb4e Remove unused black compatibility tests (#7712)
Previously attempted to repair these tests at
https://github.com/astral-sh/ruff/pull/6992 but I don't think we should
prioritize that and instead I would like to remove this dead code.
2023-09-29 10:31:54 -05:00
Micha Reiser
e2ec42539b Attach dangling comments to the comprehension instead of the if or iter nodes (#7693) 2023-09-29 10:45:01 +01:00
Dhruv Manilawala
e62e245c61 Add support for PEP 701 (#7376)
## Summary

This PR adds support for PEP 701 in Ruff. This is a rollup PR of all the
other individual PRs. The separate PRs were created for logic separation
and code reviews. Refer to each pull request for a detail description on
the change.

Refer to the PR description for the list of pull requests within this PR.

## Test Plan

### Formatter ecosystem checks

Explanation for the change in ecosystem check:
https://github.com/astral-sh/ruff/pull/7597#issue-1908878183

#### `main`

```
| project      | similarity index  | total files       | changed files     |
|--------------|------------------:|------------------:|------------------:|
| cpython      |           0.76083 |              1789 |              1631 |
| django       |           0.99983 |              2760 |                36 |
| transformers |           0.99963 |              2587 |               319 |
| twine        |           1.00000 |                33 |                 0 |
| typeshed     |           0.99983 |              3496 |                18 |
| warehouse    |           0.99967 |               648 |                15 |
| zulip        |           0.99972 |              1437 |                21 |
```

#### `dhruv/pep-701`

```
| project      | similarity index  | total files       | changed files     |
|--------------|------------------:|------------------:|------------------:|
| cpython      |           0.76051 |              1789 |              1632 |
| django       |           0.99983 |              2760 |                36 |
| transformers |           0.99963 |              2587 |               319 |
| twine        |           1.00000 |                33 |                 0 |
| typeshed     |           0.99983 |              3496 |                18 |
| warehouse    |           0.99967 |               648 |                15 |
| zulip        |           0.99972 |              1437 |                21 |
```
2023-09-29 02:55:39 +00:00
Daniel Parizher
78b8741352 [refurb] Implement implicit-cwd (FURB177) (#7704)
## Summary

Implement
[`no-implicit-cwd`](https://github.com/dosisod/refurb/blob/master/docs/checks.md#furb177-no-implicit-cwd)
as `implicit-cwd`

Related to #1348.

## Test Plan

`cargo test`
2023-09-29 02:18:59 +00:00
Charlie Marsh
246d93ec37 Document single-specifier behavior in printf-string-formatting (#7705)
Closes https://github.com/astral-sh/ruff/issues/7579.
2023-09-29 01:55:11 +00:00
Tom Kuson
3347524164 Extend unnecessary-pass (PIE790) to trigger on all unnecessary pass statements (#7697)
## Summary

Extend `unnecessary-pass` (`PIE790`) to trigger on all unnecessary
`pass` statements by checking for `pass` statements in any class or
function body with more than one statement.

Closes #7600.

## Test Plan

`cargo test`
2023-09-29 01:39:11 +00:00
Mathieu Kniewallner
598974545b feat(rules): implement flake8-bandit S505 (#7703)
Part of #1646.

## Summary

Implement `S505`
([`weak_cryptographic_key`](https://bandit.readthedocs.io/en/latest/plugins/b505_weak_cryptographic_key.html))
rule from `bandit`.

For this rule, `bandit` [reports the issue
with](https://github.com/PyCQA/bandit/blob/1.7.5/bandit/plugins/weak_cryptographic_key.py#L47-L56):
- medium severity for DSA/RSA < 2048 bits and EC < 224 bits
- high severity for DSA/RSA < 1024 bits and EC < 160 bits

Since Ruff does not handle severities for `bandit`-related rules, we
could either report the issue if we have lower values than medium
severity, or lower values than high one. Two reasons led me to choose
the first option:
- a medium severity issue is still a security issue we would want to
report to the user, who can then decide to either handle the issue or
ignore it
- `bandit` [maps the EC key algorithms to their respective key lengths
in
bits](https://github.com/PyCQA/bandit/blob/1.7.5/bandit/plugins/weak_cryptographic_key.py#L112-L133),
but there is no value below 160 bits, so technically `bandit` would
never report medium severity issues for EC keys, only high ones

Another consideration is that as shared just above, for EC key
algorithms, `bandit` has a mapping to map the algorithms to their
respective key lengths. In the implementation in Ruff, I rather went
with an explicit list of EC algorithms known to be vulnerable (which
would thus be reported) rather than implementing a mapping to retrieve
the associated key length and comparing it with the minimum value.

## Test Plan

Snapshot tests from
https://github.com/PyCQA/bandit/blob/1.7.5/examples/weak_cryptographic_key_sizes.py.
2023-09-28 21:27:37 -04:00
Tom Kuson
c2a9cf8ae5 Ignore TODO tags in commented-out-code (#7523)
## Summary

Extend the `task-tags` checking logic to ignore TODO tags (with or
without parentheses). For example,

```python
# TODO(tjkuson): Rewrite in Rust
```

is no longer flagged as commented-out code.

Closes #7031.

I also updated the documentation to inform users that the rule is prone
to false positives like this!

EDIT: Accidentally linked to the wrong issue when first opening this PR,
now corrected.

## Test Plan

`cargo test`
2023-09-28 23:13:11 +00:00
Mathieu Kniewallner
cfbebcf354 fix(rules): improve S507 detection (#7661)
## Summary

Follow-up on https://github.com/astral-sh/ruff/pull/7528 that improves
detections of mis-usages of policy in `paramiko`.

First commit applies the same fix as in `bandit`
(https://github.com/PyCQA/bandit/pull/1064), as `paramiko` supports
passing both a class and a class instance for the policy in
`set_missing_host_key_policy`
(8e389c7766/paramiko/client.py (L171-L191)).

Second commit improve the detection of `paramiko` import paths that
trigger a violation, as `AutoAddPolicy`, `WarningPolicy` and `SSHClient`
are not only exposed in `paramiko.client`, but also in `paramiko`
(66117732de/paramiko/__init__.py (L121-L164)).

## Test Plan

Snapshot tests.
2023-09-28 21:35:59 +00:00
Charlie Marsh
5e75467757 Insert necessary padding in B014 fixes (#7699)
See:
https://github.com/astral-sh/ruff/issues/7455#issuecomment-1739801758.
2023-09-28 21:11:09 +00:00
Charlie Marsh
9611f8134f Parenthesize multi-line attributes in B009 (#7701)
Closes
https://github.com/astral-sh/ruff/issues/7455#issuecomment-1739800901.
2023-09-28 16:59:00 -04:00
Charlie Marsh
f45281345d Include radix base prefix in large number representation (#7700)
## Summary

When lexing a number like `0x995DC9BBDF1939FA` that exceeds our small
number representation, we were only storing the portion after the base
(in this case, `995DC9BBDF1939FA`). When using that representation in
code generation, this could lead to invalid syntax, since
`995DC9BBDF1939FA)` on its own is not a valid integer.

This PR modifies the code to store the full span, including the radix
prefix.

See:
https://github.com/astral-sh/ruff/issues/7455#issuecomment-1739802958.

## Test Plan

`cargo test`
2023-09-28 20:38:06 +00:00
Zanie Blue
316f75987d Add explicit-preview-rules to toggle explicit selection of preview rules (#7390)
Closes #7434 

Replaces the `PREVIEW` selector (removed in #7389) with a configuration
option `explicit-preview-rules` which requires selectors to use exact
rule codes for all preview rules. This allows users to enable preview
without opting into all preview rules at once.

## Test plan

Unit tests
2023-09-28 15:00:33 -05:00
Charlie Marsh
695dbbc539 Always prefer double quotes for docstrings and triple-quoted srings (#7680)
## Summary

At present, `quote-style` is used universally. However, [PEP
8](https://peps.python.org/pep-0008/) and [PEP
257](https://peps.python.org/pep-0257/) suggest that while either single
or double quotes are acceptable in general (as long as they're
consistent), docstrings and triple-quoted strings should always use
double quotes. In our research, the vast majority of Ruff users that
enable the `flake8-quotes` rules only enable them for inline strings
(i.e., non-triple-quoted strings).

Additionally, many Black forks (like Blue and Pyink) use double quotes
for docstrings and triple-quoted strings.

Our decision for now is to always prefer double quotes for triple-quoted
strings (which should include docstrings). Based on feedback, we may
consider adding additional options (e.g., a `"preserve"` mode, to avoid
changing quotes; or a `"multiline-quote-style"` to override this).

Closes https://github.com/astral-sh/ruff/issues/7615.

## Test Plan

`cargo test`
2023-09-28 15:11:33 -04:00
Charlie Marsh
f62b4c801f Extend pragma comment cases (#7687)
## Summary

Extends the pragma comment detection in the formatter to support
case-insensitive `noqa` (as supposed by Ruff), plus a variety of other
pragmas (`isort:`, `nosec`, etc.).

Also extracts the detection out into the trivia crate so that we can
reuse it in the linter (see:
https://github.com/astral-sh/ruff/issues/7471).

## Test Plan

`cargo test`
2023-09-28 18:55:19 +00:00
Charlie Marsh
46b85ab0a9 Misc. follow-ups to single-element tuple patterns (#7698)
Just changes to internal comments and tests.

See comments in https://github.com/astral-sh/ruff/pull/7683.
2023-09-28 18:49:13 +00:00
Charlie Marsh
1c02fcd7ce Avoid unnecessary comments check in maybe_parenthesize_expression (#7686)
## Summary

No-op refactor, but we can evaluate early if the first part of
`preserve_parentheses || has_comments` is `true`, and thus avoid looking
up the node comments.

## Test Plan

`cargo test`
2023-09-28 13:42:12 -04:00
Micha Reiser
f53c410ff8 Prefer preserving WithItem parentheses (#7694) 2023-09-28 14:42:40 +01:00
konsti
1e173f7909 Rename Autofix to Fix (#7657)
**Summary** Mostly mechanical symbol rename and search-and-replace, with
small changes to the markdown docs to read better
2023-09-28 10:53:05 +00:00
Charlie Marsh
8028de8956 Improve some comments in normalize_comment (#7688) 2023-09-28 03:08:25 +00:00
Charlie Marsh
a6d79c03b3 Break with on end-of-line trailing comments (#7685)
## Summary

Ensures that:

```python
with (
    a  # comment
):
    pass
```

Retains its parentheses.

Closes https://github.com/astral-sh/ruff/issues/6750.

## Test Plan

`cargo test`
2023-09-28 00:16:40 +00:00
Charlie Marsh
58b50a6290 Avoid expanding single-element tuple patterns (#7683)
## Summary

The formatting for tuple patterns is now intended to match that of `for`
loops:

- Always parenthesize single-element tuples.
- Don't break on the trailing comma in single-element tuples.
- For other tuples, preserve the parentheses, and insert if-breaks.

Closes https://github.com/astral-sh/ruff/issues/7681.

## Test Plan

`cargo test`
2023-09-27 23:57:18 +00:00
qdegraaf
c8360a1333 Expand DeprecatedLogWarn to check for Expr::Atrribute calls (#7677)
## Summary

`PGH002`, which checks for use of deprecated `logging.warn` calls, did
not check for calls made on the attribute `warn` yet. Since
https://github.com/astral-sh/ruff/pull/7521 we check both cases for
similar rules wherever possible. To be consistent this PR expands PGH002
to do the same.

## Test Plan

Expanded existing fixtures with `logger.warn()` calls

## Issue links

Fixes final inconsistency mentioned in
https://github.com/astral-sh/ruff/issues/7502
2023-09-27 11:38:52 -04:00
qdegraaf
34480c0e4d chore: remove redundant Expr::Call checks (#7678)
## Summary

As we bind the `ast::ExprCall` in the big `match expr` in
`expression.rs`
```rust
Expr::Call(
    call @ ast::ExprCall {
     ...
```

There is no need for additional `let/if let` checks on `ExprCall` in
downstream rules. Found a few older rules which still did this while
working on something else. This PR removes the redundant check from
these rules.

## Test Plan

`cargo test`
2023-09-27 11:36:20 -04:00
Zanie Blue
70ab4b8b59 Add nursery documentation note to preview section (#7671)
Ref
https://github.com/astral-sh/ruff/issues/7491#issuecomment-1730016523

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2023-09-27 14:46:57 +00:00
Micha Reiser
e863fa55cb Rename ConfigurationOptions derive macro to OptionsMetadata
## Summary

It's common practice to name derive macros the same as the trait that they implement (`Debug`, `Display`, `Eq`, `Serialize`, ...). 

This PR renames the `ConfigurationOptions` derive macro to `OptionsMetadata` to match the trait name.

## Test Plan

`cargo build`
2023-09-27 09:04:26 +02:00
Micha Reiser
0c65d0c8a6 Add lint section to Ruff configuration
## Summary

This PR adds a new `lint` section to the configuration that groups all linter-specific settings. The existing top-level configurations continue to work without any warning because the `lint.*` settings are experimental. 

The configuration merges the top level and `lint.*` settings where the settings in `lint` have higher precedence (override the top-level settings). The reasoning behind this is that the settings in `lint.` are more specific and more specific settings should override less specific settings.

I decided against showing the new `lint.*` options on our website because it would make the page extremely long (it's technically easy to do, just attribute `lint` with `[option_group`]). We may want to explore adding an `alias` field to the `option` attribute and show the alias on the website along with its regular name. 

## Test Plan

* I added new integration tests
* I verified that the generated `options.md` is identical
* Verified the default settings in the playground

![Screenshot from 2023-09-22 13-52-23](https://github.com/astral-sh/ruff/assets/1203881/7b4d9689-aa88-402e-9199-9c43c8d8cc2d)
2023-09-27 08:46:27 +02:00
Charlie Marsh
15f3d8c8e0 Improvement documentation around ignore-names globbing (#7674)
## Summary

Improves the documentation on the setting itself, along with that on the
relevant rules.

Closes https://github.com/astral-sh/ruff/issues/7660.
2023-09-27 04:49:41 +00:00
Charlie Marsh
0a8cad2550 Allow named expressions in __all__ assignments (#7673)
## Summary

This PR adds support for named expressions when analyzing `__all__`
assignments, as per https://github.com/astral-sh/ruff/issues/7672. It
also loosens the enforcement around assignments like: `__all__ =
list(some_other_expression)`. We shouldn't flag these as invalid, even
though we can't analyze the members, since we _know_ they evaluate to a
`list`.

Closes https://github.com/astral-sh/ruff/issues/7672.

## Test Plan

`cargo test`
2023-09-27 00:36:55 -04:00
Simon Høxbro Hansen
fbbc982c29 Ensure that B006 autofixes are inserted after imports (#7629)
## Summary

Fixes #7616 by ensuring that
[B006](https://docs.astral.sh/ruff/rules/mutable-argument-default/#mutable-argument-default-b006)
fixes are inserted after module imports.

I have created a new test file, `B006_5.py`. This is mainly because I
have been working on this on and off, and the merge conflicts were
easier to handle in a separate file. If needed, I can move it into
another file.

## Test Plan

`cargo test`
2023-09-27 01:26:29 +00:00
qdegraaf
2aef46cb6f Add Expr::Name checks to rules which use is_logger_candidate (#7521)
## Summary

Expands several rules to also check for `Expr::Name` values. As they
would previously not consider:
```python
from logging import error

error("foo")
```
as potential violations
```python
import logging

logging.error("foo")
```
as potential violations leading to inconsistent behaviour. 

The rules impacted are:

- `BLE001`
- `TRY400`
- `TRY401`
- `PLE1205`
- `PLE1206`
- `LOG007`
- `G001`-`G004`
- `G101`
- `G201`
- `G202`

## Test Plan

Fixtures for all impacted rules expanded. 

## Issue Link

Refers: https://github.com/astral-sh/ruff/issues/7502
2023-09-27 00:21:22 +00:00
Zanie Blue
528f386131 Show preview notice on rule pages for nursery rules (#7670)
<!--
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

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

The note about rules being in preview was not being displayed for legacy
nursery rules.

Adds a link to the new preview documentation as well.

## Test Plan

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

Built locally and checked a nursery rule e.g.
http://127.0.0.1:8000/ruff/rules/no-indented-block-comment/
2023-09-26 17:53:29 -05:00
bluthej
ee533332ed Refactor: use Settings struct (#7665)
## Summary

Pass around a `Settings` struct instead of individual members to
simplify function signatures and to make it easier to add new settings.

This PR was suggested in [this
comment](https://github.com/astral-sh/ruff/issues/1567#issuecomment-1734182803).

## Note on the choices

I chose which functions to modify based on which seem most likely to use
new settings, but suggestions on my choices are welcome!
2023-09-26 12:17:18 -05:00
Dhruv Manilawala
8165925e01 Use 1-based cell indices consistently for Notebooks (#7662)
## Summary

This PR fixes the bug where the cell indices displayed in the `--diff` output
and the ones in the normal output were different. This was due to the fact that
the `--diff` output was using the `enumerate` function to iterate over
the cells which starts at 0.

## Test Plan

Ran the following command with and without the `--diff` flag:

```console
cargo run --bin ruff -- check --no-cache --isolated ~/playground/ruff/notebooks/test.ipynb
```

### `main`

<details><summary>Diagnostics output:</summary>
<p>

```console
$ cargo run --bin ruff -- check --no-cache --isolated ~/playground/ruff/notebooks/test.ipynb       
/Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 3:2:8: F401 [*] `math` imported but unused
/Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 5:1:8: F811 Redefinition of unused `random` from line 1
/Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 5:2:8: F401 [*] `pprint` imported but unused
/Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 6:2:4: F632 [*] Use `==` to compare constant literals
/Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 6:3:38: F632 [*] Use `==` to compare constant literals
Found 5 errors.
[*] 4 potentially fixable with the --fix option.
```

</p>
</details>

<details><summary>Diff output:</summary>
<p>

```console
$ cargo run --bin ruff -- check --no-cache --isolated ~/playground/ruff/notebooks/test.ipynb --diff
--- /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 2
+++ /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 2
@@ -1,2 +1 @@
-import random
-import math
+import random
--- /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 4
+++ /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 4
@@ -1,4 +1,3 @@
 import random
-import pprint
 
 random.randint(10, 20)
--- /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 5
+++ /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 5
@@ -1,3 +1,3 @@
 foo = 1
-if foo is 2:
-    raise ValueError(f"Invalid foo: {foo is 1}")
+if foo == 2:
+    raise ValueError(f"Invalid foo: {foo == 1}")

Would fix 4 errors.
```

</p>
</details> 

### `dhruv/consistent-cell-indices`

<details><summary>Diagnostic output:</summary>
<p>

```console
$ cargo run --bin ruff -- check --no-cache --isolated ~/playground/ruff/notebooks/test.ipynb           
/Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 3:2:8: F401 [*] `math` imported but unused
/Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 5:1:8: F811 Redefinition of unused `random` from line 1
/Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 5:2:8: F401 [*] `pprint` imported but unused
/Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 6:2:4: F632 [*] Use `==` to compare constant literals
/Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 6:3:38: F632 [*] Use `==` to compare constant literals
Found 5 errors.
[*] 4 potentially fixable with the --fix option.
```

</p>
</details> 

<details><summary>Diff output:</summary>
<p>

```console
$ cargo run --bin ruff -- check --no-cache --isolated ~/playground/ruff/notebooks/test.ipynb --diff
--- /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 3
+++ /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 3
@@ -1,2 +1 @@
-import random
-import math
+import random
--- /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 5
+++ /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 5
@@ -1,4 +1,3 @@
 import random
-import pprint
 
 random.randint(10, 20)
--- /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 6
+++ /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 6
@@ -1,3 +1,3 @@
 foo = 1
-if foo is 2:
-    raise ValueError(f"Invalid foo: {foo is 1}")
+if foo == 2:
+    raise ValueError(f"Invalid foo: {foo == 1}")

Would fix 4 errors.
```

</p>
</details> 

fixes: #6673
2023-09-26 19:43:59 +05:30
Zanie Blue
0a737843b5 Increase dependabot check interval from daily to weekly (#7668)
To reduce the noise of updates.

May follow-up with grouping per
https://github.blog/changelog/2023-06-30-grouped-version-updates-for-dependabot-public-beta/
2023-09-26 13:59:23 +00:00
konsti
4d16e2308d Formatter and parser refactoring (#7569)
I got confused and refactored a bit, now the naming should be more
consistent. This is the basis for the range formatting work.

Chages:
* `format_module` -> `format_module_source` (format a string)
* `format_node` -> `format_module_ast` (format a program parsed into an
AST)
* Added `parse_ok_tokens` that takes `Token` instead of `Result<Token>`
* Call the source code `source` consistently
* Added a `tokens_and_ranges` helper
* `python_ast` -> `module` (because that's the type)
2023-09-26 15:29:43 +02:00
dependabot[bot]
2cb5e43dd7 Bump clap from 4.4.4 to 4.4.5 (#7666)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-26 13:02:16 +02:00
konsti
26f9b4a8e6 Don't suggest replacing builtin.open() with Path.open() if the latter doesn't support all options (#7637)
**Summary** Check that `closefd` and `opener` aren't being used with
`builtin.open()` before suggesting `Path.open()` because pathlib doesn't
support these arguments.

Closes #7620

**Test Plan** New cases in the fixture.
2023-09-26 09:07:35 +00:00
Charlie Marsh
93b5d8a0fb Implement our own small-integer optimization (#7584)
## Summary

This is a follow-up to #7469 that attempts to achieve similar gains, but
without introducing malachite. Instead, this PR removes the `BigInt`
type altogether, instead opting for a simple enum that allows us to
store small integers directly and only allocate for values greater than
`i64`:

```rust
/// A Python integer literal. Represents both small (fits in an `i64`) and large integers.
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Int(Number);

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Number {
    /// A "small" number that can be represented as an `i64`.
    Small(i64),
    /// A "large" number that cannot be represented as an `i64`.
    Big(Box<str>),
}

impl std::fmt::Display for Number {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Number::Small(value) => write!(f, "{value}"),
            Number::Big(value) => write!(f, "{value}"),
        }
    }
}
```

We typically don't care about numbers greater than `isize` -- our only
uses are comparisons against small constants (like `1`, `2`, `3`, etc.),
so there's no real loss of information, except in one or two rules where
we're now a little more conservative (with the worst-case being that we
don't flag, e.g., an `itertools.pairwise` that uses an extremely large
value for the slice start constant). For simplicity, a few diagnostics
now show a dedicated message when they see integers that are out of the
supported range (e.g., `outdated-version-block`).

An additional benefit here is that we get to remove a few dependencies,
especially `num-bigint`.

## Test Plan

`cargo test`
2023-09-25 15:13:21 +00:00
Charlie Marsh
65aebf127a Treat form feed as whitespace in SimpleTokenizer (#7626)
## Summary

This is whitespace as per `is_python_whitespace`, and right now it tends
to lead to panics in the formatter. Seems reasonable to treat it as
whitespace in the `SimpleTokenizer` too.

Closes .https://github.com/astral-sh/ruff/issues/7624.
2023-09-25 14:34:59 +00:00
Charlie Marsh
17ceb5dcb3 Preserve newlines after nested compound statements (#7608)
## Summary

Given:
```python
if True:
    if True:
        pass
    else:
        pass
        # a

        # b
        # c

else:
    pass
```

We want to preserve the newline after the `# c` (before the `else`).
However, the `last_node` ends at the `pass`, and the comments are
trailing comments on the `pass`, not trailing comments on the
`last_node` (the `if`). As such, when counting the trailing newlines on
the outer `if`, we abort as soon as we see the comment (`# a`).

This PR changes the logic to skip _all_ comments (even those with
newlines between them). This is safe as we know that there are no
"leading" comments on the `else`, so there's no risk of skipping those
accidentally.

Closes https://github.com/astral-sh/ruff/issues/7602.

## Test Plan

No change in compatibility.

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1631 |
| django | 0.99983 | 2760 | 36 |
| transformers | 0.99963 | 2587 | 319 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99979 | 3496 | 22 |
| warehouse | 0.99967 | 648 | 15 |
| zulip | 0.99972 | 1437 | 21 |

After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1631 |
| django | 0.99983 | 2760 | 36 |
| transformers | 0.99963 | 2587 | 319 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99967 | 648 | 15 |
| zulip | 0.99972 | 1437 | 21 |
2023-09-25 14:21:44 +00:00
Micha Reiser
8ce138760a Emit LexError for dedent to incorrect level (#7638) 2023-09-25 11:45:44 +01:00
dependabot[bot]
10e35e38d7 Bump semver from 1.0.18 to 1.0.19 (#7641)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 09:09:17 +00:00
dependabot[bot]
f169cb5d92 Bump wild from 2.1.0 to 2.2.0 (#7640)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 09:06:10 +00:00
Charlie Marsh
39ddad7454 Refactor FURB105 into explicit cases (#7634)
## Summary

I was having trouble keeping track of the various cases here, so opted
to refactor into a more explicit `match`.
2023-09-24 18:46:09 +00:00
Charlie Marsh
f32b0eef9c Flag FURB105 with starred kwargs (#7630) 2023-09-24 14:28:20 +00:00
Dhruv Manilawala
15813a65f3 Update return type for PT022 autofix (#7613)
## Summary

This PR fixes the autofix behavior for `PT022` to create an additional
edit for the return type if it's present. The edit will update the
return type from `Generator[T, ...]` to `T`. As per the [official
documentation](https://docs.python.org/3/library/typing.html?highlight=typing%20generator#typing.Generator),
the first position is the yield type, so we can ignore other positions.

```python
typing.Generator[YieldType, SendType, ReturnType]
```

## Test Plan

Add new test cases, `cargo test` and review the snapshots.

fixes: #7610
2023-09-24 06:39:47 +00:00
Tom Kuson
604cf521b5 [refurb] Implement print-empty-string (FURB105) (#7617)
## Summary

Implement
[`simplify-print`](https://github.com/dosisod/refurb/blob/master/refurb/checks/builtin/print.py)
as `print-empty-string` (`FURB105`).

Extends the original rule in that it also checks for multiple empty
string positional arguments with an empty string separator.

Related to #1348.

## Test Plan

`cargo test`
2023-09-24 04:10:36 +00:00
Charlie Marsh
865c89800e Avoid searching for bracketed comments in unparenthesized generators (#7627)
Similar to tuples, a generator _can_ be parenthesized or
unparenthesized. Only search for bracketed comments if it contains its
own parentheses.

Closes https://github.com/astral-sh/ruff/issues/7623.
2023-09-24 02:08:44 +00:00
Charlie Marsh
1a22eae98c Use deletion for D215 full-line removals (#7625)
Closes https://github.com/astral-sh/ruff/issues/7619.
2023-09-23 22:44:55 +00:00
Charlie Marsh
8ba8896a7f Skip BOM when inserting start-of-file imports (#7622)
See:
https://github.com/astral-sh/ruff/issues/7455#issuecomment-1732387485.
2023-09-23 19:36:50 +00:00
Charlie Marsh
b194f59aab Avoid flagging B009 and B010 on starred expressions (#7621)
See:
https://github.com/astral-sh/ruff/issues/7455#issuecomment-1732387247.
2023-09-23 19:08:19 +00:00
Chammika Mannakkara
e41b08f1d0 Fix typo in infinite (#7614) 2023-09-23 11:19:36 +00:00
Charlie Marsh
1a4f2a9baf Avoid reordering mixed-indent-level comments after branches (#7609)
## Summary

Given:

```python
if True:
    if True:
        if True:
            pass

        #a
            #b
        #c
else:
    pass
```

When determining the placement of the various comments, we compute the
indentation depth of each comment, and then compare it to the depth of
the previous statement. It turns out this can lead to reordering
comments, e.g., above, `#b` is assigned as a trailing comment of `pass`,
and so gets reordered above `#a`.

This PR modifies the logic such that when we compute the indentation
depth of `#b`, we limit it to at most the indentation depth of `#a`. In
other words, when analyzing comments at the end of branches, we don't
let successive comments go any _deeper_ than their preceding comments.

Closes https://github.com/astral-sh/ruff/issues/7602.

## Test Plan

`cargo test`

No change in similarity.

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1631 |
| django | 0.99983 | 2760 | 36 |
| transformers | 0.99963 | 2587 | 319 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99979 | 3496 | 22 |
| warehouse | 0.99967 | 648 | 15 |
| zulip | 0.99972 | 1437 | 21 |

After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1631 |
| django | 0.99983 | 2760 | 36 |
| transformers | 0.99963 | 2587 | 319 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99979 | 3496 | 22 |
| warehouse | 0.99967 | 648 | 15 |
| zulip | 0.99972 | 1437 | 21 |
2023-09-22 18:12:31 -04:00
konsti
19010f276e Fix line ending doc typo (#7611)
Fixes https://docs.astral.sh/ruff/settings/#format-quote-style
2023-09-22 20:16:41 +00:00
Charlie Marsh
5174e8c926 Ignore blank lines between comments when counting newlines-after-imports (#7607)
## Summary

Given:

```python
# -*- coding: utf-8 -*-
import random

# Defaults for arguments are defined here
# args.threshold = None;


logger = logging.getLogger("FastProject")
```

We want to count the number of newlines after `import random`, to ensure
that there's _at least one_, but up to two.

Previously, we used the end range of the statement (then skipped
trivia); instead, we need to use the end of the _last comment_. This is
similar to #7556.

Closes https://github.com/astral-sh/ruff/issues/7604.
2023-09-22 17:49:39 +00:00
Charlie Marsh
8bfe9bda41 Bump version to v0.0.291 (#7606) 2023-09-22 13:25:37 -04:00
Micha Reiser
01843af21a Support option group documentation (#7593) 2023-09-22 16:31:52 +00:00
Micha Reiser
2ecf59726f Refactor Options representation (#7591) 2023-09-22 18:19:58 +02:00
Charlie Marsh
f137819536 Improve B005 documentation to reflect duplicate-character behavior (#7601)
## Summary

B005 only flags `.strip()` calls for which the argument includes
duplicate characters. This is consistent with bugbear, but isn't
explained in the documentation.
2023-09-22 16:12:16 +00:00
Micha Reiser
9d16e46129 Add most formatter options to ruff.toml / pyproject.toml (#7566) 2023-09-22 15:47:57 +00:00
dependabot[bot]
82978ac9b5 Bump indicatif from 0.17.6 to 0.17.7 (#7592)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-22 08:54:48 +00:00
T-256
814403cdf7 Bump lint rules count to 700 (#7585) 2023-09-21 21:55:03 -04:00
Charlie Marsh
f254aaa847 Remove unwrap in os_error_alias.rs (#7583) 2023-09-21 21:19:32 +00:00
Charlie Marsh
a51b0b02f0 Treat os.error as an OSError alias (#7582)
Closes https://github.com/astral-sh/ruff/issues/7580.
2023-09-21 21:18:14 +00:00
Leiser Fernández Gallo
74dbd871f8 Make ruff format idempotent when using stdin input (#7581)
## Summary
Currently, this happens
```sh
$ echo "print()" | ruff format - 

#Notice that nothing went to stdout
```
Which does not match `ruff check --fix - ` behavior and deletes my code
every time I format it (more or less 5 times per minute 😄).

I just checked that my example works as the change was very
straightforward.
2023-09-21 16:50:23 -04:00
Charlie Marsh
d7508af48d Truncate to one empty line in stub files (#7558)
## Summary

This PR modifies a variety of sites in which we insert up to two empty
lines to instead truncate to at most one empty line in stub files. We
already enforce this in _some_ places, but not all.

## Test Plan

`cargo test`

No changes in similarity (as expected, since this only impacts
unformatted `.pyi` files).

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1631 |
| django | 0.99983 | 2760 | 36 |
| transformers | 0.99963 | 2587 | 323 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99979 | 3496 | 22 |
| warehouse | 0.99967 | 648 | 15 |
| zulip | 0.99972 | 1437 | 21 |

After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1631 |
| django | 0.99983 | 2760 | 36 |
| transformers | 0.99963 | 2587 | 323 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99979 | 3496 | 22 |
| warehouse | 0.99967 | 648 | 15 |
| zulip | 0.99972 | 1437 | 21 |
2023-09-21 16:24:42 -04:00
konsti
c3774e1255 Fix gitignore to not ignore files that are required (#7538)
It is apparently possible to add files to the git index, even if they
are part of the gitignore (see e.g.
https://stackoverflow.com/questions/45400361/why-is-gitignore-not-ignoring-my-files,
even though it's strange that the gitignore entries existed before the
files were added, i wouldn't know how to get them added in that case). I
ran
```
git rm -r --cached .
```
then change the gitignore not actually ignore those files with the
exception of
`crates/ruff_cli/resources/test/fixtures/cache_mutable/source.py`, which
is actually a generated file.
2023-09-21 21:33:09 +02:00
Charlie Marsh
887455c498 Use u8 to represent version segments (#7578) 2023-09-21 14:24:51 -04:00
Charlie Marsh
4d6f5ff0a7 Remove Int wrapper type from parser (#7577)
## Summary

This is only used for the `level` field in relative imports (e.g., `from
..foo import bar`). It seems unnecessary to use a wrapper here, so this
PR changes to a `u32` directly.
2023-09-21 17:01:44 +00:00
Micha Reiser
6c3378edb1 Fix the default indent style to tab (#7576) 2023-09-21 15:42:55 +00:00
Charlie Marsh
7f1456a2c9 Allow up to two newlines before trailing clause body comments (#7575)
## Summary

This is the peer to https://github.com/astral-sh/ruff/pull/7557, but for
"leading" clause comments, like:

```python
if True:
    pass


# comment
else:
    pass
```

In this case, we again want to allow up to two newlines at the top
level.

## Test Plan

`cargo test`

No changes.

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1631 |
| django | 0.99983 | 2760 | 36 |
| transformers | 0.99963 | 2587 | 323 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99979 | 3496 | 22 |
| warehouse | 0.99967 | 648 | 15 |
| zulip | 0.99972 | 1437 | 21 |

After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1631 |
| django | 0.99983 | 2760 | 36 |
| transformers | 0.99963 | 2587 | 323 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99979 | 3496 | 22 |
| warehouse | 0.99967 | 648 | 15 |
| zulip | 0.99972 | 1437 | 21 |
2023-09-21 14:52:38 +00:00
Charlie Marsh
2759db6604 Allow up to two newlines after trailing clause body comments (#7557)
## Summary

The number of newlines after a trailing comment in a clause body needs
to follow the usual rules -- so, up to two for top-level, up to one for
nested, etc.

For example, Black preserves both newlines after `# comment` here:

```python
if True:
    pass

    # comment


else:
    pass
```

But it truncates to one newline here:

```python
if True:
    if True:
        pass
        # comment


    else:
        pass
else:
    pass
```

## Test Plan

Significant improvement on `transformers`.

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1631 |
| django | 0.99983 | 2760 | 36 |
| transformers | 0.99957 | 2587 | 402 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99979 | 3496 | 22 |
| warehouse | 0.99967 | 648 | 15 |
| zulip | 0.99972 | 1437 | 21 |


After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1631 |
| django | 0.99983 | 2760 | 36 |
| **transformers** | **0.99963** | **2587** | **323** |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99979 | 3496 | 22 |
| warehouse | 0.99967 | 648 | 15 |
| zulip | 0.99972 | 1437 | 21 |
2023-09-21 14:04:49 +00:00
Charlie Marsh
124d95d246 Fix instability in trailing clause body comments (#7556)
## Summary

When we format the trailing comments on a clause body, we check if there
are any newlines after the last statement; if not, we insert one.

This logic didn't take into account that the last statement could itself
have trailing comments, as in:

```python
if True:
    pass

    # comment
else:
    pass
```

We were thus inserting a newline after the comment, like:

```python
if True:
    pass

    # comment

else:
    pass
```

In the context of function definitions, this led to an instability,
since we insert a newline _after_ a function, which would in turn lead
to the bug above appearing in the second formatting pass.

Closes https://github.com/astral-sh/ruff/issues/7465.

## Test Plan

`cargo test`

Small improvement in `transformers`, but no regressions.

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1631 |
| django | 0.99983 | 2760 | 36 |
| transformers | 0.99956 | 2587 | 404 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99967 | 648 | 15 |
| zulip | 0.99972 | 1437 | 21 |

After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1631 |
| django | 0.99983 | 2760 | 36 |
| **transformers** | **0.99957** | **2587** | **402** |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99967 | 648 | 15 |
| zulip | 0.99972 | 1437 | 21 |
2023-09-21 13:32:16 +00:00
dependabot[bot]
ab643017f9 Bump smallvec from 1.11.0 to 1.11.1 (#7563)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 11:53:27 +02:00
dependabot[bot]
6eb9a9a633 Bump insta from 1.31.0 to 1.32.0 (#7565)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 11:53:14 +02:00
dependabot[bot]
288c07d911 Bump rayon from 1.7.0 to 1.8.0 (#7564)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 11:52:41 +02:00
Micha Reiser
f8f1cd5016 Introduce FormatterSettings (#7545) 2023-09-21 08:01:24 +02:00
Charlie Marsh
87a0cd219f Detect asyncio.get_running_loop calls in RUF006 (#7562)
## Summary

We can do a good enough job detecting this with our existing semantic
model.

Closes https://github.com/astral-sh/ruff/issues/3237.
2023-09-21 04:37:38 +00:00
Charlie Marsh
b685ee4749 Enable tab completion for ruff rule (#7560)
## Summary

Writing `ruff rule E1`, then tab, shows:

<img width="724" alt="Screen Shot 2023-09-20 at 9 29 09 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/00297f24-8828-4485-a00e-6af1ab4e7875">

Closes https://github.com/astral-sh/ruff/issues/2812.
2023-09-20 22:05:39 -04:00
Charlie Marsh
ad893f8295 Avoid invalid fix for parenthesized values in F601 (#7559)
Closes https://github.com/astral-sh/ruff/issues/4897.
2023-09-21 01:28:11 +00:00
Charlie Marsh
621bed55c0 Add padding in PERF102 fixes (#7554)
Closes https://github.com/astral-sh/ruff/issues/7097.
2023-09-20 19:33:54 -04:00
Charlie Marsh
86faee1522 Update CONTRIBUTING.md to reflect Settings refactor (#7555)
## Summary

See:
https://github.com/astral-sh/ruff/pull/7544#issuecomment-1728457885.
2023-09-20 19:33:33 -04:00
Charlie Marsh
a0917ec658 Avoid inserting imports directly after continuation (#7553)
## Summary

This is extremely rare in practice, but common in the fuzzer issues so
worth fixing quickly.

Closes https://github.com/astral-sh/ruff/issues/7199.
2023-09-20 21:26:48 +00:00
Mahesh Saripalli
ee9ee005c5 Update flake8-self license to MIT (#7552)
## Summary

This PR updates the flake8-self license from Freely Distributable to the
MIT License to match the upstream license, which was recently updated.


f5fc507515/LICENSE
2023-09-20 17:17:55 -04:00
Charlie Marsh
5df0326bc8 Treat parameters-with-newline as empty in function formatting (#7550)
## Summary

If a function has no parameters (and no comments within the parameters'
`()`), we're supposed to wrap the return annotation _whenever_ it
breaks. However, our `empty_parameters` test didn't properly account for
the case in which the parameters include a newline (but no other
content), like:

```python
def get_dashboards_hierarchy(
) -> Dict[Type['BaseDashboard'], List[Type['BaseDashboard']]]:
    """Get hierarchy of dashboards classes.

    Returns:
        Dict of dashboards classes.
    """
    dashboards_hierarchy = {}
```

This PR fixes that detection. Instead of lexing, it now checks if the
parameters itself is empty (or if it contains comments).

Closes https://github.com/astral-sh/ruff/issues/7457.
2023-09-20 16:20:22 -04:00
Micha Reiser
6540321966 Move Settings and ResolverSettings to ruff_workspace
## Summary

## Stack Summary

This stack splits `Settings` into `FormatterSettings` and `LinterSettings` and moves it into `ruff_workspace`. This change is necessary to add the `FormatterSettings` to `Settings` without adding `ruff_python_formatter` as a dependency to `ruff_linter` (and the linter should not contain the formatter settings). 

A quick overview of our settings struct at play:

* `Options`: 1:1 representation of the options in the `pyproject.toml` or `ruff.toml`.  Used for deserialization.
* `Configuration`: Resolved `Options`, potentially merged from multiple configurations (when using `extend`). The representation is very close if not identical to the `Options`.
* `Settings`: The resolved configuration that uses a data format optimized for reading. Optional fields are initialized with their default values. Initialized by `Configuration::into_settings` .

The goal of this stack is to split `Settings` into tool-specific resolved `Settings` that are independent of each other. This comes at the advantage that the individual crates don't need to know anything about the other tools. The downside is that information gets duplicated between `Settings`. Right now the duplication is minimal (`line-length`, `tab-width`) but we may need to come up with a solution if more expensive data needs sharing.

This stack focuses on `Settings`. Splitting `Configuration` into some smaller structs is something I'll follow up on later. 

## PR Summary

This PR moves the `ResolverSettings` and `Settings` struct to `ruff_workspace`. `LinterSettings` remains in `ruff_linter` because it gets passed to lint rules, the `Checker` etc.

## Test Plan

`cargo test`
2023-09-20 17:24:28 +02:00
Micha Reiser
b34278e0cd Introduce LinterSettings
## Stack Summary

This stack splits `Settings` into `FormatterSettings` and `LinterSettings` and moves it into `ruff_workspace`. This change is necessary to add the `FormatterSettings` to `Settings` without adding `ruff_python_formatter` as a dependency to `ruff_linter` (and the linter should not contain the formatter settings). 

A quick overview of our settings struct at play:

* `Options`: 1:1 representation of the options in the `pyproject.toml` or `ruff.toml`.  Used for deserialization.
* `Configuration`: Resolved `Options`, potentially merged from multiple configurations (when using `extend`). The representation is very close if not identical to the `Options`.
* `Settings`: The resolved configuration that uses a data format optimized for reading. Optional fields are initialized with their default values. Initialized by `Configuration::into_settings` .

The goal of this stack is to split `Settings` into tool-specific resolved `Settings` that are independent of each other. This comes at the advantage that the individual crates don't need to know anything about the other tools. The downside is that information gets duplicated between `Settings`. Right now the duplication is minimal (`line-length`, `tab-width`) but we may need to come up with a solution if more expensive data needs sharing.

This stack focuses on `Settings`. Splitting `Configuration` into some smaller structs is something I'll follow up on later. 

## PR Summary

This PR extracts the linter-specific settings into a new `LinterSettings` struct and adds it as a `linter` field to the `Settings` struct. This is in preparation for moving `Settings` from `ruff_linter` to `ruff_workspace`

## Test Plan

`cargo test`
2023-09-20 17:02:34 +02:00
Micha Reiser
222f1c37b8 Add empty lines between options and move documents (#7547) 2023-09-20 16:40:25 +02:00
Micha Reiser
8f41eab0c7 Extract ResolverSettings
## Stack Summary

This stack splits `Settings` into `FormatterSettings` and `LinterSettings` and moves it into `ruff_workspace`. This change is necessary to add the `FormatterSettings` to `Settings` without adding `ruff_python_formatter` as a dependency to `ruff_linter` (and the linter should not contain the formatter settings). 

A quick overview of our settings struct at play:

* `Options`: 1:1 representation of the options in the `pyproject.toml` or `ruff.toml`.  Used for deserialization.
* `Configuration`: Resolved `Options`, potentially merged from multiple configurations (when using `extend`). The representation is very close if not identical to the `Options`.
* `Settings`: The resolved configuration that uses a data format optimized for reading. Optional fields are initialized with their default values. Initialized by `Configuration::into_settings` .

The goal of this stack is to split `Settings` into tool-specific resolved `Settings` that are independent of each other. This comes at the advantage that the individual crates don't need to know anything about the other tools. The downside is that information gets duplicated between `Settings`. Right now the duplication is minimal (`line-length`, `tab-width`) but we may need to come up with a solution if more expensive data needs sharing.

This stack focuses on `Settings`. Splitting `Configuration` into some smaller structs is something I'll follow up on later. 

## PR Summary

This PR extracts a `ResolverSettings` struct that holds all the resolver-relevant fields (uninteresting for the `Formatter` or `Linter`). This will allow us to move the `ResolverSettings` out of `ruff_linter` further up in the stack.


## Test Plan

`cargo test`

(I'll to more extensive testing at the top of this stack)
2023-09-20 16:37:49 +02:00
Micha Reiser
83daddbeb7 Rename ConfigProcessor to ConfigurationTransformer (#7536) 2023-09-20 14:17:06 +00:00
Micha Reiser
b19eec9b2a Unify Settings and AllSettings (#7532) 2023-09-20 13:56:07 +00:00
Micha Reiser
ca3c15858d Use portable hash function for CacheKey (#7533) 2023-09-20 15:19:59 +02:00
Micha Reiser
bb4f7c681a Rename format option to output-format (#7514) 2023-09-20 15:18:58 +02:00
Dhruv Manilawala
0a167dd20b Use strict sorted and union for NoQA mapping insertion (#7531)
## Summary

This PR fixes the way NoQA range is inserted to the `NoqaMapping`.

Previously, the way the mapping insertion logic worked was as follows:
1. If the range which is to be inserted _touched_ the previous range, meaning
that the end of the previous range was the same as the start of the new
range, then the new range was added in addition to the previous range.
2. Else, if the new range intersected the previous range, then the previous
   range was replaced with the new _intersection_ of the two ranges.

The problem with this logic is that it does not work for the following case:
```python
assert foo, \
    """multi-line
    string"""
```

Now, the comments cannot be added to the same line which ends with a continuation
character. So, the `NoQA` directive has to be added to the next line. But, the
next line is also a triple-quoted string, so the `NoQA` directive for that line
needs to be added to the next line. This creates a **union** pattern instead of an
**intersection** pattern.

But, only union doesn't suffice because (1) means that for the edge case where
the range touch only at the end, the union won't take place.

### Solution

1. Replace '<=' with '<' to have a _strict_ insertion case
2. Use union instead of intersection

## Test Plan

Add a new test case. Run the test suite to ensure that nothing is broken.

### Integration

1. Make a `test.py` file with the following contents:

    ```python
    assert foo, \
        """multi-line
        string"""
    ```

2. Run the following command:

    ```console
	$ cargo run --bin ruff -- check --isolated --no-cache --select=F821 test.py
	/Users/dhruv/playground/ruff/fstring.py:1:8: F821 Undefined name `foo`
    Found 1 error.
    ```

3. Use `--add-noqa`:

    ```console
	$ cargo run --bin ruff -- check --isolated --no-cache --select=F821 --add-noqa test.py
    Added 1 noqa directive.
    ```

4. Check that the NoQA directive was added in the correct position:

    ```python
    assert foo, \
        """multi-line
        string"""  # noqa: F821
    ```

5. Run the `check` command to ensure that the NoQA directive is respected:

    ```console
	$ cargo run --bin ruff -- check --isolated --no-cache --select=F821 test.py
    ```

fixes: #7530
2023-09-20 11:07:19 +00:00
dependabot[bot]
c43542896f Bump unicode-width from 0.1.10 to 0.1.11 (#7534)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-20 10:26:54 +02:00
Micha Reiser
192463c2fb Allow parenthesized content exceed configured line width (#7490) 2023-09-20 08:39:25 +02:00
Charlie Marsh
5849a75223 Rename ruff crate to ruff_linter (#7529) 2023-09-20 08:38:27 +02:00
Mathieu Kniewallner
dcbd8eacd8 feat(rules): implement flake8-bandit S507 (ssh_no_host_key_verification) (#7528)
Part of #1646.

## Summary

Implement `S507`
([`ssh_no_host_key_verification`](https://bandit.readthedocs.io/en/latest/plugins/b507_ssh_no_host_key_verification.html))
rule from `bandit`.

## Test Plan

Snapshot test from
https://github.com/PyCQA/bandit/blob/1.7.5/examples/no_host_key_verification.py,
with several additions to test for more cases (most notably passing the
parameter as a named argument).
2023-09-19 20:31:45 -04:00
Micha Reiser
297ec2c2d2 Use Settings where AllSettings isn't required (#7518) 2023-09-19 23:16:20 +02:00
Tom Kuson
511cc25fc4 [refurb] Implement unnecessary-enumerate (FURB148) (#7454)
## Summary

Implement
[`no-ignored-enumerate-items`](https://github.com/dosisod/refurb/blob/master/refurb/checks/builtin/no_ignored_enumerate.py)
as `unnecessary-enumerate` (`FURB148`).

The auto-fix considers if a `start` argument is passed to the
`enumerate()` function. If only the index is used, then the suggested
fix is to pass the `start` value to the `range()` function. So,

```python
for i, _ in enumerate(xs, 1):
    ...
```

becomes

```python
for i in range(1, len(xs)):
    ...
```

If the index is ignored and only the value is ignored, and if a start
value greater than zero is passed to `enumerate()`, the rule doesn't
produce a suggestion. I couldn't find a unanimously accepted best way to
iterate over a collection whilst skipping the first n elements. The rule
still triggers, however.

Related to #1348.

## Test Plan

`cargo test`

---------

Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2023-09-19 20:19:28 +00:00
Charlie Marsh
4c4eceee36 Add dangling comment handling for lambda expressions (#7493)
## Summary

This PR adds dangling comment handling for `lambda` expressions. In
short, comments around the `lambda` and the `:` are all considered
dangling. Comments that come between the `lambda` and the `:` may be
moved after the colon for simplicity (this is an odd position for a
comment anyway), unless they also precede the lambda parameters, in
which case they're formatted before the parameters.

Closes https://github.com/astral-sh/ruff/issues/7470.

## Test Plan

`cargo test`

No change in similarity.

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99982 | 2760 | 37 |
| transformers | 0.99957 | 2587 | 398 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99929 | 648 | 16 |
| zulip | 0.99962 | 1437 | 22 |

After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99982 | 2760 | 37 |
| transformers | 0.99957 | 2587 | 398 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99929 | 648 | 16 |
| zulip | 0.99962 | 1437 | 22 |
2023-09-19 15:23:51 -04:00
Charlie Marsh
e07670ad97 Add dangling comment handling to dictionary key-value pairs (#7495)
## Summary

This PR fixes a formatting instability by changing the comment handling
around the `:` in a dictionary to mirror that of the `:` in a lambda: we
treat comments around the `:` as dangling, then format them after the
`:`.

Closes https://github.com/astral-sh/ruff/issues/7458.

## Test Plan

`cargo test`

No change in similarity.

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1631 |
| django | 0.99983 | 2760 | 36 |
| transformers | 0.99956 | 2587 | 404 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99929 | 648 | 16 |
| zulip | 0.99969 | 1437 | 21 |

After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1631 |
| django | 0.99983 | 2760 | 36 |
| transformers | 0.99956 | 2587 | 404 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99929 | 648 | 16 |
| zulip | 0.99969 | 1437 | 21 |
2023-09-19 19:17:21 +00:00
Charlie Marsh
b792140579 Ensure that LOG007 only triggers on .exception() calls (#7524)
## Summary

Previously, this rule triggered for `logging.info("...",
exc_info=False)`.
2023-09-19 17:29:54 +00:00
Tom Kuson
36a60bd50e Add documentation to non-empty-stub-body (#7519)
## Summary

Add documentation to `non-empty-stub-body` (`PYI010`) rule. Related to
#2646.

## Test Plan

`python scripts/check_docs_formatted.py`
2023-09-19 13:21:11 -04:00
konsti
4ae463d04b Add a test for stmt assign breaking in preview mode (#7516)
In preview mode, black will consistently break the right side first.
This doesn't work yet, but we'll need the test later.
2023-09-19 16:16:40 +02:00
konsti
6dade5b9ab Tokenizer: Emit only a single bogus token (#7425)
**Summary** Instead of emitting a bogus token per char, we now only emit
on single last bogus token. This leads to much more concise output.

**Test Plan** Updated fixtures
2023-09-19 16:06:03 +02:00
Charlie Marsh
97510c888b Show --no-X variants in CLI help (#7504)
I'd really like this to render as `--preview / --no-preview`, but I
looked for a while in the Clap internals and issue tracker
(https://github.com/clap-rs/clap/issues/815) and I really can't figure
out a way to do it -- this seems like the best we can do? It's also what
they do in Orogene.

Closes https://github.com/astral-sh/ruff/issues/7486.
2023-09-19 12:27:30 +00:00
konsti
94b68f201b Fix stylist indentation with a formfeed (#7489)
**Summary** In python, a formfeed is technically undefined behaviour
(https://docs.python.org/3/reference/lexical_analysis.html#indentation):
> A formfeed character may be present at the start of the line; it will
be ignored for
> the indentation calculations above. Formfeed characters occurring
elsewhere in the
> leading whitespace have an undefined effect (for instance, they may
reset the space
> count to zero).

In practice, they just reset the indentation:


df8b3a46a7/Parser/tokenizer.c (L1819-L1821)
a41bb2733f/crates/ruff_python_parser/src/lexer.rs (L664-L667)

The stylist didn't handle formfeeds previously and would produce invalid
indents. The remedy is to cut everything before a form feed.

Checks box for
https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458825

**Test Plan** Unit test for the stylist and a regression test for the
rule
2023-09-19 12:01:16 +02:00
konsti
ef34c5cbec Update itertools to 0.11 (#7513)
Preparation for #7469.

Changelog:
https://github.com/rust-itertools/itertools/blob/master/CHANGELOG.md#0110
2023-09-19 09:53:14 +00:00
dependabot[bot]
fdbefd777c Bump syn from 2.0.33 to 2.0.37 (#7512)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-19 11:08:56 +02:00
dependabot[bot]
078547adbb Bump clap from 4.4.3 to 4.4.4 (#7511)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-19 11:08:39 +02:00
dependabot[bot]
42a0bec146 Bump schemars from 0.8.13 to 0.8.15 (#7510)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-19 11:08:22 +02:00
Micha Reiser
37b7d0f921 fix: Compiler warning about unused map_or (#7508) 2023-09-19 08:10:01 +00:00
Micha Reiser
6a4dbd622b Add optimized best_fit_parenthesize IR (#7475) 2023-09-19 06:29:05 +00:00
Charlie Marsh
28b48ab902 Avoid flagging starred expressions in UP007 (#7505)
## Summary

These can't be fixed, because fixing them would lead to invalid syntax.
So flagging them also feels misleading.

Closes https://github.com/astral-sh/ruff/issues/7452.
2023-09-19 03:37:38 +00:00
Valeriy Savchenko
4123d074bd [refurb] Implement reimplemented-starmap rule (FURB140) (#7253)
## Summary

This PR is part of a bigger effort of re-implementing `refurb` rules
#1348. It adds support for
[FURB140](https://github.com/dosisod/refurb/blob/master/refurb/checks/itertools/use_starmap.py)

## Test Plan

I included a new test + checked that all other tests pass.
2023-09-19 02:18:54 +00:00
Mathieu Kniewallner
c6ba7dfbc6 feat(rules): implement flake8-bandit S201 (flask_debug_true) (#7503)
Part of #1646.

## Summary

Implement `S201`
([`flask_debug_true`](https://bandit.readthedocs.io/en/latest/plugins/b201_flask_debug_true.html))
rule from `bandit`.

I am fairly new to Rust and Ruff's codebase, so there might be better
ways to implement the rule or write the code.

## Test Plan

Snapshot test from
https://github.com/PyCQA/bandit/blob/1.7.5/examples/flask_debug.py, with
a few additions in the "unrelated" part to test a bit more cases.
2023-09-19 00:43:28 +00:00
Micael Jarniac
40f6456add Use MkDocs' not_in_nav (#5498)
Closes #5497
Needs MkDocs 1.5 to be released.
- [x] https://github.com/mkdocs/mkdocs/milestone/15

## Summary
Uses MkDocs' `not_in_nav` config to hide spam about files in
`docs/rules/` not being in nav.
2023-09-19 00:01:43 +00:00
Micha Reiser
3e1dffab20 refactor: Use OnceCell in Memoized (#7500) 2023-09-18 19:58:55 +00:00
Micha Reiser
3336d23f48 perf: Reduce best fitting allocations (#7411) 2023-09-18 19:49:44 +00:00
Jonathan Plasse
2421805033 Avoid N802 violations for @overload methods (#7498)
Close #7479

The `@override` was already implemented

## Test Plan

Tested the code in the issue. After removing all the noqa's, only one
occurrence of `BadName()` raised a violation.
Added a fixture
2023-09-18 14:32:40 -04:00
Tom Kuson
359f50e6dc Ignore pass-statement-stub-body documentation formatting (#7497)
## Summary

Fix CI (broken in #7496).

The code snippet was formatted as Black would format a stub file, but
the CI script doesn't know that (it assumes all code snippets are
non-stub files). Easier to ignore.

Sorry for breaking CI!

## Test Plan

`python scripts/check_docs_formatted.py`
2023-09-18 14:27:20 -04:00
qdegraaf
bccba5d73f [flake8-logging] Implement LOG007: ExceptionWithoutExcInfo (#7410)
## Summary

This PR implements a new rule for `flake8-logging` plugin that checks
for uses of `logging.exception()` with `exc_info` set to `False` or a
falsy value. It suggests using `logging.error` in these cases instead.

I am unsure about the name. Open to suggestions there, went with the
most explicit name I could think of in the meantime.

Refer https://github.com/astral-sh/ruff/issues/7248

## Test Plan

Added a new fixture cases and ran `cargo test`
2023-09-18 17:46:12 +00:00
Tom Kuson
0bfdb15ecf Add documentation to pass-statement-stub-body (#7496)
## Summary

Add documentation to `pass-statement-stub-body` (`PYI009`) rule. Related
to #2646.

## Test Plan

`python scripts/check_docs_formatted.py`
2023-09-18 17:33:15 +00:00
dependabot[bot]
a902d14c31 Bump chrono from 0.4.30 to 0.4.31 (#7481) 2023-09-18 13:11:51 -04:00
Charlie Marsh
728539291f Move FormatExprDict to top of expr_dict.rs (#7494)
Put the node itself up top, and internal structs down below.
2023-09-18 11:55:18 -04:00
Dhruv Manilawala
c2bd8af59a Remove triple-quoted string ranges computation (#7476)
## Summary

This is a follow-up PR for #7435 to remove the now unused triple-quoted
string ranges from the indexer.
2023-09-18 20:57:49 +05:30
Jaap Roes
c946bf157e Extend bad-dunder-method-name to permit __html__ (#7492)
## Summary

Fixes #7478

## Test Plan

`cargo test`
2023-09-18 15:16:22 +00:00
Charlie Marsh
8ab2519717 Respect parentheses for precedence in await (#7468)
## Summary

We were using `Parenthesize::IfBreaks` universally for `await`, but
dropping parentheses can change the AST due to precedence. It turns out
that Black's rules aren't _exactly_ the same as operator precedence
(e.g., they leave parentheses around `await ([1, 2, 3])`, although they
aren't strictly required).

Closes https://github.com/astral-sh/ruff/issues/7467.

## Test Plan

`cargo test`

No change in similarity.

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99982 | 2760 | 37 |
| transformers | 0.99957 | 2587 | 398 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99929 | 648 | 16 |
| zulip | 0.99962 | 1437 | 22 |

After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99982 | 2760 | 37 |
| transformers | 0.99957 | 2587 | 398 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99929 | 648 | 16 |
| zulip | 0.99962 | 1437 | 22 |
2023-09-18 09:56:41 -04:00
konsti
c4d85d6fb6 Fix ''' ""''' formatting (#7485)
## Summary

`''' ""'''` is an edge case that was previously incorrectly formatted as
`""" """""`.

Fixes #7460

## Test Plan

Added regression test
2023-09-18 10:28:15 +00:00
dependabot[bot]
70ea49bf72 Bump test-case from 3.1.0 to 3.2.1 (#7484)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 11:31:41 +02:00
dependabot[bot]
8e255974bc Bump unicode-ident from 1.0.11 to 1.0.12 (#7482)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 11:31:06 +02:00
dependabot[bot]
d358604464 Bump serde_json from 1.0.106 to 1.0.107 (#7480)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 11:30:06 +02:00
dependabot[bot]
8243db74fe Bump indoc from 2.0.3 to 2.0.4 (#7483)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 11:29:50 +02:00
Micha Reiser
0346e781d4 Fix handling of newlines in empty files (#7473) 2023-09-18 06:08:10 +00:00
Tom Kuson
b66bfa6570 Extend bad-dunder-method-name to permit attrs dunders (#7472)
## Summary

Closes #7451.

## Test Plan

`cargo test`
2023-09-17 16:13:33 -04:00
Charlie Marsh
28273eb00b Avoid flagging starred elements in C402 (#7466)
## Summary

Rewriting these is not valid syntax.

Part of https://github.com/astral-sh/ruff/issues/7455.
2023-09-17 15:23:27 +00:00
Charlie Marsh
12acd191e1 Remove parentheses when rewriting assert calls to statements (#7464) 2023-09-17 15:18:56 +00:00
Charlie Marsh
64b929bc29 Add padding to prevent some autofix errors (#7461)
## Summary

We should really be doing this anywhere we _replace_ an expression.

See: https://github.com/astral-sh/ruff/issues/7455.
2023-09-17 15:14:16 +00:00
Micha Reiser
26ae0a6e8d Fix dangling module comments (#7456) 2023-09-17 14:56:41 +00:00
Dhruv Manilawala
959338d39d Refactor tab-indentation as a token-based rule (#7435)
## Summary

This PR updates the `W191` (`tab-indentation`) rule from a line-based to
a token-based rule.

Earlier, the rule used the `triple_quoted_string_ranges` from the
indexer to skip over any lines _inside_ a triple-quoted string. This was the only
use of the ranges. These ranges were extracted through the tokens, so instead
we can directly use the newline tokens to perform the check.

This would also mean that we can remove the `triple_quoted_string_ranges` from
the indexer but I'll hold that off until we have a better idea on #7326
but I don't think it would be a problem to remove it.

This will also fix #7379 once PEP 701 changes are merged.

## Test Plan

`cargo test`
2023-09-16 20:25:20 +00:00
Charlie Marsh
422ff82f4a Avoid extra parentheses in yield expressions (#7444)
## Summary

This is equivalent to https://github.com/astral-sh/ruff/pull/7424, but
for `yield` and `yield from` expressions. Specifically, we want to avoid
adding unnecessary extra parentheses for `yield expr` when `expr` itself
does not require parentheses.

## Test Plan

`cargo test`

No change in any of the similarity metrics.

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99982 | 2760 | 37 |
| transformers | 0.99957 | 2587 | 399 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99929 | 648 | 16 |
| zulip | 0.99962 | 1437 | 22 |

After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99982 | 2760 | 37 |
| transformers | 0.99957 | 2587 | 399 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99929 | 648 | 16 |
| zulip | 0.99962 | 1437 | 22 |
2023-09-16 14:46:56 -04:00
Charlie Marsh
8d0a5e01bd Modify comment_ranges slice in BackwardsTokenizer (#7432)
## Summary

I was kinda curious to understand this issue
(https://github.com/astral-sh/ruff/issues/7426) and just ended up
attempting to address it.

## Test Plan

`cargo test`
2023-09-16 14:04:45 -04:00
Charlie Marsh
aae02cf275 Fix broken is_expression_parenthesized call from rebase (#7442) 2023-09-16 17:22:16 +00:00
Charlie Marsh
7e2eba2592 Avoiding grouping comprehension targets separately from conditions (#7429)
## Summary

Black seems to treat comprehension targets and conditions as if they're
in a single group -- so if the comprehension expands, all conditions are
rendered on their own line, etc.

Closes https://github.com/astral-sh/ruff/issues/7421.

## Test Plan

`cargo test`

No change in any of the similarity metrics.

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99982 | 2760 | 37 |
| transformers | 0.99957 | 2587 | 399 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99923 | 648 | 18 |
| zulip | 0.99962 | 1437 | 22 |

After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99982 | 2760 | 37 |
| transformers | 0.99957 | 2587 | 399 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99923 | 648 | 18 |
| zulip | 0.99962 | 1437 | 22 |
2023-09-16 17:19:34 +00:00
Charlie Marsh
22770fb4be Avoid extra parentheses in await expressions (#7424)
## Summary

This PR aligns the await parenthesizing with the unary case, which is:
if the value is already parenthesized, avoid parenthesizing; otherwise,
only parenthesize if the _value_ needs parenthesizing.

Closes https://github.com/astral-sh/ruff/issues/7420.

## Test Plan

`cargo test`

No change in similarity.

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99982 | 2760 | 37 |
| transformers | 0.99957 | 2587 | 399 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99923 | 648 | 18 |
| zulip | 0.99962 | 1437 | 22 |

After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99982 | 2760 | 37 |
| transformers | 0.99957 | 2587 | 399 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99923 | 648 | 18 |
| zulip | 0.99962 | 1437 | 22 |
2023-09-16 13:10:35 -04:00
Charlie Marsh
1880cceac1 Avoid extra parentheses in unary expressions (#7428)
## Summary

This PR applies a similar fix to unary expressions as in
https://github.com/astral-sh/ruff/pull/7424. Specifically, we only need
to parenthesize the entire operator if the operand itself doesn't have
parentheses, and requires parentheses.

Closes https://github.com/astral-sh/ruff/issues/7423.

## Test Plan

`cargo test`

No change in similarity.

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99982 | 2760 | 37 |
| transformers | 0.99957 | 2587 | 399 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99923 | 648 | 18 |
| zulip | 0.99962 | 1437 | 22 |

After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99982 | 2760 | 37 |
| transformers | 0.99957 | 2587 | 399 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99923 | 648 | 18 |
| zulip | 0.99962 | 1437 | 22 |
2023-09-16 13:07:38 -04:00
Dhruv Manilawala
0d1fb823d6 [flake8-logging] Implement LOG002: invalid-get-logger-argument (#7399)
## Summary

This PR implements a new rule for `flake8-logging` plugin that checks
for
`logging.getLogger` calls with either `__file__` or `__cached__` as the
first
argument and generates a suggested fix to use `__name__` instead.

Refer: #7248

## Test Plan

Add test cases and `cargo test`
2023-09-16 12:21:30 -04:00
Micha Reiser
c907317199 Fix build (#7437) 2023-09-16 14:50:36 +00:00
Micha Reiser
916dd5b7fa fix: Use BestFit layout for subscript (#7409) 2023-09-16 16:21:45 +02:00
konsti
2cbe1733c8 Use CommentRanges in backwards lexing (#7360)
## Summary

The tokenizer was split into a forward and a backwards tokenizer. The
backwards tokenizer uses the same names as the forwards ones (e.g.
`next_token`). The backwards tokenizer gets the comment ranges that we
already built to skip comments.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2023-09-16 03:21:45 +00:00
Charlie Marsh
1f6e1485f9 Change playground to use pages deploy (#7430)
Fixes a deprecation warning.
2023-09-16 03:11:34 +00:00
Charlie Marsh
9b43162cc4 Move documentation to docs.astral.sh/ruff (#7419)
## Summary

We're planning to move the documentation from
[https://beta.ruff.rs/docs](https://beta.ruff.rs/docs) to
[https://docs.astral.sh/ruff](https://docs.astral.sh/ruff), for a few
reasons:

1. We want to remove the `beta` from the domain, as Ruff is no longer
considered beta software.
2. We want to migrate to a structure that could accommodate multiple
future tools living under one domain.

The docs are actually already live at
[https://docs.astral.sh/ruff](https://docs.astral.sh/ruff), but later
today, I'll add a permanent redirect from the previous to the new
domain. **All existing links will continue to work, now and in
perpetuity.**

This PR contains the code changes necessary for the updated
documentation. As part of this effort, I moved the playground and
documentation from my personal Cloudflare account to our team Cloudflare
account (hence the new `--project-name` references). After merging, I'll
also update the secrets on this repo.
2023-09-15 22:49:42 -04:00
Charlie Marsh
cc9e84c144 Format trailing operator comments as dangling (#7427)
## Summary

Given a trailing operator comment in a unary expression, like:

```python
if (
  not  # comment
  a):
    ...
```

We were attaching these to the operand (`a`), but formatting them in the
unary operator via special handling. Parents shouldn't format the
comments of their children, so this instead attaches them as dangling
comments on the unary expression. (No intended change in formatting.)
2023-09-15 20:34:09 -04:00
Zanie Blue
f4d50a2aec Fix release validation step (#7417)
Proof of concept at #7416
Fixes `main` branch check added in #7279 (see
[failure](https://github.com/astral-sh/ruff/actions/runs/6201772425/job/16839150669))
Removes the meaningless "SHA consistency" check (since we literally
check out the SHA now)
2023-09-15 20:18:25 +00:00
Zanie Blue
0c030b5bf3 Bump version to 0.0.290 (#7413)
See also:
- https://github.com/astral-sh/astral-sh/pull/41
- https://github.com/astral-sh/ruff-pre-commit/pull/51
2023-09-15 13:51:46 -05:00
Zanie Blue
1b082ce67e Add maximum length for line-length to JSON schema (#7412)
<!--
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

<!-- What's the purpose of the change? What does it do, and why? -->
Adds the maximum of 320 for the line-length setting to the JSON schema
for better integration with IDEs.

Related https://github.com/astral-sh/ruff/pull/6873

## Test Plan

<!-- How was it tested? -->
2023-09-15 18:10:06 +00:00
dependabot[bot]
f936d319cc Bump toml from 0.7.6 to 0.7.8 (#7405)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-15 09:14:58 +00:00
dependabot[bot]
85d8b6228f Bump chrono from 0.4.28 to 0.4.30 (#7406)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-15 09:00:17 +00:00
dependabot[bot]
7594dadc1d Bump syn from 2.0.29 to 2.0.33 (#7404)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-15 08:49:52 +00:00
dependabot[bot]
de37fbfac9 Bump serde-wasm-bindgen from 0.5.0 to 0.6.0 (#7403)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-15 08:47:42 +00:00
dependabot[bot]
4e2769a16c Bump mimalloc from 0.1.38 to 0.1.39 (#7402)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-15 08:46:55 +00:00
Manuel Jacob
75b5c314e3 Change CWE reference in documentation for S607 rule (#7398)
<!--
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

The previous reference was “CWE-78: Improper Neutralization of Special
Elements used in an OS Command ('OS Command Injection')”, which
describes another issue. The new reference is “CWE-426: Untrusted Search
Path”, which describes exactly the problem that this rule should warn
about.

## Test Plan

The change was not tested, as it only changes two numbers in the
documentation.
2023-09-14 23:12:54 -05:00
Charlie Marsh
450fb9b99a [flake8-logging] Implement LOG001: direct-logger-instantiation (#7397)
## Summary

See: https://github.com/astral-sh/ruff/issues/7248.
2023-09-14 23:07:10 -04:00
Charlie Marsh
6163c99551 Mark PERF403 as a preview rule (#7396) 2023-09-15 01:57:48 +00:00
qdegraaf
3112202a5b [flake8-logging] Add flake8_logging boilerplate and first rule LOG009 (#7249)
## Summary

Adds `LOG009` from
[flake8-logging](https://github.com/adamchainz/flake8-logging). Also
adds the boilerplate for a new plugin

Checks for usages of undocumented `logging.WARN` constant and suggests
replacement with `logging.WARNING`.

## Test Plan

`cargo test` with fresh fixture

## Issue links

Refers: https://github.com/astral-sh/ruff/issues/7248
2023-09-15 01:41:32 +00:00
Charlie Marsh
64ea00048b Add known problems to UP040 documentation (#7395) 2023-09-15 01:31:20 +00:00
qdegraaf
067a4acd54 [perflint] Add PERF403 (#6132)
## Summary

Adds `PERF403` mirroring `W8403` from
https://github.com/tonybaloney/perflint

## Test Plan

Fixtures were added based on perflint tests

## Issue Links

Refers: https://github.com/astral-sh/ruff/issues/4789
2023-09-15 01:23:51 +00:00
Nathan Whitaker
c88376f468 Add support for bounds, constraints, and explicit variance on generic type variables to UP040 (#6749)
## Summary

Extends UP040 to support moving type variables with
bounds/constraints/variance that are used in type aliases to use PEP-695
syntax.

Part of #4617.

## Test Plan

The existing tests added by #6314 already cover the relevant cases.
2023-09-14 21:11:24 -04:00
Charlie Marsh
9b7c29853d Reflect conversion reason in UP012 messages (#7393)
Closes https://github.com/astral-sh/ruff/issues/7254.
2023-09-14 20:08:58 +00:00
Charlie Marsh
3e21d32b79 Avoid flagging single-quoted docstrings with continuations for multi-line rules (#7392)
Rules like D209 and D205 are only intended to apply to multi-line
docstrings. If a docstring is single-quoted, but extends via a
continuation, it should be excluded (it'll be flagged by another rule
anyway). Closes https://github.com/astral-sh/ruff/issues/7058.
2023-09-14 18:58:39 +00:00
Charlie Marsh
f9e3ea23ba Show rule codes in shell tab completion (#7375)
## Summary

I noticed that we have a custom parser for rule selectors, but it wasn't
actually being used? This PR adds it back to our Clap setup and changes
the parser to only show full categories and individual rules when
tab-completing:

<img width="1792" alt="Screen Shot 2023-09-13 at 9 13 38 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/028b18d2-8c92-49c1-b781-f24c9ae310f7">

<img width="1792" alt="Screen Shot 2023-09-13 at 9 13 40 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/fd598da5-78fb-412d-a69e-2a0963d479cd">

<img width="1792" alt="Screen Shot 2023-09-13 at 9 13 58 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/7c482b90-6e54-425c-ae23-fb50496a177a">

The previous implementation showed all codes, which I found too noisy:

<img width="1792" alt="Screen Shot 2023-09-13 at 8 57 09 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/db370a0e-2a9f-4acd-b1e3-224a1f8e9ce5">
2023-09-14 18:37:23 +00:00
Charlie Marsh
6856d0b44b Use dot references in docs for methods (#7391)
## Summary

This matches the convention used in the Python documentation.
2023-09-14 14:35:34 -04:00
Charlie Marsh
21539f1663 Allow NURSERY in JSON Schema (#7374)
## Summary

At some point, we removed these so that they wouldn't be autocompleted
for users, since we wanted to discourage usage of `ALL`. But given that
they're valid values, I think that was a bad idea -- it leads to an even
more confusing experience whereby JSON Schema validators tell you that
you have an error, when you don't.

Closes https://github.com/astral-sh/ruff/issues/7261.
2023-09-14 18:09:35 +00:00
Zanie Blue
b9bb6bf780 Remove the PREVIEW rule selector (#7389)
The rule selector is not useful because `--select PREVIEW` only targets
Ruff developers and `--ignore PREVIEW` has no effect due to its low
specificity. We may restore it later if useful.
2023-09-14 12:31:59 -05:00
Charlie Marsh
d39eae2713 Extend C416 to catch tuple unpacking (#7363)
Closes https://github.com/astral-sh/ruff/issues/7307.
2023-09-14 15:53:15 +00:00
Charlie Marsh
5d21b9c22e Catch panics in formatter (#7377)
## Summary

This PR ensures that we catch and render panics in the formatter
identically to other kinds of errors. It also improves the consistency
in error rendering throughout and makes a few stylistic changes to the
messages.

Closes https://github.com/astral-sh/ruff/issues/7247.

## Test Plan

I created a file `foo.py` with a syntax error, and a file `bar.py` with
an intentional panic.

<img width="1624" alt="Screen Shot 2023-09-13 at 10 25 22 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/605c2839-ad02-4376-a2e9-d5a593ab660f">

<img width="1624" alt="Screen Shot 2023-09-13 at 10 25 24 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/b1381909-157c-48cb-9630-d0bbfcb1b640">
2023-09-14 11:44:16 -04:00
Charlie Marsh
ec2f229a45 Remove ExprContext from ComparableExpr (#7362)
`ComparableExpr` includes the `ExprContext` field on an expression, so,
e.g., the two tuples in `(a, b) = (a, b)` won't be considered equal.
Similarly, the tuples in `[(a, b) for (a, b) in c]` _also_ wouldn't be
considered equal. I find this behavior surprising, since
`ComparableExpr` is intended to allow you to compare two ASTs, but
`ExprContext` is really encoding information about the broader context
for the expression.
2023-09-14 15:40:02 +00:00
Charlie Marsh
34c1cb7d11 Treat parenthesized power operands as non-simple (#7371)
Closes https://github.com/astral-sh/ruff/issues/7318.
2023-09-14 15:36:21 +00:00
dependabot[bot]
2ddea7c657 Bump regex from 1.9.4 to 1.9.5 (#7384)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-14 14:58:24 +00:00
dependabot[bot]
45eabdd2c3 Bump shlex from 1.1.0 to 1.2.0 (#7381)
Bumps [shlex](https://github.com/comex/rust-shlex) from 1.1.0 to 1.2.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/comex/rust-shlex/blob/master/CHANGELOG.md">shlex's
changelog</a>.</em></p>
<blockquote>
<h1>1.2.0</h1>
<ul>
<li>Adds <code>bytes</code> module to support operating directly on byte
strings.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/comex/rust-shlex/commits">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=shlex&package-manager=cargo&previous-version=1.1.0&new-version=1.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2023-09-14 09:40:05 -05:00
Micha Reiser
675c86c175 fix: Group fluent subscript (#7386) 2023-09-14 13:04:14 +02:00
dependabot[bot]
1f8e2b8f14 Bump proc-macro2 from 1.0.66 to 1.0.67 (#7383)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-14 09:49:57 +00:00
Micha Reiser
58b3040342 chore: Upgrade notify (#7338) 2023-09-14 09:47:55 +00:00
dependabot[bot]
6a9b8aede1 Bump thiserror from 1.0.47 to 1.0.48 (#7385)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-14 09:21:47 +00:00
dependabot[bot]
f48126ad00 Bump clap from 4.4.1 to 4.4.3 (#7382)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-14 09:13:12 +00:00
Charlie Marsh
11287f944f Avoid re-parenthesizing call chains whose inner values are parenthesized (#7373)
## Summary

Given a statement like:

```python
result = (
    f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
    + 1
)()
```

When we go to parenthesize the target of the assignment, we use
`maybe_parenthesize_expression` with `Parenthesize::IfBreaks`. This then
checks if the call on the right-hand side needs to be parenthesized, the
implementation of which looks like:

```rust
impl NeedsParentheses for ExprCall {
    fn needs_parentheses(
        &self,
        _parent: AnyNodeRef,
        context: &PyFormatContext,
    ) -> OptionalParentheses {
        if CallChainLayout::from_expression(self.into(), context.source())
            == CallChainLayout::Fluent
        {
            OptionalParentheses::Multiline
        } else if context.comments().has_dangling(self) {
            OptionalParentheses::Always
        } else {
            self.func.needs_parentheses(self.into(), context)
        }
    }
}
```

Checking for `self.func.needs_parentheses(self.into(), context)` is
problematic, since, as in the example above, `self.func` may _already_
be parenthesized -- in which case, we _don't_ want to parenthesize the
entire expression. If we do, we end up with this non-ideal formatting:

```python
result = (
    (
        f(
            111111111111111111111111111111111111111111111111111111111111111111111111111111111
        )
        + 1
    )()
)
```

This PR modifies the `NeedsParentheses` implementations for call chain
expressions to return `Never` if the inner expression has its own
parentheses, in which case, the formatting implementations for those
expressions will preserve them anyway.

Closes https://github.com/astral-sh/ruff/issues/7370.

## Test Plan

Zulip improves a bit, everything else is unchanged.

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99981 | 2760 | 40 |
| transformers | 0.99944 | 2587 | 413 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99834 | 648 | 20 |
| zulip | 0.99956 | 1437 | 23 |

After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99981 | 2760 | 40 |
| transformers | 0.99944 | 2587 | 413 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99834 | 648 | 20 |
| **zulip** | **0.99962** | **1437** | **22** |
2023-09-14 05:05:37 -04:00
Micha Reiser
a65efcf459 fix: Don't omit optional parentheses for subscripts (#7380) 2023-09-14 08:43:53 +00:00
Jelle van der Waa
04183b0299 [pylint] Implement too-many-public-methods rule (PLR0904) (#6179)
Implement
https://pylint.pycqa.org/en/latest/user_guide/messages/refactor/too-many-public-methods.html

Confusingly the rule page mentions a max of 7 while in practice it is
20.
https://github.com/search?q=repo%3Apylint-dev%2Fpylint+max-public-methods&type=code

## Summary

Implement pylint's R0904

## Test Plan

Unit tests.
2023-09-14 00:52:26 +00:00
James Braza
36fa1fe359 Docs linking error tutorial with error suppression (#7014)
Documents takeaway from
https://github.com/astral-sh/ruff/discussions/7011#discussioncomment-6869239.
2023-09-13 20:22:18 -04:00
Charlie Marsh
6e625bd93d Invert reverse argument regardless of whether it's a boolean (#7372)
## Summary

When fixing `reversed(sorted(x, reverse=False))`, we rewrite as
`sorted(x, reverse=True)`. However, if the `reverse` argument isn't
`True` or `False`, we leave it as-is, which is incorrect.

Now, given `reversed(sorted(x, reverse=y))`, we rewrite as `sorted(x,
reverse=not y)`.
2023-09-13 20:12:35 -04:00
Zanie Blue
ebd1b296fd Add warnings for nursery and preview rule selection (#7210)
## Summary

Adds warnings for cases where:
- A selector does not include any rules because preview is disabled
- A nursery rule is selected without the preview flag

## Test plan

Add integration tests
2023-09-13 15:29:58 -05:00
Zanie Blue
1373e1c395 Update release workflow to checkout the given sha (#7279) 2023-09-13 14:59:41 -05:00
Zanie Blue
4bff397318 Move FURB145 from nursery to preview (#7364)
Moves the new rule from nursery to preview for the upcoming release.

Adds new test coverage for selection of a single preview rule and fixes
a bug where preview rules were incorrectly selectable with exact codes.
2023-09-13 14:54:28 -05:00
Charlie Marsh
5347df4728 Parenthesize single-generator arguments when adding reverse keyword (#7365)
Closes https://github.com/astral-sh/ruff/issues/7289.
2023-09-13 19:40:45 +00:00
Tom Kuson
ebe9c03545 [refurb] Implement no-slice-copy (FURB145) (#7007)
## Summary

Implement
[`no-slice-copy`](https://github.com/dosisod/refurb/blob/master/refurb/checks/builtin/no_slice_copy.py)
as `slice-copy` (`FURB145`).

Related to #1348.

## Test Plan

`cargo test`
2023-09-13 17:31:15 +00:00
Charlie Marsh
b0cbcd3dfa Update deprecated-import lists based on recent typing-extension release (#7356)
## Summary

Generated by running
f3cff244e3/testing/generate-typing-rewrite-info (L14)
with latest `typing-extensions` and manually applying the changes.

Closes https://github.com/astral-sh/ruff/issues/7324.
2023-09-13 13:21:11 -04:00
Charlie Marsh
4df9e07a79 Add a benchmarking script for the formatter CLI (#7340)
## Summary

This PR adds a benchmarking script for the formatter, which benchmarks
the Ruff formatter against Black, yapf, and autopep8.

Three benchmarks are included:

1. Format everything.
2. Format everything, but use a single thread.
3. Format everything, but `--check` (don't write to disk).

There's some nuance in figuring out the right combination of arguments
to each command, but the _main_ nuance is to ensure that we always run
the given formatter (and modify the target repo in-place) prior to
benchmarking it, so that the formatters aren't disadvantaged by the
existing formatting of the target repo. (E.g.: prior to benchmarking
Black's preview style, we need to make sure we format the target repo
with Black's preview style; otherwise, preview style appears much
slower.)

Part of https://github.com/astral-sh/ruff/issues/7309.
2023-09-13 13:15:48 -04:00
Charlie Marsh
f0f7ea7502 Treat whitespace-only line as blank for D411 (#7351)
This better aligns with the definition of "blank line" that we use
throughout the docstring rules.

Closes https://github.com/astral-sh/ruff/issues/7216.
2023-09-13 16:41:12 +00:00
Micha Reiser
4f26002dd5 chore: Upgrade strum (#7337)
## Summary

This PR upgrades `strum` from 0.24.x to 0.25.x. 

The breaking changes are: 
* strum macros now uses syn2
* The `to_string` behavior changed when using `default`. I did a quick search, we aren't using `strum(default)` 


`strum` now has a `#[derive(EnumIs)]` macro that generates `is_` methods. 

## Test Plan

cargo test
2023-09-13 18:33:27 +02:00
dependabot[bot]
d1a9c198e3 Bump path-absolutize from 3.1.0 to 3.1.1 (#7345)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 16:21:51 +00:00
dependabot[bot]
7a4f699fba Bump argfile from 0.1.5 to 0.1.6 (#7344)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 16:18:21 +00:00
dependabot[bot]
3fb5418c2c Bump memchr from 2.6.2 to 2.6.3 (#7343)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 18:14:42 +02:00
dependabot[bot]
9fcc009a0c Bump serde_json from 1.0.105 to 1.0.106 (#7342)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 18:13:24 +02:00
Micha Reiser
bf8e5a167b chore: Upgrade walkdir (#7336)
## Summary

The only commit is [api: add follow_root_links() option to WalkDir](dcc527d832) whicih addsa  new option wheter `walkdir` should follow a root symlink or not. 
The new option defaults to `true` which is the same as before. 

## Test Plan

`cargo test`
2023-09-13 18:07:24 +02:00
Micha Reiser
8a001dfc3d chore: Upgrade pyproject-toml crate (#7335)
## Summary

This PR bumps the pyproject-toml crate to 0.7.0. The only difference is that it now depends on indexmap 2. I reviewed the indexmap 2 changes and they don't seem relevant to us. 

I used this opportunity to remove the default features from `serde_with` which removes our indexmap 1 dependency (and some other unused dependencies)

## Test Plan

`cargo test`
2023-09-13 17:55:03 +02:00
Zanie Blue
0823394525 Display nursery rules as preview in documentation (#7341)
This is broken in the last release
2023-09-13 10:46:43 -05:00
Zanie Blue
e15047815c Update Rust toolchain file to use TOML format (#7339)
Ref https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file
Should resolve Dependabot failure at
https://github.com/astral-sh/ruff/network/updates/721380342
Follows #7034
2023-09-13 15:43:03 +00:00
Micha Reiser
7531bb3b21 Upgrade is-macros to 0.3.0 (#7334) 2023-09-13 15:27:40 +02:00
Micha Reiser
2d9b39871f Introduce IndentWidth (#7301) 2023-09-13 14:52:24 +02:00
Micha Reiser
e122a96d27 playground: Respect line-length and preview configuration (#7330) 2023-09-13 12:14:25 +00:00
konsti
f4c7bff36b Don't reorder parameters in function calls (#7268)
## Summary

In `f(*args, a=b, *args2, **kwargs)` the args (`*args`, `*args2`) and
keywords (`a=b`, `**kwargs`) are interleaved, which we previously didn't
handle.

Fixes #6498

**main**

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| **django** | 0.99966 | 2760 | 58 |
| transformers | 0.99930 | 2587 | 447 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99825 | 648 | 22 |
| zulip | 0.99950 | 1437 | 27 |

**PR**

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| **django** | 0.99967 | 2760 | 53 |
| transformers | 0.99930 | 2587 | 447 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99825 | 648 | 22 |
| zulip | 0.99950 | 1437 | 27 |


## Test Plan

New fixtures
2023-09-13 09:01:49 +00:00
konsti
56440ad835 Introduce ArgOrKeyword to keep call parameter order (#7302)
## Motivation

The `ast::Arguments` for call argument are split into positional
arguments (args) and keywords arguments (keywords). We currently assume
that call consists of first args and then keywords, which is generally
the case, but not always:

```python
f(*args, a=2, *args2, **kwargs)

class A(*args, a=2, *args2, **kwargs):
    pass
```

The consequence is accidentally reordering arguments
(https://github.com/astral-sh/ruff/pull/7268).

## Summary

`Arguments::args_and_keywords` returns an iterator of an `ArgOrKeyword`
enum that yields args and keywords in the correct order. I've fixed the
obvious `args` and `keywords` usages, but there might be some cases with
wrong assumptions remaining.

## Test Plan

The generator got new test cases, otherwise the stacked PR
(https://github.com/astral-sh/ruff/pull/7268) which uncovered this.
2023-09-13 08:45:46 +00:00
Charlie Marsh
179128dc54 Link discussion in formatter README (#7311) 2023-09-12 16:50:22 +00:00
5098 changed files with 211231 additions and 170431 deletions

4
.gitattributes vendored
View File

@@ -1,7 +1,7 @@
* text=auto eol=lf
crates/ruff/resources/test/fixtures/isort/line_ending_crlf.py text eol=crlf
crates/ruff/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf
crates/ruff_linter/resources/test/fixtures/isort/line_ending_crlf.py text eol=crlf
crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf
ruff.schema.json linguist-generated=true text=auto eol=lf
*.md.snap linguist-language=Markdown

2
.github/CODEOWNERS vendored
View File

@@ -6,4 +6,4 @@
# - Order is important. The last matching pattern has the most precedence.
# Jupyter
/crates/ruff/src/jupyter/ @dhruvmanila
/crates/ruff_linter/src/jupyter/ @dhruvmanila

View File

@@ -9,5 +9,5 @@ updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "daily"
interval: "weekly"
labels: ["internal"]

5
.github/release.yml vendored
View File

@@ -4,7 +4,6 @@ changelog:
labels:
- internal
- documentation
- formatter
categories:
- title: Breaking Changes
labels:
@@ -12,7 +11,6 @@ changelog:
- title: Rules
labels:
- rule
- autofix
- title: Settings
labels:
- configuration
@@ -20,6 +18,9 @@ changelog:
- title: Bug Fixes
labels:
- bug
- title: Formatter
labels:
- formatter
- title: Preview
labels:
- preview

View File

@@ -96,7 +96,6 @@ jobs:
uses: taiki-e/install-action@v2
with:
tool: cargo-insta
- run: pip install black[d]==23.1.0
- uses: Swatinem/rust-cache@v2
- name: "Run tests (Ubuntu)"
if: ${{ matrix.os == 'ubuntu-latest' }}
@@ -106,10 +105,6 @@ jobs:
shell: bash
# We can't reject unreferenced snapshots on windows because flake8_executable can't run on windows
run: cargo insta test --all --all-features
- run: cargo test --package ruff_cli --test black_compatibility_test -- --ignored
# TODO: Skipped as it's currently broken. The resource were moved from the
# ruff_cli to ruff crate, but this test was not updated.
if: false
# Check for broken links in the documentation.
- run: cargo doc --all --no-deps
env:
@@ -230,12 +225,12 @@ jobs:
- uses: actions/checkout@v4
- name: "Install nightly Rust toolchain"
# Only pinned to make caching work, update freely
run: rustup toolchain install nightly-2023-06-08
run: rustup toolchain install nightly-2023-10-15
- uses: Swatinem/rust-cache@v2
- name: "Install cargo-udeps"
uses: taiki-e/install-action@cargo-udeps
- name: "Run cargo-udeps"
run: cargo +nightly-2023-06-08 udeps
run: cargo +nightly-2023-10-15 udeps
python-package:
name: "python package"
@@ -342,6 +337,45 @@ jobs:
- name: "Remove checkouts from cache"
run: rm -r target/progress_projects
check-ruff-lsp:
name: "test ruff-lsp"
runs-on: ubuntu-latest
needs: cargo-test
steps:
- uses: extractions/setup-just@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@v4
name: "Download ruff-lsp source"
with:
repository: "astral-sh/ruff-lsp"
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/download-artifact@v3
name: Download development ruff binary
id: ruff-target
with:
name: ruff
path: target/debug
- name: Install ruff-lsp dependencies
run: |
just install
- name: Run ruff-lsp tests
run: |
# Setup development binary
pip uninstall --yes ruff
chmod +x ${{ steps.ruff-target.outputs.download-path }}/ruff
export PATH=${{ steps.ruff-target.outputs.download-path }}:$PATH
ruff version
just test
benchmarks:
runs-on: ubuntu-latest
steps:

View File

@@ -47,9 +47,9 @@ jobs:
run: mkdocs build --strict -f mkdocs.generated.yml
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@v3.1.1
uses: cloudflare/wrangler-action@v3.3.1
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
# `github.head_ref` is only set during pull requests and for manual runs or tags we use `main` to deploy to production
command: pages deploy site --project-name=ruff-docs --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
command: pages deploy site --project-name=astral-docs --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}

View File

@@ -40,8 +40,9 @@ jobs:
working-directory: playground
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@v3.1.1
uses: cloudflare/wrangler-action@v3.3.1
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages publish playground/dist --project-name=ruff --branch ${GITHUB_HEAD_REF} --commit-hash ${GITHUB_SHA}
# `github.head_ref` is only set during pull requests and for manual runs or tags we use `main` to deploy to production
command: pages deploy playground/dist --project-name=ruff-playground --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}

View File

@@ -7,12 +7,15 @@ on:
description: "The version to tag, without the leading 'v'. If omitted, will initiate a dry run (no uploads)."
type: string
sha:
description: "Optionally, the full sha of the commit to be released"
description: "The full sha of the commit to be released. If omitted, the latest commit on the default branch will be used."
default: ""
type: string
pull_request:
paths:
# When we change pyproject.toml, we want to ensure that the maturin builds still work
- pyproject.toml
# And when we change this workflow itself...
- .github/workflows/release.yaml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -31,6 +34,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -57,6 +62,8 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -95,6 +102,8 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -141,6 +150,8 @@ jobs:
arch: x64
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -187,6 +198,8 @@ jobs:
- i686-unknown-linux-gnu
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -244,6 +257,8 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -297,6 +312,8 @@ jobs:
- i686-unknown-linux-musl
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -351,6 +368,8 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -399,8 +418,21 @@ jobs:
if: ${{ inputs.tag }}
steps:
- uses: actions/checkout@v4
with:
ref: main # We checkout the main branch to check for the commit
- name: Check main branch
if: ${{ inputs.sha }}
run: |
# Fetch the main branch since a shallow checkout is used by default
git fetch origin main --unshallow
if ! git branch --contains ${{ inputs.sha }} | grep -E '(^|\s)main$'; then
echo "The specified sha is not on the main branch" >&2
exit 1
fi
- name: Check tag consistency
run: |
# Switch to the commit we want to release
git checkout ${{ inputs.sha }}
version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
if [ "${{ inputs.tag }}" != "${version}" ]; then
echo "The input tag does not match the version from pyproject.toml:" >&2
@@ -410,18 +442,6 @@ jobs:
else
echo "Releasing ${version}"
fi
- name: Check SHA consistency
if: ${{ inputs.sha }}
run: |
git_sha=$(git rev-parse HEAD)
if [ "${{ inputs.sha }}" != "${git_sha}" ]; then
echo "The specified sha does not match the git checkout" >&2
echo "${{ inputs.sha }}" >&2
echo "${git_sha}" >&2
exit 1
else
echo "Releasing ${git_sha}"
fi
upload-release:
name: Upload to PyPI
@@ -465,6 +485,8 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- name: git tag
run: |
git config user.email "hey@astral.sh"

8
.gitignore vendored
View File

@@ -1,5 +1,5 @@
# Benchmarking cpython (CONTRIBUTING.md)
crates/ruff/resources/test/cpython
crates/ruff_linter/resources/test/cpython
# generate_mkdocs.py
mkdocs.generated.yml
# check_ecosystem.py
@@ -208,3 +208,9 @@ cython_debug/
# VIM
.*.sw?
.sw?
# Custom re-inclusions for the resolver test cases
!crates/ruff_python_resolver/resources/test/airflow/venv/
!crates/ruff_python_resolver/resources/test/airflow/venv/lib
!crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/_watchdog_fsevents.cpython-311-darwin.so
!crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/orjson/orjson.cpython-311-darwin.so

View File

@@ -13,3 +13,8 @@ MD041: false
# MD013/line-length
MD013: false
# MD024/no-duplicate-heading
MD024:
# Allow when nested under different parents e.g. CHANGELOG.md
allow_different_nesting: true

View File

@@ -2,8 +2,8 @@ fail_fast: true
exclude: |
(?x)^(
crates/ruff/resources/.*|
crates/ruff/src/rules/.*/snapshots/.*|
crates/ruff_linter/resources/.*|
crates/ruff_linter/src/rules/.*/snapshots/.*|
crates/ruff_cli/resources/.*|
crates/ruff_python_formatter/resources/.*|
crates/ruff_python_formatter/tests/snapshots/.*|
@@ -23,6 +23,11 @@ repos:
- id: mdformat
additional_dependencies:
- mdformat-mkdocs
- mdformat-admon
exclude: |
(?x)^(
docs/formatter/black.md
)$
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.33.0
@@ -50,7 +55,7 @@ repos:
require_serial: true
exclude: |
(?x)^(
crates/ruff/resources/.*|
crates/ruff_linter/resources/.*|
crates/ruff_python_formatter/resources/.*
)$

View File

@@ -1,5 +1,35 @@
# Breaking Changes
## 0.1.0
### The deprecated `format` setting has been removed
Ruff previously used the `format` setting, `--format` CLI option, and `RUFF_FORMAT` environment variable to
configure the output format of the CLI. This usage was deprecated in `v0.0.291` — the `format` setting is now used
to control Ruff's code formatting. As of this release:
- The `format` setting cannot be used to configure the output format, use `output-format` instead
- The `RUFF_FORMAT` environment variable is ignored, use `RUFF_OUTPUT_FORMAT` instead
- The `--format` option has been removed from `ruff check`, use `--output-format` instead
### Unsafe fixes are not applied by default ([#7769](https://github.com/astral-sh/ruff/pull/7769))
Ruff labels fixes as "safe" and "unsafe". The meaning and intent of your code will be retained when applying safe
fixes, but the meaning could be changed when applying unsafe fixes. Previously, unsafe fixes were always displayed
and applied when fixing was enabled. Now, unsafe fixes are hidden by default and not applied. The `--unsafe-fixes`
flag or `unsafe-fixes` configuration option can be used to enable unsafe fixes.
See the [docs](https://docs.astral.sh/ruff/configuration/#fix-safety) for details.
### Remove formatter-conflicting rules from the default rule set ([#7900](https://github.com/astral-sh/ruff/pull/7900))
Previously, Ruff enabled all implemented rules in Pycodestyle (`E`) by default. Ruff now only includes the
Pycodestyle prefixes `E4`, `E7`, and `E9` to exclude rules that conflict with automatic formatters. Consequently,
the stable rule set no longer includes `line-too-long` (`E501`) and `mixed-spaces-and-tabs` (`E101`). Other
excluded Pycodestyle rules include whitespace enforcement in `E1` and `E2`; these rules are currently in preview, and are already omitted by default.
This change only affects those using Ruff under its default rule set. Users that include `E` in their `select` will experience no change in behavior.
## 0.0.288
### Remove support for emoji identifiers ([#7212](https://github.com/astral-sh/ruff/pull/7212))
@@ -299,4 +329,4 @@ default.
`pyproject.toml` files are now resolved hierarchically, such that for each Python file, we find
the first `pyproject.toml` file in its path, and use that to determine its lint settings.
See the [documentation](https://beta.ruff.rs/docs/configuration/#python-file-discovery) for more.
See the [documentation](https://docs.astral.sh/ruff/configuration/#python-file-discovery) for more.

164
CHANGELOG.md Normal file
View File

@@ -0,0 +1,164 @@
# Changelog
## 0.1.1
### Rule changes
- Add unsafe fix for `escape-sequence-in-docstring` (`D301`) ([#7970](https://github.com/astral-sh/ruff/pull/7970))
### Configuration
- Respect `#(deprecated)` attribute in configuration options ([#8035](https://github.com/astral-sh/ruff/pull/8035))
- Add `[format|lint].exclude` options ([#8000](https://github.com/astral-sh/ruff/pull/8000))
- Respect `tab-size` setting in formatter ([#8006](https://github.com/astral-sh/ruff/pull/8006))
- Add `lint.preview` ([#8002](https://github.com/astral-sh/ruff/pull/8002))
### Preview features
- \[`pylint`\] Implement `literal-membership` (`PLR6201`) ([#7973](https://github.com/astral-sh/ruff/pull/7973))
- \[`pylint`\] Implement `too-many-boolean-expressions` (`PLR0916`) ([#7975](https://github.com/astral-sh/ruff/pull/7975))
- \[`pylint`\] Implement `misplaced-bare-raise` (`E0704`) ([#7961](https://github.com/astral-sh/ruff/pull/7961))
- \[`pylint`\] Implement `global-at-module-level` (`W0604`) ([#8058](https://github.com/astral-sh/ruff/pull/8058))
- \[`pylint`\] Implement `unspecified-encoding` (`PLW1514`) ([#7939](https://github.com/astral-sh/ruff/pull/7939))
- Add fix for `triple-single-quotes` (`D300`) ([#7967](https://github.com/astral-sh/ruff/pull/7967))
### Formatter
- New code style badge for `ruff format` ([#7878](https://github.com/astral-sh/ruff/pull/7878))
- Fix comments outside expression parentheses ([#7873](https://github.com/astral-sh/ruff/pull/7873))
- Add `--target-version` to `ruff format` ([#8055](https://github.com/astral-sh/ruff/pull/8055))
- Skip over parentheses when detecting `in` keyword ([#8054](https://github.com/astral-sh/ruff/pull/8054))
- Add `--diff` option to `ruff format` ([#7937](https://github.com/astral-sh/ruff/pull/7937))
- Insert newline after nested function or class statements ([#7946](https://github.com/astral-sh/ruff/pull/7946))
- Use `pass` over ellipsis in non-function/class contexts ([#8049](https://github.com/astral-sh/ruff/pull/8049))
### Bug fixes
- Lazily evaluate all PEP 695 type alias values ([#8033](https://github.com/astral-sh/ruff/pull/8033))
- Avoid failed assertion when showing fixes from stdin ([#8029](https://github.com/astral-sh/ruff/pull/8029))
- Avoid flagging HTTP and HTTPS literals in urllib-open ([#8046](https://github.com/astral-sh/ruff/pull/8046))
- Avoid flagging `bad-dunder-method-name` for `_` ([#8015](https://github.com/astral-sh/ruff/pull/8015))
- Remove Python 2-only methods from `URLOpen` audit ([#8047](https://github.com/astral-sh/ruff/pull/8047))
- Use set bracket replacement for `iteration-over-set` to preserve whitespace and comments ([#8001](https://github.com/astral-sh/ruff/pull/8001))
### Documentation
- Update tutorial to match revised Ruff defaults ([#8066](https://github.com/astral-sh/ruff/pull/8066))
- Update rule `B005` docs ([#8028](https://github.com/astral-sh/ruff/pull/8028))
- Update GitHub actions example in docs to use `--output-format` ([#8014](https://github.com/astral-sh/ruff/pull/8014))
- Document `lint.preview` and `format.preview` ([#8032](https://github.com/astral-sh/ruff/pull/8032))
- Clarify that new rules should be added to `RuleGroup::Preview`. ([#7989](https://github.com/astral-sh/ruff/pull/7989))
## 0.1.0
This is the first release which uses the `CHANGELOG` file. See [GitHub Releases](https://github.com/astral-sh/ruff/releases) for prior changelog entries.
Read Ruff's new [versioning policy](https://docs.astral.sh/ruff/versioning/).
### Breaking changes
- Unsafe fixes are no longer displayed or applied without opt-in ([#7769](https://github.com/astral-sh/ruff/pull/7769))
- Drop formatting specific rules from the default set ([#7900](https://github.com/astral-sh/ruff/pull/7900))
- The deprecated `format` setting has been removed ([#7984](https://github.com/astral-sh/ruff/pull/7984))
- The `format` setting cannot be used to configure the output format, use `output-format` instead
- The `RUFF_FORMAT` environment variable is ignored, use `RUFF_OUTPUT_FORMAT` instead
- The `--format` option has been removed from `ruff check`, use `--output-format` instead
### Rule changes
- Extend `reimplemented-starmap` (`FURB140`) to catch calls with a single and starred argument ([#7768](https://github.com/astral-sh/ruff/pull/7768))
- Improve cases covered by `RUF015` ([#7848](https://github.com/astral-sh/ruff/pull/7848))
- Update `SIM15` to allow `open` followed by `close` ([#7916](https://github.com/astral-sh/ruff/pull/7916))
- Respect `msgspec.Struct` default-copy semantics in `RUF012` ([#7786](https://github.com/astral-sh/ruff/pull/7786))
- Add `sqlalchemy` methods to \`flake8-boolean-trap\`\` exclusion list ([#7874](https://github.com/astral-sh/ruff/pull/7874))
- Add fix for `PLR1714` ([#7910](https://github.com/astral-sh/ruff/pull/7910))
- Add fix for `PIE804` ([#7884](https://github.com/astral-sh/ruff/pull/7884))
- Add fix for `PLC0208` ([#7887](https://github.com/astral-sh/ruff/pull/7887))
- Add fix for `PYI055` ([#7886](https://github.com/astral-sh/ruff/pull/7886))
- Update `non-pep695-type-alias` to require `--unsafe-fixes` outside of stub files ([#7836](https://github.com/astral-sh/ruff/pull/7836))
- Improve fix message for `UP018` ([#7913](https://github.com/astral-sh/ruff/pull/7913))
- Update `PLW3201` to support `Enum` [sunder names](https://docs.python.org/3/library/enum.html#supported-sunder-names) ([#7987](https://github.com/astral-sh/ruff/pull/7987))
### Preview features
- Only show warnings for empty preview selectors when enabling rules ([#7842](https://github.com/astral-sh/ruff/pull/7842))
- Add `unnecessary-key-check` to simplify `key in dct and dct[key]` to `dct.get(key)` ([#7895](https://github.com/astral-sh/ruff/pull/7895))
- Add `assignment-in-assert` to prevent walrus expressions in assert statements ([#7856](https://github.com/astral-sh/ruff/pull/7856))
- \[`refurb`\] Add `single-item-membership-test` (`FURB171`) ([#7815](https://github.com/astral-sh/ruff/pull/7815))
- \[`pylint`\] Add `and-or-ternary` (`R1706`) ([#7811](https://github.com/astral-sh/ruff/pull/7811))
_New rules are added in [preview](https://docs.astral.sh/ruff/preview/)._
### Configuration
- Add `unsafe-fixes` setting ([#7769](https://github.com/astral-sh/ruff/pull/7769))
- Add `extend-safe-fixes` and `extend-unsafe-fixes` for promoting and demoting fixes ([#7841](https://github.com/astral-sh/ruff/pull/7841))
### CLI
- Added `--unsafe-fixes` option for opt-in to display and apply unsafe fixes ([#7769](https://github.com/astral-sh/ruff/pull/7769))
- Fix use of deprecated `--format` option in warning ([#7837](https://github.com/astral-sh/ruff/pull/7837))
- Show changed files when running under `--check` ([#7788](https://github.com/astral-sh/ruff/pull/7788))
- Write summary messages to stderr when fixing via stdin instead of omitting them ([#7838](https://github.com/astral-sh/ruff/pull/7838))
- Update fix summary message in `check --diff` to include unsafe fix hints ([#7790](https://github.com/astral-sh/ruff/pull/7790))
- Add notebook `cell` field to JSON output format ([#7664](https://github.com/astral-sh/ruff/pull/7664))
- Rename applicability levels to `Safe`, `Unsafe`, and `Display` ([#7843](https://github.com/astral-sh/ruff/pull/7843))
### Bug fixes
- Fix bug where f-strings were allowed in match pattern literal ([#7857](https://github.com/astral-sh/ruff/pull/7857))
- Fix `SIM110` with a yield in the condition ([#7801](https://github.com/astral-sh/ruff/pull/7801))
- Preserve trailing comments in `C414` fixes ([#7775](https://github.com/astral-sh/ruff/pull/7775))
- Check sequence type before triggering `unnecessary-enumerate` `len` suggestion ([#7781](https://github.com/astral-sh/ruff/pull/7781))
- Use correct start location for class/function clause header ([#7802](https://github.com/astral-sh/ruff/pull/7802))
- Fix incorrect fixes for `SIM101` ([#7798](https://github.com/astral-sh/ruff/pull/7798))
- Format comment before parameter default correctly ([#7870](https://github.com/astral-sh/ruff/pull/7870))
- Fix `E251` false positive inside f-strings ([#7894](https://github.com/astral-sh/ruff/pull/7894))
- Allow bindings to be created and referenced within annotations ([#7885](https://github.com/astral-sh/ruff/pull/7885))
- Show per-cell diffs when analyzing notebooks over `stdin` ([#7789](https://github.com/astral-sh/ruff/pull/7789))
- Avoid curly brace escape in f-string format spec ([#7780](https://github.com/astral-sh/ruff/pull/7780))
- Fix lexing single-quoted f-string with multi-line format spec ([#7787](https://github.com/astral-sh/ruff/pull/7787))
- Consider nursery rules to be in-preview for `ruff rule` ([#7812](https://github.com/astral-sh/ruff/pull/7812))
- Report precise location for invalid conversion flag ([#7809](https://github.com/astral-sh/ruff/pull/7809))
- Visit pattern match guard as a boolean test ([#7911](https://github.com/astral-sh/ruff/pull/7911))
- Respect `--unfixable` in `ISC` rules ([#7917](https://github.com/astral-sh/ruff/pull/7917))
- Fix edge case with `PIE804` ([#7922](https://github.com/astral-sh/ruff/pull/7922))
- Show custom message in `PTH118` for `Path.joinpath` with starred arguments ([#7852](https://github.com/astral-sh/ruff/pull/7852))
- Fix false negative in `outdated-version-block` when using greater than comparisons ([#7920](https://github.com/astral-sh/ruff/pull/7920))
- Avoid converting f-strings within Django `gettext` calls ([#7898](https://github.com/astral-sh/ruff/pull/7898))
- Fix false positive in `PLR6301` ([#7933](https://github.com/astral-sh/ruff/pull/7933))
- Treat type aliases as typing-only expressions e.g. resolves false positive in `TCH004` ([#7968](https://github.com/astral-sh/ruff/pull/7968))
- Resolve `cache-dir` relative to project root ([#7962](https://github.com/astral-sh/ruff/pull/7962))
- Respect subscripted base classes in type-checking rules e.g. resolves false positive in `TCH003` ([#7954](https://github.com/astral-sh/ruff/pull/7954))
- Fix JSON schema limit for `line-length` ([#7883](https://github.com/astral-sh/ruff/pull/7883))
- Fix commented-out `coalesce` keyword ([#7876](https://github.com/astral-sh/ruff/pull/7876))
### Documentation
- Document `reimplemented-starmap` performance effects ([#7846](https://github.com/astral-sh/ruff/pull/7846))
- Default to following the system dark/light mode ([#7888](https://github.com/astral-sh/ruff/pull/7888))
- Add documentation for fixes ([#7901](https://github.com/astral-sh/ruff/pull/7901))
- Fix typo in docs of `PLR6301` ([#7831](https://github.com/astral-sh/ruff/pull/7831))
- Update `UP038` docs to note that it results in slower code ([#7872](https://github.com/astral-sh/ruff/pull/7872))
- crlf -> cr-lf ([#7766](https://github.com/astral-sh/ruff/pull/7766))
- Add an example of an unsafe fix ([#7924](https://github.com/astral-sh/ruff/pull/7924))
- Fix documented examples for `unnecessary-subscript-reversal` ([#7774](https://github.com/astral-sh/ruff/pull/7774))
- Correct error in tuple example in ruff formatter docs ([#7822](https://github.com/astral-sh/ruff/pull/7822))
- Add versioning policy to documentation ([#7923](https://github.com/astral-sh/ruff/pull/7923))
- Fix invalid code in `FURB177` example ([#7832](https://github.com/astral-sh/ruff/pull/7832))
### Formatter
- Less scary `ruff format` message ([#7867](https://github.com/astral-sh/ruff/pull/7867))
- Remove spaces from import statements ([#7859](https://github.com/astral-sh/ruff/pull/7859))
- Formatter quoting for f-strings with triple quotes ([#7826](https://github.com/astral-sh/ruff/pull/7826))
- Update `ruff_python_formatter` generate.py comment ([#7850](https://github.com/astral-sh/ruff/pull/7850))
- Document one-call chaining deviation ([#7767](https://github.com/astral-sh/ruff/pull/7767))
- Allow f-string modifications in line-shrinking cases ([#7818](https://github.com/astral-sh/ruff/pull/7818))
- Add trailing comment deviation to README ([#7827](https://github.com/astral-sh/ruff/pull/7827))
- Add trailing zero between dot and exponential ([#7956](https://github.com/astral-sh/ruff/pull/7956))
- Force parentheses for power operations in unary expressions ([#7955](https://github.com/astral-sh/ruff/pull/7955))
### Playground
- Fix playground `Quick Fix` action ([#7824](https://github.com/astral-sh/ruff/pull/7824))

View File

@@ -112,11 +112,11 @@ Ruff is structured as a monorepo with a [flat crate structure](https://matklad.g
such that all crates are contained in a flat `crates` directory.
The vast majority of the code, including all lint rules, lives in the `ruff` crate (located at
`crates/ruff`). As a contributor, that's the crate that'll be most relevant to you.
`crates/ruff_linter`). As a contributor, that's the crate that'll be most relevant to you.
At time of writing, the repository includes the following crates:
- `crates/ruff`: library crate containing all lint rules and the core logic for running them.
- `crates/ruff_linter`: library crate containing all lint rules and the core logic for running them.
If you're working on a rule, this is the crate for you.
- `crates/ruff_benchmark`: binary crate for running micro-benchmarks.
- `crates/ruff_cache`: library crate for caching lint results.
@@ -153,7 +153,7 @@ At a high level, the steps involved in adding a new lint rule are as follows:
1. Determine a name for the new rule as per our [rule naming convention](#rule-naming-convention)
(e.g., `AssertFalse`, as in, "allow `assert False`").
1. Create a file for your rule (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/assert_false.rs`).
1. Create a file for your rule (e.g., `crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_false.rs`).
1. In that file, define a violation struct (e.g., `pub struct AssertFalse`). You can grep for
`#[violation]` to see examples.
@@ -162,21 +162,22 @@ At a high level, the steps involved in adding a new lint rule are as follows:
(e.g., `pub(crate) fn assert_false`) based on whatever inputs are required for the rule (e.g.,
an `ast::StmtAssert` node).
1. Define the logic for invoking the diagnostic in `crates/ruff/src/checkers/ast/analyze` (for
AST-based rules), `crates/ruff/src/checkers/tokens.rs` (for token-based rules),
`crates/ruff/src/checkers/physical_lines.rs` (for text-based rules),
`crates/ruff/src/checkers/filesystem.rs` (for filesystem-based rules), etc. For AST-based rules,
1. Define the logic for invoking the diagnostic in `crates/ruff_linter/src/checkers/ast/analyze` (for
AST-based rules), `crates/ruff_linter/src/checkers/tokens.rs` (for token-based rules),
`crates/ruff_linter/src/checkers/physical_lines.rs` (for text-based rules),
`crates/ruff_linter/src/checkers/filesystem.rs` (for filesystem-based rules), etc. For AST-based rules,
you'll likely want to modify `analyze/statement.rs` (if your rule is based on analyzing
statements, like imports) or `analyze/expression.rs` (if your rule is based on analyzing
expressions, like function calls).
1. Map the violation struct to a rule code in `crates/ruff/src/codes.rs` (e.g., `B011`).
1. Map the violation struct to a rule code in `crates/ruff_linter/src/codes.rs` (e.g., `B011`). New rules
should be added in `RuleGroup::Preview`.
1. Add proper [testing](#rule-testing-fixtures-and-snapshots) for your rule.
1. Update the generated files (documentation and generated code).
To trigger the violation, you'll likely want to augment the logic in `crates/ruff/src/checkers/ast.rs`
To trigger the violation, you'll likely want to augment the logic in `crates/ruff_linter/src/checkers/ast.rs`
to call your new function at the appropriate time and with the appropriate inputs. The `Checker`
defined therein is a Python AST visitor, which iterates over the AST, building up a semantic model,
and calling out to lint rule analyzer functions as it goes.
@@ -204,7 +205,7 @@ As such, rule names should...
For example, `AssertFalse` guards against `assert False` statements.
- _Not_ contain instructions on how to fix the violation, which instead belong in the rule
documentation and the `autofix_title`.
documentation and the `fix_title`.
- _Not_ contain a redundant prefix, like `Disallow` or `Banned`, which are already implied by the
convention.
@@ -221,7 +222,7 @@ Ruff's output for each fixture, which you can then commit alongside your changes
Once you've completed the code for the rule itself, you can define tests with the following steps:
1. Add a Python file to `crates/ruff/resources/test/fixtures/[linter]` that contains the code you
1. Add a Python file to `crates/ruff_linter/resources/test/fixtures/[linter]` that contains the code you
want to test. The file name should match the rule name (e.g., `E402.py`), and it should include
examples of both violations and non-violations.
@@ -230,16 +231,16 @@ Once you've completed the code for the rule itself, you can define tests with th
For example, if you're adding a new rule named `E402`, you would run:
```shell
cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402
cargo run -p ruff_cli -- check crates/ruff_linter/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402
```
**Note:** Only a subset of rules are enabled by default. When testing a new rule, ensure that
you activate it by adding `--select ${rule_code}` to the command.
1. Add the test to the relevant `crates/ruff/src/rules/[linter]/mod.rs` file. If you're contributing
1. Add the test to the relevant `crates/ruff_linter/src/rules/[linter]/mod.rs` file. If you're contributing
a rule to a pre-existing set, you should be able to find a similar example to pattern-match
against. If you're adding a new linter, you'll need to create a new `mod.rs` file (see,
e.g., `crates/ruff/src/rules/flake8_bugbear/mod.rs`)
e.g., `crates/ruff_linter/src/rules/flake8_bugbear/mod.rs`)
1. Run `cargo test`. Your test will fail, but you'll be prompted to follow-up
with `cargo insta review`. Run `cargo insta review`, review and accept the generated snapshot,
@@ -251,25 +252,24 @@ Once you've completed the code for the rule itself, you can define tests with th
Ruff's user-facing settings live in a few different places.
First, the command-line options are defined via the `Cli` struct in `crates/ruff/src/cli.rs`.
First, the command-line options are defined via the `Args` struct in `crates/ruff_cli/src/args.rs`.
Second, the `pyproject.toml` options are defined in `crates/ruff/src/settings/options.rs` (via the
`Options` struct), `crates/ruff/src/settings/configuration.rs` (via the `Configuration` struct), and
`crates/ruff/src/settings/mod.rs` (via the `Settings` struct). These represent, respectively: the
schema used to parse the `pyproject.toml` file; an internal, intermediate representation; and the
final, internal representation used to power Ruff.
Second, the `pyproject.toml` options are defined in `crates/ruff_workspace/src/options.rs` (via the
`Options` struct), `crates/ruff_workspace/src/configuration.rs` (via the `Configuration` struct),
and `crates/ruff_workspace/src/settings.rs` (via the `Settings` struct), which then includes
the `LinterSettings` struct as a field.
These represent, respectively: the schema used to parse the `pyproject.toml` file; an internal,
intermediate representation; and the final, internal representation used to power Ruff.
To add a new configuration option, you'll likely want to modify these latter few files (along with
`cli.rs`, if appropriate). If you want to pattern-match against an existing example, grep for
`arg.rs`, if appropriate). If you want to pattern-match against an existing example, grep for
`dummy_variable_rgx`, which defines a regular expression to match against acceptable unused
variables (e.g., `_`).
Note that plugin-specific configuration options are defined in their own modules (e.g.,
`crates/ruff/src/flake8_unused_arguments/settings.rs`).
You may also want to add the new configuration option to the `flake8-to-ruff` tool, which is
responsible for converting `flake8` configuration files to Ruff's TOML format. This logic
lives in `crates/ruff/src/flake8_to_ruff/converter.rs`.
`Settings` in `crates/ruff_linter/src/flake8_unused_arguments/settings.rs` coupled with
`Flake8UnusedArgumentsOptions` in `crates/ruff_workspace/src/options.rs`).
Finally, regenerate the documentation and generated code with `cargo dev generate-all`.
@@ -362,46 +362,46 @@ First, clone [CPython](https://github.com/python/cpython). It's a large and dive
which makes it a good target for benchmarking.
```shell
git clone --branch 3.10 https://github.com/python/cpython.git crates/ruff/resources/test/cpython
git clone --branch 3.10 https://github.com/python/cpython.git crates/ruff_linter/resources/test/cpython
```
To benchmark the release build:
```shell
cargo build --release && hyperfine --warmup 10 \
"./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache -e" \
"./target/release/ruff ./crates/ruff/resources/test/cpython/ -e"
"./target/release/ruff ./crates/ruff_linter/resources/test/cpython/ --no-cache -e" \
"./target/release/ruff ./crates/ruff_linter/resources/test/cpython/ -e"
Benchmark 1: ./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache
Benchmark 1: ./target/release/ruff ./crates/ruff_linter/resources/test/cpython/ --no-cache
Time (mean ± σ): 293.8 ms ± 3.2 ms [User: 2384.6 ms, System: 90.3 ms]
Range (min … max): 289.9 ms … 301.6 ms 10 runs
Benchmark 2: ./target/release/ruff ./crates/ruff/resources/test/cpython/
Benchmark 2: ./target/release/ruff ./crates/ruff_linter/resources/test/cpython/
Time (mean ± σ): 48.0 ms ± 3.1 ms [User: 65.2 ms, System: 124.7 ms]
Range (min … max): 45.0 ms … 66.7 ms 62 runs
Summary
'./target/release/ruff ./crates/ruff/resources/test/cpython/' ran
6.12 ± 0.41 times faster than './target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache'
'./target/release/ruff ./crates/ruff_linter/resources/test/cpython/' ran
6.12 ± 0.41 times faster than './target/release/ruff ./crates/ruff_linter/resources/test/cpython/ --no-cache'
```
To benchmark against the ecosystem's existing tools:
```shell
hyperfine --ignore-failure --warmup 5 \
"./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache" \
"pyflakes crates/ruff/resources/test/cpython" \
"./target/release/ruff ./crates/ruff_linter/resources/test/cpython/ --no-cache" \
"pyflakes crates/ruff_linter/resources/test/cpython" \
"autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython" \
"pycodestyle crates/ruff/resources/test/cpython" \
"flake8 crates/ruff/resources/test/cpython"
"pycodestyle crates/ruff_linter/resources/test/cpython" \
"flake8 crates/ruff_linter/resources/test/cpython"
Benchmark 1: ./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache
Benchmark 1: ./target/release/ruff ./crates/ruff_linter/resources/test/cpython/ --no-cache
Time (mean ± σ): 294.3 ms ± 3.3 ms [User: 2467.5 ms, System: 89.6 ms]
Range (min … max): 291.1 ms … 302.8 ms 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 2: pyflakes crates/ruff/resources/test/cpython
Benchmark 2: pyflakes crates/ruff_linter/resources/test/cpython
Time (mean ± σ): 15.786 s ± 0.143 s [User: 15.560 s, System: 0.214 s]
Range (min … max): 15.640 s … 16.157 s 10 runs
@@ -411,31 +411,31 @@ Benchmark 3: autoflake --recursive --expand-star-imports --remove-all-unused-imp
Time (mean ± σ): 6.175 s ± 0.169 s [User: 54.102 s, System: 1.057 s]
Range (min … max): 5.950 s … 6.391 s 10 runs
Benchmark 4: pycodestyle crates/ruff/resources/test/cpython
Benchmark 4: pycodestyle crates/ruff_linter/resources/test/cpython
Time (mean ± σ): 46.921 s ± 0.508 s [User: 46.699 s, System: 0.202 s]
Range (min … max): 46.171 s … 47.863 s 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 5: flake8 crates/ruff/resources/test/cpython
Benchmark 5: flake8 crates/ruff_linter/resources/test/cpython
Time (mean ± σ): 12.260 s ± 0.321 s [User: 102.934 s, System: 1.230 s]
Range (min … max): 11.848 s … 12.933 s 10 runs
Warning: Ignoring non-zero exit code.
Summary
'./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache' ran
'./target/release/ruff ./crates/ruff_linter/resources/test/cpython/ --no-cache' ran
20.98 ± 0.62 times faster than 'autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython'
41.66 ± 1.18 times faster than 'flake8 crates/ruff/resources/test/cpython'
53.64 ± 0.77 times faster than 'pyflakes crates/ruff/resources/test/cpython'
159.43 ± 2.48 times faster than 'pycodestyle crates/ruff/resources/test/cpython'
41.66 ± 1.18 times faster than 'flake8 crates/ruff_linter/resources/test/cpython'
53.64 ± 0.77 times faster than 'pyflakes crates/ruff_linter/resources/test/cpython'
159.43 ± 2.48 times faster than 'pycodestyle crates/ruff_linter/resources/test/cpython'
```
To benchmark a subset of rules, e.g. `LineTooLong` and `DocLineTooLong`:
```shell
cargo build --release && hyperfine --warmup 10 \
"./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache -e --select W505,E501"
"./target/release/ruff ./crates/ruff_linter/resources/test/cpython/ --no-cache -e --select W505,E501"
```
You can run `poetry install` from `./scripts/benchmarks` to create a working environment for the
@@ -468,10 +468,10 @@ rm Lib/test/bad_coding.py \
Lib/test/test_typing.py
```
Then, from `crates/ruff/resources/test/cpython`, run: `time pylint -j 0 -E $(git ls-files '*.py')`. This
Then, from `crates/ruff_linter/resources/test/cpython`, run: `time pylint -j 0 -E $(git ls-files '*.py')`. This
will execute Pylint with maximum parallelism and only report errors.
To benchmark Pyupgrade, run the following from `crates/ruff/resources/test/cpython`:
To benchmark Pyupgrade, run the following from `crates/ruff_linter/resources/test/cpython`:
```shell
hyperfine --ignore-failure --warmup 5 --prepare "git reset --hard HEAD" \
@@ -719,8 +719,8 @@ Module {
- `cargo dev generate-cli-help`, `cargo dev generate-docs` and `cargo dev generate-json-schema`:
Update just `docs/configuration.md`, `docs/rules` and `ruff.schema.json` respectively.
- `cargo dev generate-options`: Generate a markdown-compatible table of all `pyproject.toml`
options. Used for <https://beta.ruff.rs/docs/settings/>
- `cargo dev generate-rules-table`: Generate a markdown-compatible table of all rules. Used for <https://beta.ruff.rs/docs/rules/>
options. Used for <https://docs.astral.sh/ruff/settings/>.
- `cargo dev generate-rules-table`: Generate a markdown-compatible table of all rules. Used for <https://docs.astral.sh/ruff/rules/>.
- `cargo dev round-trip <python file or jupyter notebook>`: Read a Python file or Jupyter Notebook,
parse it, serialize the parsed representation and write it back. Used to check how good our
representation is so that fixes don't rewrite irrelevant parts of a file.
@@ -778,7 +778,7 @@ To understand Ruff's import categorization system, we first need to define two c
- "Package root": The top-most directory defining the Python package that includes a given Python
file. To find the package root for a given Python file, traverse up its parent directories until
you reach a parent directory that doesn't contain an `__init__.py` file (and isn't marked as
a [namespace package](https://beta.ruff.rs/docs/settings/#namespace-packages)); take the directory
a [namespace package](https://docs.astral.sh/ruff/settings/#namespace-packages)); take the directory
just before that, i.e., the first directory in the package.
For example, given:
@@ -867,7 +867,7 @@ There are three ways in which an import can be categorized as "first-party":
package (e.g., `from foo import bar` or `import foo.bar`), they'll be classified as first-party
automatically. This check is as simple as comparing the first segment of the current file's
module path to the first segment of the import.
1. **Source roots**: Ruff supports a `[src](https://beta.ruff.rs/docs/settings/#src)` setting, which
1. **Source roots**: Ruff supports a `[src](https://docs.astral.sh/ruff/settings/#src)` setting, which
sets the directories to scan when identifying first-party imports. The algorithm is
straightforward: given an import, like `import foo`, iterate over the directories enumerated in
the `src` setting and, for each directory, check for the existence of a subdirectory `foo` or a

884
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,8 +5,8 @@ resolver = "2"
[workspace.package]
edition = "2021"
rust-version = "1.71"
homepage = "https://beta.ruff.rs/docs"
documentation = "https://beta.ruff.rs/docs"
homepage = "https://docs.astral.sh/ruff"
documentation = "https://docs.astral.sh/ruff"
repository = "https://github.com/astral-sh/ruff"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
license = "MIT"
@@ -14,50 +14,47 @@ license = "MIT"
[workspace.dependencies]
anyhow = { version = "1.0.69" }
bitflags = { version = "2.3.1" }
chrono = { version = "0.4.23", default-features = false, features = ["clock"] }
clap = { version = "4.1.8", features = ["derive"] }
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
clap = { version = "4.4.6", features = ["derive"] }
colored = { version = "2.0.0" }
filetime = { version = "0.2.20" }
glob = { version = "0.3.1" }
globset = { version = "0.4.10" }
ignore = { version = "0.4.20" }
insta = { version = "1.31.0", feature = ["filters", "glob"] }
is-macro = { version = "0.2.2" }
itertools = { version = "0.10.5" }
insta = { version = "1.34.0", feature = ["filters", "glob"] }
is-macro = { version = "0.3.0" }
itertools = { version = "0.11.0" }
libcst = { version = "1.1.0", default-features = false }
log = { version = "0.4.17" }
memchr = "2.5.0"
num-bigint = { version = "0.4.3" }
num-traits = { version = "0.2.15" }
memchr = { version = "2.6.4" }
once_cell = { version = "1.17.1" }
path-absolutize = { version = "3.0.14" }
proc-macro2 = { version = "1.0.51" }
path-absolutize = { version = "3.1.1" }
proc-macro2 = { version = "1.0.69" }
quote = { version = "1.0.23" }
regex = { version = "1.7.1" }
regex = { version = "1.10.2" }
rustc-hash = { version = "1.1.0" }
schemars = { version = "0.8.12" }
schemars = { version = "0.8.15" }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.93" }
serde_json = { version = "1.0.107" }
shellexpand = { version = "3.0.0" }
similar = { version = "2.2.1", features = ["inline"] }
smallvec = { version = "1.10.0" }
similar = { version = "2.3.0", features = ["inline"] }
smallvec = { version = "1.11.1" }
static_assertions = "1.1.0"
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
syn = { version = "2.0.15" }
test-case = { version = "3.0.0" }
thiserror = { version = "1.0.43" }
toml = { version = "0.7.2" }
tracing = "0.1.37"
tracing-indicatif = "0.3.4"
strum = { version = "0.25.0", features = ["strum_macros"] }
strum_macros = { version = "0.25.3" }
syn = { version = "2.0.38" }
test-case = { version = "3.2.1" }
thiserror = { version = "1.0.50" }
toml = { version = "0.7.8" }
tracing = { version = "0.1.40" }
tracing-indicatif = { version = "0.3.4" }
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
unicode-ident = "1.0.11"
unicode-width = "0.1.10"
unicode-ident = { version = "1.0.12" }
unicode_names2 = { version = "1.2.0" }
unicode-width = { version = "0.1.11" }
uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
wsl = { version = "0.1.0" }
# v1.0.1
libcst = { version = "0.1.0", default-features = false }
[profile.release]
lto = "fat"
codegen-units = 1

47
LICENSE
View File

@@ -1194,7 +1194,27 @@ are:
- flake8-self, licensed as follows:
"""
Freely Distributable
MIT License
Copyright (c) 2023 Korijn van Golen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-django, licensed under the GPL license.
@@ -1224,6 +1244,31 @@ are:
SOFTWARE.
"""
- flake8-logging, licensed as follows:
"""
MIT License
Copyright (c) 2023 Adam Johnson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- Pyright, licensed as follows:
"""
MIT License

127
README.md
View File

@@ -8,9 +8,9 @@
[![image](https://img.shields.io/pypi/pyversions/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![Actions status](https://github.com/astral-sh/ruff/workflows/CI/badge.svg)](https://github.com/astral-sh/ruff/actions)
[**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://beta.ruff.rs/docs/) | [**Playground**](https://play.ruff.rs/)
[**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
An extremely fast Python linter, written in Rust.
An extremely fast Python linter and code formatter, written in Rust.
<p align="center">
<picture align="center">
@@ -24,28 +24,27 @@ An extremely fast Python linter, written in Rust.
<i>Linting the CPython codebase from scratch.</i>
</p>
- ⚡️ 10-100x faster than existing linters
- ⚡️ 10-100x faster than existing linters (like Flake8) and formatters (like Black)
- 🐍 Installable via `pip`
- 🛠️ `pyproject.toml` support
- 🤝 Python 3.11 compatibility
- 🤝 Python 3.12 compatibility
- ⚖️ Drop-in parity with [Flake8](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8), isort, and Black
- 📦 Built-in caching, to avoid re-analyzing unchanged files
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
- 📏 Over [600 built-in rules](https://beta.ruff.rs/docs/rules/)
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the
built-in Flake8 rule set
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
- ⌨️ First-party [editor integrations](https://beta.ruff.rs/docs/editor-integrations/) for
- 🔧 Fix support, for automatic error correction (e.g., automatically remove unused imports)
- 📏 Over [700 built-in rules](https://docs.astral.sh/ruff/rules/), with native re-implementations
of popular Flake8 plugins, like flake8-bugbear
- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/editor-integrations/) for
[VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://beta.ruff.rs/docs/configuration/#pyprojecttoml-discovery)
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://docs.astral.sh/ruff/configuration/#pyprojecttoml-discovery)
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
functionality behind a single, common interface.
Ruff can be used to replace [Flake8](https://pypi.org/project/flake8/) (plus dozens of plugins),
[isort](https://pypi.org/project/isort/), [pydocstyle](https://pypi.org/project/pydocstyle/),
[yesqa](https://github.com/asottile/yesqa), [eradicate](https://pypi.org/project/eradicate/),
[pyupgrade](https://pypi.org/project/pyupgrade/), and [autoflake](https://pypi.org/project/autoflake/),
all while executing tens or hundreds of times faster than any individual tool.
[Black](https://github.com/psf/black), [isort](https://pypi.org/project/isort/),
[pydocstyle](https://pypi.org/project/pydocstyle/), [pyupgrade](https://pypi.org/project/pyupgrade/),
[autoflake](https://pypi.org/project/autoflake/), and more, all while executing tens or hundreds of
times faster than any individual tool.
Ruff is extremely actively developed and used in major open-source projects like:
@@ -98,7 +97,7 @@ developer of [Zulip](https://github.com/zulip/zulip):
## Table of Contents
For more, see the [documentation](https://beta.ruff.rs/docs/).
For more, see the [documentation](https://docs.astral.sh/ruff/).
1. [Getting Started](#getting-started)
1. [Configuration](#configuration)
@@ -111,7 +110,7 @@ For more, see the [documentation](https://beta.ruff.rs/docs/).
## Getting Started
For more, see the [documentation](https://beta.ruff.rs/docs/).
For more, see the [documentation](https://docs.astral.sh/ruff/).
### Installation
@@ -122,27 +121,42 @@ pip install ruff
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
and with [a variety of other package managers](https://beta.ruff.rs/docs/installation/).
and with [a variety of other package managers](https://docs.astral.sh/ruff/installation/).
### Usage
To run Ruff, try any of the following:
To run Ruff as a linter, try any of the following:
```shell
ruff check . # Lint all files in the current directory (and any subdirectories)
ruff check path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories)
ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code`
ruff check path/to/code/to/file.py # Lint `file.py`
ruff check . # Lint all files in the current directory (and any subdirectories).
ruff check path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories).
ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code`.
ruff check path/to/code/to/file.py # Lint `file.py`.
ruff check @arguments.txt # Lint using an input file, treating its contents as newline-delimited command-line arguments.
```
Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
Or, to run Ruff as a formatter:
```shell
ruff format . # Format all files in the current directory (and any subdirectories).
ruff format path/to/code/ # Format all files in `/path/to/code` (and any subdirectories).
ruff format path/to/code/*.py # Format all `.py` files in `/path/to/code`.
ruff format path/to/code/to/file.py # Format `file.py`.
ruff format @arguments.txt # Format using an input file, treating its contents as newline-delimited command-line arguments.
```
Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff-pre-commit`](https://github.com/astral-sh/ruff-pre-commit):
```yaml
# Run the Ruff linter.
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.289
rev: v0.1.1
hooks:
# Run the Ruff linter.
- id: ruff
# Run the Ruff formatter.
- id: ruff-format
```
Ruff can also be used as a [VS Code extension](https://github.com/astral-sh/ruff-vscode) or
@@ -165,21 +179,13 @@ jobs:
### Configuration
Ruff can be configured through a `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file (see:
[_Configuration_](https://beta.ruff.rs/docs/configuration/), or [_Settings_](https://beta.ruff.rs/docs/settings/)
[_Configuration_](https://docs.astral.sh/ruff/configuration/), or [_Settings_](https://docs.astral.sh/ruff/settings/)
for a complete list of all configuration options).
If left unspecified, the default configuration is equivalent to:
If left unspecified, Ruff's default configuration is equivalent to:
```toml
[tool.ruff]
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
select = ["E", "F"]
ignore = []
# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
unfixable = []
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
@@ -207,39 +213,57 @@ exclude = [
# Same as Black.
line-length = 88
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
indent-width = 4
# Assume Python 3.8
target-version = "py38"
[tool.ruff.mccabe]
# Unlike Flake8, default to a complexity level of 10.
max-complexity = 10
[tool.ruff.lint]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
select = ["E4", "E7", "E9", "F"]
ignore = []
# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
[tool.ruff.format]
# Like Black, use double quotes for strings.
quote-style = "double"
# Like Black, indent with spaces, rather than tabs.
indent-style = "space"
# Like Black, respect magic trailing commas.
magic-trailing-comma = "respect"
# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
```
Some configuration options can be provided via the command-line, such as those related to
rule enablement and disablement, file discovery, logging level, and more:
rule enablement and disablement, file discovery, and logging level:
```shell
ruff check path/to/code/ --select F401 --select F403 --quiet
```
See `ruff help` for more on Ruff's top-level commands, or `ruff help check` for more on the
linting command.
See `ruff help` for more on Ruff's top-level commands, or `ruff help check` and `ruff help format`
for more on the linting and formatting commands, respectively.
## Rules
<!-- Begin section: Rules -->
**Ruff supports over 600 lint rules**, many of which are inspired by popular tools like Flake8,
**Ruff supports over 700 lint rules**, many of which are inspired by popular tools like Flake8,
isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
Rust as a first-party feature.
By default, Ruff enables Flake8's `E` and `F` rules. Ruff supports all rules from the `F` category,
and a [subset](https://beta.ruff.rs/docs/rules/#error-e) of the `E` category, omitting those
stylistic rules made obsolete by the use of an autoformatter, like
By default, Ruff enables Flake8's `F` rules, along with a subset of the `E` rules, omitting any
stylistic rules that overlap with the use of a formatter, like `ruff format` or
[Black](https://github.com/psf/black).
If you're just getting started with Ruff, **the default rule set is a great place to start**: it
@@ -274,6 +298,7 @@ quality tools, including:
- [flake8-gettext](https://pypi.org/project/flake8-gettext/)
- [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/)
- [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions)
- [flake8-logging](https://pypi.org/project/flake8-logging/)
- [flake8-logging-format](https://pypi.org/project/flake8-logging-format/)
- [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420)
- [flake8-pie](https://pypi.org/project/flake8-pie/)
@@ -303,12 +328,12 @@ quality tools, including:
- [tryceratops](https://pypi.org/project/tryceratops/)
- [yesqa](https://pypi.org/project/yesqa/)
For a complete enumeration of the supported rules, see [_Rules_](https://beta.ruff.rs/docs/rules/).
For a complete enumeration of the supported rules, see [_Rules_](https://docs.astral.sh/ruff/rules/).
## Contributing
Contributions are welcome and highly appreciated. To get started, check out the
[**contributing guidelines**](https://beta.ruff.rs/docs/contributing/).
[**contributing guidelines**](https://docs.astral.sh/ruff/contributing/).
You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5).
@@ -330,7 +355,7 @@ In some cases, Ruff includes a "direct" Rust port of the corresponding tool.
We're grateful to the maintainers of these tools for their work, and for all
the value they've provided to the Python community.
Ruff's autoformatter is built on a fork of Rome's [`rome_formatter`](https://github.com/rome/tools/tree/main/crates/rome_formatter),
Ruff's formatter is built on a fork of Rome's [`rome_formatter`](https://github.com/rome/tools/tree/main/crates/rome_formatter),
and again draws on both API and implementation details from [Rome](https://github.com/rome/tools),
[Prettier](https://github.com/prettier/prettier), and [Black](https://github.com/psf/black).

8
assets/badge/format.json Normal file
View File

@@ -0,0 +1,8 @@
{
"label": "code style",
"message": "Ruff",
"logoSvg": "<svg width=\"510\" height=\"622\" viewBox=\"0 0 510 622\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M206.701 0C200.964 0 196.314 4.64131 196.314 10.3667V41.4667C196.314 47.192 191.663 51.8333 185.927 51.8333H156.843C151.107 51.8333 146.456 56.4746 146.456 62.2V145.133C146.456 150.859 141.806 155.5 136.069 155.5H106.986C101.249 155.5 96.5988 160.141 96.5988 165.867V222.883C96.5988 228.609 91.9484 233.25 86.2118 233.25H57.1283C51.3917 233.25 46.7413 237.891 46.7413 243.617V300.633C46.7413 306.359 42.0909 311 36.3544 311H10.387C4.6504 311 0 315.641 0 321.367V352.467C0 358.192 4.6504 362.833 10.387 362.833H145.418C151.154 362.833 155.804 367.475 155.804 373.2V430.217C155.804 435.942 151.154 440.583 145.418 440.583H116.334C110.597 440.583 105.947 445.225 105.947 450.95V507.967C105.947 513.692 101.297 518.333 95.5601 518.333H66.4766C60.74 518.333 56.0896 522.975 56.0896 528.7V611.633C56.0896 617.359 60.74 622 66.4766 622H149.572C155.309 622 159.959 617.359 159.959 611.633V570.167H201.507C207.244 570.167 211.894 565.525 211.894 559.8V528.7C211.894 522.975 216.544 518.333 222.281 518.333H251.365C257.101 518.333 261.752 513.692 261.752 507.967V476.867C261.752 471.141 266.402 466.5 272.138 466.5H301.222C306.959 466.5 311.609 461.859 311.609 456.133V425.033C311.609 419.308 316.259 414.667 321.996 414.667H351.079C356.816 414.667 361.466 410.025 361.466 404.3V373.2C361.466 367.475 366.117 362.833 371.853 362.833H400.937C406.673 362.833 411.324 358.192 411.324 352.467V321.367C411.324 315.641 415.974 311 421.711 311H450.794C456.531 311 461.181 306.359 461.181 300.633V217.7C461.181 211.975 456.531 207.333 450.794 207.333H420.672C414.936 207.333 410.285 202.692 410.285 196.967V165.867C410.285 160.141 414.936 155.5 420.672 155.5H449.756C455.492 155.5 460.143 150.859 460.143 145.133V114.033C460.143 108.308 464.793 103.667 470.53 103.667H499.613C505.35 103.667 510 99.0253 510 93.3V10.3667C510 4.64132 505.35 0 499.613 0H206.701ZM168.269 440.583C162.532 440.583 157.882 445.225 157.882 450.95V507.967C157.882 513.692 153.231 518.333 147.495 518.333H118.411C112.675 518.333 108.024 522.975 108.024 528.7V559.8C108.024 565.525 112.675 570.167 118.411 570.167H159.959V528.7C159.959 522.975 164.61 518.333 170.346 518.333H199.43C205.166 518.333 209.817 513.692 209.817 507.967V476.867C209.817 471.141 214.467 466.5 220.204 466.5H249.287C255.024 466.5 259.674 461.859 259.674 456.133V425.033C259.674 419.308 264.325 414.667 270.061 414.667H299.145C304.881 414.667 309.532 410.025 309.532 404.3V373.2C309.532 367.475 314.182 362.833 319.919 362.833H349.002C354.739 362.833 359.389 358.192 359.389 352.467V321.367C359.389 315.641 364.039 311 369.776 311H398.859C404.596 311 409.246 306.359 409.246 300.633V269.533C409.246 263.808 404.596 259.167 398.859 259.167H318.88C313.143 259.167 308.493 254.525 308.493 248.8V217.7C308.493 211.975 313.143 207.333 318.88 207.333H347.963C353.7 207.333 358.35 202.692 358.35 196.967V165.867C358.35 160.141 363.001 155.5 368.737 155.5H397.821C403.557 155.5 408.208 150.859 408.208 145.133V114.033C408.208 108.308 412.858 103.667 418.595 103.667H447.678C453.415 103.667 458.065 99.0253 458.065 93.3V62.2C458.065 56.4746 453.415 51.8333 447.678 51.8333H208.778C203.041 51.8333 198.391 56.4746 198.391 62.2V145.133C198.391 150.859 193.741 155.5 188.004 155.5H158.921C153.184 155.5 148.534 160.141 148.534 165.867V222.883C148.534 228.609 143.883 233.25 138.147 233.25H109.063C103.327 233.25 98.6762 237.891 98.6762 243.617V300.633C98.6762 306.359 103.327 311 109.063 311H197.352C203.089 311 207.739 315.641 207.739 321.367V430.217C207.739 435.942 203.089 440.583 197.352 440.583H168.269Z\" fill=\"#D7FF64\"/></svg>",
"logoWidth": 10,
"labelColor": "grey",
"color": "#261230"
}

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.289"
version = "0.1.1"
description = """
Convert Flake8 configuration files to Ruff configuration files.
"""
@@ -13,7 +13,7 @@ repository = { workspace = true }
license = { workspace = true }
[dependencies]
ruff = { path = "../ruff", default-features = false }
ruff_linter = { path = "../ruff_linter", default-features = false }
ruff_workspace = { path = "../ruff_workspace" }
anyhow = { workspace = true }
@@ -23,7 +23,7 @@ configparser = { version = "3.0.2" }
itertools = { workspace = true }
log = { workspace = true }
once_cell = { workspace = true }
pep440_rs = { version = "0.3.1", features = ["serde"] }
pep440_rs = { version = "0.3.12", features = ["serde"] }
regex = { workspace = true }
rustc-hash = { workspace = true }
serde = { workspace = true }

View File

@@ -86,7 +86,7 @@ flake8-to-ruff path/to/.flake8 --plugin flake8-builtins --plugin flake8-quotes
configuration options that don't exist in Flake8.)
1. Ruff will omit any rule codes that are unimplemented or unsupported by Ruff, including rule
codes from unsupported plugins. (See the
[documentation](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) for the complete
[documentation](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8) for the complete
list of supported plugins.)
## License

View File

@@ -1,7 +1,7 @@
//! Extract Black configuration settings from a pyproject.toml.
use ruff::line_width::LineLength;
use ruff::settings::types::PythonVersion;
use ruff_linter::line_width::LineLength;
use ruff_linter::settings::types::PythonVersion;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]

View File

@@ -3,21 +3,22 @@ use std::str::FromStr;
use itertools::Itertools;
use ruff::line_width::LineLength;
use ruff::registry::Linter;
use ruff::rule_selector::RuleSelector;
use ruff::rules::flake8_pytest_style::types::{
use ruff_linter::line_width::LineLength;
use ruff_linter::registry::Linter;
use ruff_linter::rule_selector::RuleSelector;
use ruff_linter::rules::flake8_pytest_style::types::{
ParametrizeNameType, ParametrizeValuesRowType, ParametrizeValuesType,
};
use ruff::rules::flake8_quotes::settings::Quote;
use ruff::rules::flake8_tidy_imports::settings::Strictness;
use ruff::rules::pydocstyle::settings::Convention;
use ruff::settings::types::PythonVersion;
use ruff::warn_user;
use ruff_linter::rules::flake8_quotes::settings::Quote;
use ruff_linter::rules::flake8_tidy_imports::settings::Strictness;
use ruff_linter::rules::pydocstyle::settings::Convention;
use ruff_linter::settings::types::PythonVersion;
use ruff_linter::settings::DEFAULT_SELECTORS;
use ruff_linter::warn_user;
use ruff_workspace::options::{
Flake8AnnotationsOptions, Flake8BugbearOptions, Flake8BuiltinsOptions, Flake8ErrMsgOptions,
Flake8PytestStyleOptions, Flake8QuotesOptions, Flake8TidyImportsOptions, McCabeOptions,
Options, Pep8NamingOptions, PydocstyleOptions,
Flake8PytestStyleOptions, Flake8QuotesOptions, Flake8TidyImportsOptions, LintCommonOptions,
LintOptions, McCabeOptions, Options, Pep8NamingOptions, PydocstyleOptions,
};
use ruff_workspace::pyproject::Pyproject;
@@ -25,11 +26,6 @@ use super::external_config::ExternalConfig;
use super::plugin::Plugin;
use super::{parser, plugin};
const DEFAULT_SELECTORS: &[RuleSelector] = &[
RuleSelector::Linter(Linter::Pyflakes),
RuleSelector::Linter(Linter::Pycodestyle),
];
pub(crate) fn convert(
config: &HashMap<String, HashMap<String, Option<String>>>,
external_config: &ExternalConfig,
@@ -103,6 +99,7 @@ pub(crate) fn convert(
// Parse each supported option.
let mut options = Options::default();
let mut lint_options = LintCommonOptions::default();
let mut flake8_annotations = Flake8AnnotationsOptions::default();
let mut flake8_bugbear = Flake8BugbearOptions::default();
let mut flake8_builtins = Flake8BuiltinsOptions::default();
@@ -150,7 +147,7 @@ pub(crate) fn convert(
"per-file-ignores" | "per_file_ignores" => {
match parser::parse_files_to_codes_mapping(value.as_ref()) {
Ok(per_file_ignores) => {
options.per_file_ignores =
lint_options.per_file_ignores =
Some(parser::collect_per_file_ignores(per_file_ignores));
}
Err(e) => {
@@ -358,47 +355,47 @@ pub(crate) fn convert(
}
// Deduplicate and sort.
options.select = Some(
lint_options.select = Some(
select
.into_iter()
.sorted_by_key(RuleSelector::prefix_and_code)
.collect(),
);
options.ignore = Some(
lint_options.ignore = Some(
ignore
.into_iter()
.sorted_by_key(RuleSelector::prefix_and_code)
.collect(),
);
if flake8_annotations != Flake8AnnotationsOptions::default() {
options.flake8_annotations = Some(flake8_annotations);
lint_options.flake8_annotations = Some(flake8_annotations);
}
if flake8_bugbear != Flake8BugbearOptions::default() {
options.flake8_bugbear = Some(flake8_bugbear);
lint_options.flake8_bugbear = Some(flake8_bugbear);
}
if flake8_builtins != Flake8BuiltinsOptions::default() {
options.flake8_builtins = Some(flake8_builtins);
lint_options.flake8_builtins = Some(flake8_builtins);
}
if flake8_errmsg != Flake8ErrMsgOptions::default() {
options.flake8_errmsg = Some(flake8_errmsg);
lint_options.flake8_errmsg = Some(flake8_errmsg);
}
if flake8_pytest_style != Flake8PytestStyleOptions::default() {
options.flake8_pytest_style = Some(flake8_pytest_style);
lint_options.flake8_pytest_style = Some(flake8_pytest_style);
}
if flake8_quotes != Flake8QuotesOptions::default() {
options.flake8_quotes = Some(flake8_quotes);
lint_options.flake8_quotes = Some(flake8_quotes);
}
if flake8_tidy_imports != Flake8TidyImportsOptions::default() {
options.flake8_tidy_imports = Some(flake8_tidy_imports);
lint_options.flake8_tidy_imports = Some(flake8_tidy_imports);
}
if mccabe != McCabeOptions::default() {
options.mccabe = Some(mccabe);
lint_options.mccabe = Some(mccabe);
}
if pep8_naming != Pep8NamingOptions::default() {
options.pep8_naming = Some(pep8_naming);
lint_options.pep8_naming = Some(pep8_naming);
}
if pydocstyle != PydocstyleOptions::default() {
options.pydocstyle = Some(pydocstyle);
lint_options.pydocstyle = Some(pydocstyle);
}
// Extract any settings from the existing `pyproject.toml`.
@@ -436,6 +433,13 @@ pub(crate) fn convert(
}
}
if lint_options != LintCommonOptions::default() {
options.lint = Some(LintOptions {
common: lint_options,
..LintOptions::default()
});
}
// Create the pyproject.toml.
Pyproject::new(options)
}
@@ -458,13 +462,15 @@ mod tests {
use pep440_rs::VersionSpecifiers;
use pretty_assertions::assert_eq;
use ruff::line_width::LineLength;
use ruff::registry::Linter;
use ruff::rule_selector::RuleSelector;
use ruff::rules::flake8_quotes;
use ruff::rules::pydocstyle::settings::Convention;
use ruff::settings::types::PythonVersion;
use ruff_workspace::options::{Flake8QuotesOptions, Options, PydocstyleOptions};
use ruff_linter::line_width::LineLength;
use ruff_linter::registry::Linter;
use ruff_linter::rule_selector::RuleSelector;
use ruff_linter::rules::flake8_quotes;
use ruff_linter::rules::pydocstyle::settings::Convention;
use ruff_linter::settings::types::PythonVersion;
use ruff_workspace::options::{
Flake8QuotesOptions, LintCommonOptions, LintOptions, Options, PydocstyleOptions,
};
use ruff_workspace::pyproject::Pyproject;
use crate::converter::DEFAULT_SELECTORS;
@@ -474,8 +480,8 @@ mod tests {
use super::super::plugin::Plugin;
use super::convert;
fn default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> Options {
Options {
fn lint_default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> LintCommonOptions {
LintCommonOptions {
ignore: Some(vec![]),
select: Some(
DEFAULT_SELECTORS
@@ -485,7 +491,7 @@ mod tests {
.sorted_by_key(RuleSelector::prefix_and_code)
.collect(),
),
..Options::default()
..LintCommonOptions::default()
}
}
@@ -496,7 +502,13 @@ mod tests {
&ExternalConfig::default(),
None,
);
let expected = Pyproject::new(default_options([]));
let expected = Pyproject::new(Options {
lint: Some(LintOptions {
common: lint_default_options([]),
..LintOptions::default()
}),
..Options::default()
});
assert_eq!(actual, expected);
}
@@ -512,7 +524,11 @@ mod tests {
);
let expected = Pyproject::new(Options {
line_length: Some(LineLength::try_from(100).unwrap()),
..default_options([])
lint: Some(LintOptions {
common: lint_default_options([]),
..LintOptions::default()
}),
..Options::default()
});
assert_eq!(actual, expected);
}
@@ -529,7 +545,11 @@ mod tests {
);
let expected = Pyproject::new(Options {
line_length: Some(LineLength::try_from(100).unwrap()),
..default_options([])
lint: Some(LintOptions {
common: lint_default_options([]),
..LintOptions::default()
}),
..Options::default()
});
assert_eq!(actual, expected);
}
@@ -544,7 +564,13 @@ mod tests {
&ExternalConfig::default(),
Some(vec![]),
);
let expected = Pyproject::new(default_options([]));
let expected = Pyproject::new(Options {
lint: Some(LintOptions {
common: lint_default_options([]),
..LintOptions::default()
}),
..Options::default()
});
assert_eq!(actual, expected);
}
@@ -559,13 +585,19 @@ mod tests {
Some(vec![]),
);
let expected = Pyproject::new(Options {
flake8_quotes: Some(Flake8QuotesOptions {
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None,
docstring_quotes: None,
avoid_escape: None,
lint: Some(LintOptions {
common: LintCommonOptions {
flake8_quotes: Some(Flake8QuotesOptions {
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None,
docstring_quotes: None,
avoid_escape: None,
}),
..lint_default_options([])
},
..LintOptions::default()
}),
..default_options([])
..Options::default()
});
assert_eq!(actual, expected);
}
@@ -584,12 +616,18 @@ mod tests {
Some(vec![Plugin::Flake8Docstrings]),
);
let expected = Pyproject::new(Options {
pydocstyle: Some(PydocstyleOptions {
convention: Some(Convention::Numpy),
ignore_decorators: None,
property_decorators: None,
lint: Some(LintOptions {
common: LintCommonOptions {
pydocstyle: Some(PydocstyleOptions {
convention: Some(Convention::Numpy),
ignore_decorators: None,
property_decorators: None,
}),
..lint_default_options([Linter::Pydocstyle.into()])
},
..LintOptions::default()
}),
..default_options([Linter::Pydocstyle.into()])
..Options::default()
});
assert_eq!(actual, expected);
}
@@ -605,13 +643,19 @@ mod tests {
None,
);
let expected = Pyproject::new(Options {
flake8_quotes: Some(Flake8QuotesOptions {
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None,
docstring_quotes: None,
avoid_escape: None,
lint: Some(LintOptions {
common: LintCommonOptions {
flake8_quotes: Some(Flake8QuotesOptions {
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None,
docstring_quotes: None,
avoid_escape: None,
}),
..lint_default_options([Linter::Flake8Quotes.into()])
},
..LintOptions::default()
}),
..default_options([Linter::Flake8Quotes.into()])
..Options::default()
});
assert_eq!(actual, expected);
}
@@ -630,7 +674,11 @@ mod tests {
);
let expected = Pyproject::new(Options {
target_version: Some(PythonVersion::Py38),
..default_options([])
lint: Some(LintOptions {
common: lint_default_options([]),
..LintOptions::default()
}),
..Options::default()
});
assert_eq!(actual, expected);

View File

@@ -19,7 +19,7 @@ use crate::converter::convert;
use crate::external_config::ExternalConfig;
use crate::plugin::Plugin;
use crate::pyproject::parse;
use ruff::logging::{set_up_logging, LogLevel};
use ruff_linter::logging::{set_up_logging, LogLevel};
#[derive(Parser)]
#[command(

View File

@@ -3,10 +3,11 @@ use std::str::FromStr;
use anyhow::{bail, Result};
use once_cell::sync::Lazy;
use regex::Regex;
use ruff::settings::types::PatternPrefixPair;
use ruff::{warn_user, RuleSelector};
use rustc_hash::FxHashMap;
use ruff_linter::settings::types::PatternPrefixPair;
use ruff_linter::{warn_user, RuleSelector};
static COMMA_SEPARATED_LIST_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").unwrap());
/// Parse a comma-separated list of `RuleSelector` values (e.g.,
@@ -183,7 +184,7 @@ pub(crate) fn collect_per_file_ignores(
for pair in pairs {
per_file_ignores
.entry(pair.pattern)
.or_insert_with(Vec::new)
.or_default()
.push(pair.prefix);
}
per_file_ignores
@@ -192,11 +193,11 @@ pub(crate) fn collect_per_file_ignores(
#[cfg(test)]
mod tests {
use anyhow::Result;
use ruff::RuleSelector;
use ruff::codes;
use ruff::registry::Linter;
use ruff::settings::types::PatternPrefixPair;
use ruff_linter::codes;
use ruff_linter::registry::Linter;
use ruff_linter::settings::types::PatternPrefixPair;
use ruff_linter::RuleSelector;
use super::{parse_files_to_codes_mapping, parse_prefix_codes, parse_strings};

View File

@@ -3,9 +3,9 @@ use std::fmt;
use std::str::FromStr;
use anyhow::anyhow;
use ruff::registry::Linter;
use ruff::settings::types::PreviewMode;
use ruff::RuleSelector;
use ruff_linter::registry::Linter;
use ruff_linter::rule_selector::PreviewOptions;
use ruff_linter::RuleSelector;
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum Plugin {
@@ -332,7 +332,7 @@ pub(crate) fn infer_plugins_from_codes(selectors: &HashSet<RuleSelector>) -> Vec
.filter(|plugin| {
for selector in selectors {
if selector
.rules(PreviewMode::Disabled)
.rules(&PreviewOptions::default())
.any(|rule| Linter::from(plugin).rules().any(|r| r == rule))
{
return true;

View File

@@ -1,55 +0,0 @@
"""
Should emit:
B009 - Lines 19-31
B010 - Lines 40-45
"""
# Valid getattr usage
getattr(foo, bar)
getattr(foo, "bar", None)
getattr(foo, "bar{foo}".format(foo="a"), None)
getattr(foo, "bar{foo}".format(foo="a"))
getattr(foo, bar, None)
getattr(foo, "123abc")
getattr(foo, r"123\abc")
getattr(foo, "except")
getattr(foo, "__123abc")
# Invalid usage
getattr(foo, "bar")
getattr(foo, "_123abc")
getattr(foo, "__123abc__")
getattr(foo, "abc123")
getattr(foo, r"abc123")
_ = lambda x: getattr(x, "bar")
if getattr(x, "bar"):
pass
getattr(1, "real")
getattr(1., "real")
getattr(1.0, "real")
getattr(1j, "real")
getattr(True, "real")
getattr(x := 1, "real")
getattr(x + y, "real")
getattr("foo"
"bar", "real")
# Valid setattr usage
setattr(foo, bar, None)
setattr(foo, "bar{foo}".format(foo="a"), None)
setattr(foo, "123abc", None)
setattr(foo, "__123abc", None)
setattr(foo, r"123\abc", None)
setattr(foo, "except", None)
_ = lambda x: setattr(x, "bar", 1)
if setattr(x, "bar", 1):
pass
# Invalid usage
setattr(foo, "bar", None)
setattr(foo, "_123abc", None)
setattr(foo, "__123abc__", None)
setattr(foo, "abc123", None)
setattr(foo, r"abc123", None)
setattr(foo.bar, r"baz", None)

View File

@@ -1,15 +0,0 @@
x = [2, 3, 1]
list(x)
list(sorted(x))
reversed(sorted(x))
reversed(sorted(x, key=lambda e: e))
reversed(sorted(x, reverse=True))
reversed(sorted(x, key=lambda e: e, reverse=True))
reversed(sorted(x, reverse=True, key=lambda e: e))
reversed(sorted(x, reverse=False))
def reversed(*args, **kwargs):
return None
reversed(sorted(x, reverse=True))

View File

@@ -1,34 +0,0 @@
x = [1, 2, 3]
list(list(x))
list(tuple(x))
tuple(list(x))
tuple(tuple(x))
set(set(x))
set(list(x))
set(tuple(x))
set(sorted(x))
set(sorted(x, key=lambda y: y))
set(reversed(x))
sorted(list(x))
sorted(tuple(x))
sorted(sorted(x))
sorted(sorted(x, key=foo, reverse=False), reverse=False, key=foo)
sorted(sorted(x, reverse=True), reverse=True)
sorted(reversed(x))
sorted(list(x), key=lambda y: y)
tuple(
list(
[x, 3, "hell"\
"o"]
)
)
set(set())
set(list())
set(tuple())
sorted(reversed())
# Nested sorts with differing keyword arguments. Not flagged.
sorted(sorted(x, key=lambda y: y))
sorted(sorted(x, key=lambda y: y), key=lambda x: x)
sorted(sorted(x), reverse=True)
sorted(sorted(x, reverse=False), reverse=True)

View File

@@ -1,61 +0,0 @@
_ = "a" "b" "c"
_ = "abc" + "def"
_ = "abc" \
"def"
_ = (
"abc" +
"def"
)
_ = (
f"abc" +
"def"
)
_ = (
b"abc" +
b"def"
)
_ = (
"abc"
"def"
)
_ = (
f"abc"
"def"
)
_ = (
b"abc"
b"def"
)
_ = """a""" """b"""
_ = """a
b""" """c
d"""
_ = f"""a""" f"""b"""
_ = f"a" "b"
_ = """a""" "b"
_ = 'a' "b"
_ = rf"a" rf"b"
# Single-line explicit concatenation should be ignored.
_ = "abc" + "def" + "ghi"
_ = foo + "abc" + "def"
_ = "abc" + foo + "def"
_ = "abc" + "def" + foo
_ = foo + bar + "abc"
_ = "abc" + foo + bar
_ = foo + "abc" + bar

View File

@@ -1,18 +0,0 @@
import logging
import logging as foo
logging.info("Hello {}".format("World!"))
logging.log(logging.INFO, "Hello {}".format("World!"))
foo.info("Hello {}".format("World!"))
logging.log(logging.INFO, msg="Hello {}".format("World!"))
logging.log(level=logging.INFO, msg="Hello {}".format("World!"))
logging.log(msg="Hello {}".format("World!"), level=logging.INFO)
# Flask support
import flask
from flask import current_app
from flask import current_app as app
flask.current_app.logger.info("Hello {}".format("World!"))
current_app.logger.info("Hello {}".format("World!"))
app.logger.log(logging.INFO, "Hello {}".format("World!"))

View File

@@ -1,4 +0,0 @@
import logging
logging.info("Hello %s" % "World!")
logging.log(logging.INFO, "Hello %s" % "World!")

View File

@@ -1,4 +0,0 @@
import logging
logging.info("Hello" + " " + "World!")
logging.log(logging.INFO, "Hello" + " " + "World!")

View File

@@ -1,8 +0,0 @@
import logging
name = "world"
logging.info(f"Hello {name}")
logging.log(logging.INFO, f"Hello {name}")
_LOGGER = logging.getLogger()
_LOGGER.info(f"{__name__}")

View File

@@ -1,10 +0,0 @@
import logging
from distutils import log
from logging_setup import logger
logging.warn("Hello World!")
log.warn("Hello world!") # This shouldn't be considered as a logger candidate
logger.warn("Hello world!")
logging . warn("Hello World!")

View File

@@ -1,8 +0,0 @@
import logging
logging.info(
"Hello world!",
extra={
"name": "foobar",
},
)

View File

@@ -1,8 +0,0 @@
import logging
logging.info(
"Hello world!",
extra=dict(
name="foobar",
),
)

View File

@@ -1,21 +0,0 @@
import logging
import sys
# G201
try:
pass
except:
logging.error("Hello World", exc_info=True)
try:
pass
except:
logging.error("Hello World", exc_info=sys.exc_info())
# OK
try:
pass
except:
logging.error("Hello World", exc_info=False)
logging.error("Hello World", exc_info=sys.exc_info())

View File

@@ -1,21 +0,0 @@
import logging
import sys
# G202
try:
pass
except:
logging.exception("Hello World", exc_info=True)
try:
pass
except:
logging.exception("Hello World", exc_info=sys.exc_info())
# OK
try:
pass
except:
logging.exception("Hello World", exc_info=False)
logging.exception("Hello World", exc_info=True)

View File

@@ -1,31 +0,0 @@
import builtins
from typing import Union
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
x: type[int] | type[str] | type[float]
y: builtins.type[int] | type[str] | builtins.type[complex]
z: Union[type[float], type[complex]]
z: Union[type[float, int], type[complex]]
def func(arg: type[int] | str | type[float]) -> None:
...
# OK
x: type[int, str, float]
y: builtins.type[int, str, complex]
z: Union[float, complex]
def func(arg: type[int, float] | str) -> None:
...
# OK
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
def func():
# PYI055
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker

View File

@@ -1,17 +0,0 @@
import pytest
@pytest.fixture()
def ok_complex_logic():
if some_condition:
resource = acquire_resource()
yield resource
resource.release()
return
yield None
@pytest.fixture()
def error():
resource = acquire_resource()
yield resource

View File

@@ -1,11 +0,0 @@
this_should_raise_Q003 = 'This is a \'string\''
this_should_raise_Q003 = 'This is \\ a \\\'string\''
this_is_fine = '"This" is a \'string\''
this_is_fine = "This is a 'string'"
this_is_fine = "\"This\" is a 'string'"
this_is_fine = r'This is a \'string\''
this_is_fine = R'This is a \'string\''
this_should_raise = (
'This is a'
'\'string\''
)

View File

@@ -1,10 +0,0 @@
this_should_raise_Q003 = "This is a \"string\""
this_is_fine = "'This' is a \"string\""
this_is_fine = 'This is a "string"'
this_is_fine = '\'This\' is a "string"'
this_is_fine = r"This is a \"string\""
this_is_fine = R"This is a \"string\""
this_should_raise = (
"This is a"
"\"string\""
)

View File

@@ -1,35 +0,0 @@
import os
import os.path
p = "/foo"
q = "bar"
a = os.path.abspath(p)
aa = os.chmod(p)
aaa = os.mkdir(p)
os.makedirs(p)
os.rename(p)
os.replace(p)
os.rmdir(p)
os.remove(p)
os.unlink(p)
os.getcwd(p)
b = os.path.exists(p)
bb = os.path.expanduser(p)
bbb = os.path.isdir(p)
bbbb = os.path.isfile(p)
bbbbb = os.path.islink(p)
os.readlink(p)
os.stat(p)
os.path.isabs(p)
os.path.join(p, q)
os.sep.join([p, q])
os.sep.join((p, q))
os.path.basename(p)
os.path.dirname(p)
os.path.samefile(p)
os.path.splitext(p)
with open(p) as fp:
fp.read()
open(p).close()
os.getcwdb(p)

View File

@@ -1,33 +0,0 @@
#: E231
a = (1,2)
#: E231
a[b1,:]
#: E231
a = [{'a':''}]
#: Okay
a = (4,)
b = (5, )
c = {'text': text[5:]}
result = {
'key1': 'value',
'key2': 'value',
}
def foo() -> None:
#: E231
if (1,2):
pass
#: Okay
a = (1,\
2)
#: E231:2:20
mdtypes_template = {
'tag_full': [('mdtype', 'u4'), ('byte_count', 'u4')],
'tag_smalldata':[('byte_count_mdtype', 'u4'), ('data', 'S4')],
}
#: Okay
a = (1,

View File

@@ -1,6 +0,0 @@
# TODO: comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
# TODO comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
# TODO comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
# FIXME: comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
# FIXME comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
# FIXME comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`

View File

@@ -1,90 +0,0 @@
#: E721
if type(res) == type(42):
pass
#: E721
if type(res) != type(""):
pass
#: E721
import types
if res == types.IntType:
pass
#: E721
import types
if type(res) is not types.ListType:
pass
#: E721
assert type(res) == type(False)
#: E721
assert type(res) == type([])
#: E721
assert type(res) == type(())
#: E721
assert type(res) == type((0,))
#: E721
assert type(res) == type((0))
#: E721
assert type(res) != type((1,))
#: E721
assert type(res) is type((1,))
#: E721
assert type(res) is not type((1,))
#: E211 E721
assert type(res) == type(
[
2,
]
)
#: E201 E201 E202 E721
assert type(res) == type(())
#: E201 E202 E721
assert type(res) == type((0,))
#: Okay
import types
if isinstance(res, int):
pass
if isinstance(res, str):
pass
if isinstance(res, types.MethodType):
pass
if type(a) != type(b) or type(a) == type(ccc):
pass
assert type(res) == type(None)
types = StrEnum
if x == types.X:
pass
#: E721
assert type(res) is int
class Foo:
def asdf(self, value: str | None):
#: E721
if type(value) is str:
...
class Foo:
def type(self):
pass
def asdf(self, value: str | None):
#: E721
if type(value) is str:
...
class Foo:
def asdf(self, value: str | None):
def type():
pass
# Okay
if type(value) is str:
...

View File

@@ -1,7 +0,0 @@
import logging
import warnings
from warnings import warn
warnings.warn("this is ok")
warn("by itself is also ok")
logging.warning("this is fine")

View File

@@ -1,5 +0,0 @@
import logging
from logging import warn
logging.warn("this is not ok")
warn("not ok")

View File

@@ -1,54 +0,0 @@
from typing import override
class Apples:
def _init_(self): # [bad-dunder-name]
pass
def __hello__(self): # [bad-dunder-name]
print("hello")
def __init_(self): # [bad-dunder-name]
# author likely unintentionally misspelled the correct init dunder.
pass
def _init_(self): # [bad-dunder-name]
# author likely unintentionally misspelled the correct init dunder.
pass
def ___neg__(self): # [bad-dunder-name]
# author likely accidentally added an additional `_`
pass
def __inv__(self): # [bad-dunder-name]
# author likely meant to call the invert dunder method
pass
@override
def _ignore__(self): # [bad-dunder-name]
# overridden dunder methods should be ignored
pass
def hello(self):
print("hello")
def __init__(self):
pass
def init(self):
# valid name even though someone could accidentally mean __init__
pass
def _protected_method(self):
print("Protected")
def __private_method(self):
print("Private")
@property
def __doc__(self):
return "Docstring"
def __foo_bar__(): # this is not checked by the [bad-dunder-name] rule
...

View File

@@ -1,28 +0,0 @@
import logging
logging.warning("Hello %s %s", "World!") # [logging-too-few-args]
# do not handle calls with kwargs (like pylint)
logging.warning("Hello %s", "World!", "again", something="else")
logging.warning("Hello %s", "World!")
# do not handle calls without any args
logging.info("100% dynamic")
# do not handle calls with *args
logging.error("Example log %s, %s", "foo", "bar", "baz", *args)
# do not handle calls with **kwargs
logging.error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
# do not handle keyword arguments
logging.error("%(objects)d modifications: %(modifications)d errors: %(errors)d")
logging.info(msg="Hello %s")
logging.info(msg="Hello %s %s")
import warning
warning.warning("Hello %s %s", "World!")

View File

@@ -1,24 +0,0 @@
import logging
logging.warning("Hello %s", "World!", "again") # [logging-too-many-args]
logging.warning("Hello %s", "World!", "again", something="else")
logging.warning("Hello %s", "World!")
# do not handle calls with *args
logging.error("Example log %s, %s", "foo", "bar", "baz", *args)
# do not handle calls with **kwargs
logging.error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
# do not handle keyword arguments
logging.error("%(objects)d modifications: %(modifications)d errors: %(errors)d", {"objects": 1, "modifications": 1, "errors": 1})
logging.info(msg="Hello")
logging.info(msg="Hello", something="else")
import warning
warning.warning("Hello %s", "World!", "again")

View File

@@ -1,15 +0,0 @@
type("")
type(b"")
type(0)
type(0.0)
type(0j)
# OK
type(arg)(" ")
# OK
y = x.dtype.type(0.0)
# OK
type = lambda *args, **kwargs: None
type("")

View File

@@ -1,80 +0,0 @@
open("foo", "U")
open("foo", "Ur")
open("foo", "Ub")
open("foo", "rUb")
open("foo", "r")
open("foo", "rt")
open("f", "r", encoding="UTF-8")
open("f", "wt")
with open("foo", "U") as f:
pass
with open("foo", "Ur") as f:
pass
with open("foo", "Ub") as f:
pass
with open("foo", "rUb") as f:
pass
with open("foo", "r") as f:
pass
with open("foo", "rt") as f:
pass
with open("foo", "r", encoding="UTF-8") as f:
pass
with open("foo", "wt") as f:
pass
open(f("a", "b", "c"), "U")
open(f("a", "b", "c"), "Ub")
with open(f("a", "b", "c"), "U") as f:
pass
with open(f("a", "b", "c"), "Ub") as f:
pass
with open("foo", "U") as fa, open("bar", "U") as fb:
pass
with open("foo", "Ub") as fa, open("bar", "Ub") as fb:
pass
open("foo", mode="U")
open(name="foo", mode="U")
open(mode="U", name="foo")
with open("foo", mode="U") as f:
pass
with open(name="foo", mode="U") as f:
pass
with open(mode="U", name="foo") as f:
pass
open("foo", mode="Ub")
open(name="foo", mode="Ub")
open(mode="Ub", name="foo")
with open("foo", mode="Ub") as f:
pass
with open(name="foo", mode="Ub") as f:
pass
with open(mode="Ub", name="foo") as f:
pass
open(file="foo", mode='U', buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
open(file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U')
open(file="foo", buffering=- 1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None)
open(mode='U', file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
open(file="foo", mode='Ub', buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
open(file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub')
open(file="foo", buffering=- 1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None)
open(mode='Ub', file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
open = 1
open("foo", "U")
open("foo", "Ur")
open("foo", "Ub")
open("foo", "rUb")
open("foo", "r")
open("foo", "rt")
open("f", "r", encoding="UTF-8")
open("f", "wt")

View File

@@ -1,21 +0,0 @@
x = [1, 2, 3]
y = [4, 5, 6]
# RUF017
sum([x, y], start=[])
sum([x, y], [])
sum([[1, 2, 3], [4, 5, 6]], start=[])
sum([[1, 2, 3], [4, 5, 6]], [])
sum([[1, 2, 3], [4, 5, 6]],
[])
# OK
sum([x, y])
sum([[1, 2, 3], [4, 5, 6]])
# Regression test for: https://github.com/astral-sh/ruff/issues/7059
def func():
import functools, operator
sum([x, y], [])

View File

@@ -1,31 +0,0 @@
x = "𝐁ad string"
y = ""
def f():
"""Here's a docstring with an unusual parenthesis: """
# And here's a comment with an unusual punctuation mark:
...
def f():
"""Here's a docstring with a greek rho: ρ"""
# And here's a comment with a greek alpha:
...
x = "𝐁ad string"
x = ""
# This should be ignored, since it contains an unambiguous unicode character, and no
# ASCII.
x = "Русский"
# The first word should be ignored, while the second should be included, since it
# contains ASCII.
x = "βα Bαd"
# The two characters should be flagged here. The first character is a "word"
# consisting of a single ambiguous character, while the second character is a "word
# boundary" (whitespace) that it itself ambiguous.
x = "Р усский"

View File

@@ -1,77 +0,0 @@
"""
Violation:
Use '.exception' over '.error' inside except blocks
"""
import logging
import sys
logger = logging.getLogger(__name__)
def bad():
try:
a = 1
except Exception:
logging.error("Context message here")
if True:
logging.error("Context message here")
def bad():
try:
a = 1
except Exception:
logger.error("Context message here")
if True:
logger.error("Context message here")
def bad():
try:
a = 1
except Exception:
log.error("Context message here")
if True:
log.error("Context message here")
def bad():
try:
a = 1
except Exception:
self.logger.error("Context message here")
if True:
self.logger.error("Context message here")
def good():
try:
a = 1
except Exception:
logger.exception("Context message here")
def good():
try:
a = 1
except Exception:
foo.exception("Context message here")
def fine():
try:
a = 1
except Exception:
logger.error("Context message here", exc_info=True)
def fine():
try:
a = 1
except Exception:
logger.error("Context message here", exc_info=sys.exc_info())

View File

@@ -1,64 +0,0 @@
# Errors
def main_function():
try:
process()
handle()
finish()
except Exception as ex:
logger.exception(f"Found an error: {ex}") # TRY401
def main_function():
try:
process()
handle()
finish()
except ValueError as bad:
if True is False:
for i in range(10):
logger.exception(f"Found an error: {bad} {good}") # TRY401
except IndexError as bad:
logger.exception(f"Found an error: {bad} {bad}") # TRY401
except Exception as bad:
logger.exception(f"Found an error: {bad}") # TRY401
logger.exception(f"Found an error: {bad}") # TRY401
if True:
logger.exception(f"Found an error: {bad}") # TRY401
import logging
logger = logging.getLogger(__name__)
def func_fstr():
try:
...
except Exception as ex:
logger.exception(f"Logging an error: {ex}") # TRY401
def func_concat():
try:
...
except Exception as ex:
logger.exception("Logging an error: " + str(ex)) # TRY401
def func_comma():
try:
...
except Exception as ex:
logger.exception("Logging an error:", ex) # TRY401
# OK
def main_function():
try:
process()
handle()
finish()
except Exception as ex:
logger.exception(f"Found an error: {er}")
logger.exception(f"Found an error: {ex.field}")

View File

@@ -1,96 +0,0 @@
# project
An example multi-package Python project used to test setting resolution and other complex
behaviors.
## Expected behavior
Running from the repo root should pick up and enforce the appropriate settings for each package:
```console
∴ cargo run -p ruff_cli -- check crates/ruff/resources/test/project/
crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:1: I001 [*] Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:8: F401 [*] `numpy` imported but unused
crates/ruff/resources/test/project/examples/.dotfiles/script.py:2:17: F401 [*] `app.app_file` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
crates/ruff/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused
crates/ruff/resources/test/project/project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
Found 7 errors.
[*] 7 potentially fixable with the --fix option.
```
Running from the project directory itself should exhibit the same behavior:
```console
∴ (cd crates/ruff/resources/test/project/ && cargo run -p ruff_cli -- check .)
examples/.dotfiles/script.py:1:1: I001 [*] Import block is un-sorted or un-formatted
examples/.dotfiles/script.py:1:8: F401 [*] `numpy` imported but unused
examples/.dotfiles/script.py:2:17: F401 [*] `app.app_file` imported but unused
examples/docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
examples/docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
project/file.py:1:8: F401 [*] `os` imported but unused
project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
Found 7 errors.
[*] 7 potentially fixable with the --fix option.
```
Running from the sub-package directory should exhibit the same behavior, but omit the top-level
files:
```console
∴ (cd crates/ruff/resources/test/project/examples/docs && cargo run -p ruff_cli -- check .)
docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
Found 2 errors.
[*] 2 potentially fixable with the --fix option.
```
`--config` should force Ruff to use the specified `pyproject.toml` for all files, and resolve
file paths from the current working directory:
```console
∴ (cargo run -p ruff_cli -- check --config=crates/ruff/resources/test/project/pyproject.toml crates/ruff/resources/test/project/)
crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:8: F401 [*] `numpy` imported but unused
crates/ruff/resources/test/project/examples/.dotfiles/script.py:2:17: F401 [*] `app.app_file` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/concepts/file.py:1:8: F401 [*] `os` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/docs/docs/file.py:1:8: F401 [*] `os` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:3:8: F401 [*] `numpy` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:4:27: F401 [*] `docs.concepts.file` imported but unused
crates/ruff/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused
crates/ruff/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused
Found 9 errors.
[*] 9 potentially fixable with the --fix option.
```
Running from a parent directory should "ignore" the `exclude` (hence, `concepts/file.py` gets
included in the output):
```console
∴ (cd crates/ruff/resources/test/project/examples && cargo run -p ruff_cli -- check --config=docs/ruff.toml .)
docs/docs/concepts/file.py:5:5: F841 [*] Local variable `x` is assigned to but never used
docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
excluded/script.py:5:5: F841 [*] Local variable `x` is assigned to but never used
Found 4 errors.
[*] 4 potentially fixable with the --fix option.
```
Passing an excluded directory directly should report errors in the contained files:
```console
∴ cargo run -p ruff_cli -- check crates/ruff/resources/test/project/examples/excluded/
crates/ruff/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 potentially fixable with the --fix option.
```
Unless we `--force-exclude`:
```console
∴ cargo run -p ruff_cli -- check crates/ruff/resources/test/project/examples/excluded/ --force-exclude
warning: No Python files found under the given path(s)
∴ cargo run -p ruff_cli -- check crates/ruff/resources/test/project/examples/excluded/script.py --force-exclude
warning: No Python files found under the given path(s)
```

View File

@@ -1,357 +0,0 @@
use itertools::Itertools;
use std::collections::BTreeSet;
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use rustc_hash::{FxHashMap, FxHashSet};
use ruff_diagnostics::{Diagnostic, Edit, Fix, IsolationLevel, SourceMap};
use ruff_source_file::Locator;
use crate::linter::FixTable;
use crate::registry::{AsRule, Rule};
pub(crate) mod codemods;
pub(crate) mod edits;
pub(crate) mod snippet;
pub(crate) struct FixResult {
/// The resulting source code, after applying all fixes.
pub(crate) code: String,
/// The number of fixes applied for each [`Rule`].
pub(crate) fixes: FixTable,
/// Source map for the fixed source code.
pub(crate) source_map: SourceMap,
}
/// Auto-fix errors in a file, and write the fixed source code to disk.
pub(crate) fn fix_file(diagnostics: &[Diagnostic], locator: &Locator) -> Option<FixResult> {
let mut with_fixes = diagnostics
.iter()
.filter(|diag| diag.fix.is_some())
.peekable();
if with_fixes.peek().is_none() {
None
} else {
Some(apply_fixes(with_fixes, locator))
}
}
/// Apply a series of fixes.
fn apply_fixes<'a>(
diagnostics: impl Iterator<Item = &'a Diagnostic>,
locator: &'a Locator<'a>,
) -> FixResult {
let mut output = String::with_capacity(locator.len());
let mut last_pos: Option<TextSize> = None;
let mut applied: BTreeSet<&Edit> = BTreeSet::default();
let mut isolated: FxHashSet<u32> = FxHashSet::default();
let mut fixed = FxHashMap::default();
let mut source_map = SourceMap::default();
for (rule, fix) in diagnostics
.filter_map(|diagnostic| {
diagnostic
.fix
.as_ref()
.map(|fix| (diagnostic.kind.rule(), fix))
})
.sorted_by(|(rule1, fix1), (rule2, fix2)| cmp_fix(*rule1, *rule2, fix1, fix2))
{
let mut edits = fix
.edits()
.iter()
.filter(|edit| !applied.contains(edit))
.peekable();
// If the fix contains at least one new edit, enforce isolation and positional requirements.
if let Some(first) = edits.peek() {
// If this fix requires isolation, and we've already applied another fix in the
// same isolation group, skip it.
if let IsolationLevel::Group(id) = fix.isolation() {
if !isolated.insert(id) {
continue;
}
}
// If this fix overlaps with a fix we've already applied, skip it.
if last_pos.is_some_and(|last_pos| last_pos >= first.start()) {
continue;
}
}
let mut applied_edits = Vec::with_capacity(fix.edits().len());
for edit in edits {
// Add all contents from `last_pos` to `fix.location`.
let slice = locator.slice(TextRange::new(last_pos.unwrap_or_default(), edit.start()));
output.push_str(slice);
// Add the start source marker for the patch.
source_map.push_start_marker(edit, output.text_len());
// Add the patch itself.
output.push_str(edit.content().unwrap_or_default());
// Add the end source marker for the added patch.
source_map.push_end_marker(edit, output.text_len());
// Track that the edit was applied.
last_pos = Some(edit.end());
applied_edits.push(edit);
}
applied.extend(applied_edits.drain(..));
*fixed.entry(rule).or_default() += 1;
}
// Add the remaining content.
let slice = locator.after(last_pos.unwrap_or_default());
output.push_str(slice);
FixResult {
code: output,
fixes: fixed,
source_map,
}
}
/// Compare two fixes.
fn cmp_fix(rule1: Rule, rule2: Rule, fix1: &Fix, fix2: &Fix) -> std::cmp::Ordering {
fix1.min_start()
.cmp(&fix2.min_start())
.then_with(|| match (&rule1, &rule2) {
// Apply `EndsInPeriod` fixes before `NewLineAfterLastParagraph` fixes.
(Rule::EndsInPeriod, Rule::NewLineAfterLastParagraph) => std::cmp::Ordering::Less,
(Rule::NewLineAfterLastParagraph, Rule::EndsInPeriod) => std::cmp::Ordering::Greater,
// Apply `IfElseBlockInsteadOfDictGet` fixes before `IfElseBlockInsteadOfIfExp` fixes.
(Rule::IfElseBlockInsteadOfDictGet, Rule::IfElseBlockInsteadOfIfExp) => {
std::cmp::Ordering::Less
}
(Rule::IfElseBlockInsteadOfIfExp, Rule::IfElseBlockInsteadOfDictGet) => {
std::cmp::Ordering::Greater
}
_ => std::cmp::Ordering::Equal,
})
}
#[cfg(test)]
mod tests {
use ruff_text_size::{Ranged, TextSize};
use ruff_diagnostics::{Diagnostic, Edit, Fix, SourceMarker};
use ruff_source_file::Locator;
use crate::autofix::{apply_fixes, FixResult};
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
#[allow(deprecated)]
fn create_diagnostics(edit: impl IntoIterator<Item = Edit>) -> Vec<Diagnostic> {
edit.into_iter()
.map(|edit| Diagnostic {
// The choice of rule here is arbitrary.
kind: MissingNewlineAtEndOfFile.into(),
range: edit.range(),
fix: Some(Fix::unspecified(edit)),
parent: None,
})
.collect()
}
#[test]
fn empty_file() {
let locator = Locator::new(r#""#);
let diagnostics = create_diagnostics([]);
let FixResult {
code,
fixes,
source_map,
} = apply_fixes(diagnostics.iter(), &locator);
assert_eq!(code, "");
assert_eq!(fixes.values().sum::<usize>(), 0);
assert!(source_map.markers().is_empty());
}
#[test]
fn apply_one_insertion() {
let locator = Locator::new(
r#"
import os
print("hello world")
"#
.trim(),
);
let diagnostics = create_diagnostics([Edit::insertion(
"import sys\n".to_string(),
TextSize::new(10),
)]);
let FixResult {
code,
fixes,
source_map,
} = apply_fixes(diagnostics.iter(), &locator);
assert_eq!(
code,
r#"
import os
import sys
print("hello world")
"#
.trim()
);
assert_eq!(fixes.values().sum::<usize>(), 1);
assert_eq!(
source_map.markers(),
&[
SourceMarker::new(10.into(), 10.into(),),
SourceMarker::new(10.into(), 21.into(),),
]
);
}
#[test]
fn apply_one_replacement() {
let locator = Locator::new(
r#"
class A(object):
...
"#
.trim(),
);
let diagnostics = create_diagnostics([Edit::replacement(
"Bar".to_string(),
TextSize::new(8),
TextSize::new(14),
)]);
let FixResult {
code,
fixes,
source_map,
} = apply_fixes(diagnostics.iter(), &locator);
assert_eq!(
code,
r#"
class A(Bar):
...
"#
.trim(),
);
assert_eq!(fixes.values().sum::<usize>(), 1);
assert_eq!(
source_map.markers(),
&[
SourceMarker::new(8.into(), 8.into(),),
SourceMarker::new(14.into(), 11.into(),),
]
);
}
#[test]
fn apply_one_removal() {
let locator = Locator::new(
r#"
class A(object):
...
"#
.trim(),
);
let diagnostics = create_diagnostics([Edit::deletion(TextSize::new(7), TextSize::new(15))]);
let FixResult {
code,
fixes,
source_map,
} = apply_fixes(diagnostics.iter(), &locator);
assert_eq!(
code,
r#"
class A:
...
"#
.trim()
);
assert_eq!(fixes.values().sum::<usize>(), 1);
assert_eq!(
source_map.markers(),
&[
SourceMarker::new(7.into(), 7.into()),
SourceMarker::new(15.into(), 7.into()),
]
);
}
#[test]
fn apply_two_removals() {
let locator = Locator::new(
r#"
class A(object, object, object):
...
"#
.trim(),
);
let diagnostics = create_diagnostics([
Edit::deletion(TextSize::from(8), TextSize::from(16)),
Edit::deletion(TextSize::from(22), TextSize::from(30)),
]);
let FixResult {
code,
fixes,
source_map,
} = apply_fixes(diagnostics.iter(), &locator);
assert_eq!(
code,
r#"
class A(object):
...
"#
.trim()
);
assert_eq!(fixes.values().sum::<usize>(), 2);
assert_eq!(
source_map.markers(),
&[
SourceMarker::new(8.into(), 8.into()),
SourceMarker::new(16.into(), 8.into()),
SourceMarker::new(22.into(), 14.into(),),
SourceMarker::new(30.into(), 14.into(),),
]
);
}
#[test]
fn ignore_overlapping_fixes() {
let locator = Locator::new(
r#"
class A(object):
...
"#
.trim(),
);
let diagnostics = create_diagnostics([
Edit::deletion(TextSize::from(7), TextSize::from(15)),
Edit::replacement("ignored".to_string(), TextSize::from(9), TextSize::from(11)),
]);
let FixResult {
code,
fixes,
source_map,
} = apply_fixes(diagnostics.iter(), &locator);
assert_eq!(
code,
r#"
class A:
...
"#
.trim(),
);
assert_eq!(fixes.values().sum::<usize>(), 1);
assert_eq!(
source_map.markers(),
&[
SourceMarker::new(7.into(), 7.into(),),
SourceMarker::new(15.into(), 7.into(),),
]
);
}
}

View File

@@ -1,27 +0,0 @@
pub(super) use bindings::bindings;
pub(super) use comprehension::comprehension;
pub(super) use deferred_for_loops::deferred_for_loops;
pub(super) use deferred_scopes::deferred_scopes;
pub(super) use definitions::definitions;
pub(super) use except_handler::except_handler;
pub(super) use expression::expression;
pub(super) use module::module;
pub(super) use parameter::parameter;
pub(super) use parameters::parameters;
pub(super) use statement::statement;
pub(super) use suite::suite;
pub(super) use unresolved_references::unresolved_references;
mod bindings;
mod comprehension;
mod deferred_for_loops;
mod deferred_scopes;
mod definitions;
mod except_handler;
mod expression;
mod module;
mod parameter;
mod parameters;
mod statement;
mod suite;
mod unresolved_references;

File diff suppressed because it is too large Load Diff

View File

@@ -1,242 +0,0 @@
//! `NoQA` enforcement and validation.
use std::path::Path;
use itertools::Itertools;
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_source_file::Locator;
use crate::noqa;
use crate::noqa::{Directive, FileExemption, NoqaDirectives, NoqaMapping};
use crate::registry::{AsRule, Rule};
use crate::rule_redirects::get_redirect_target;
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
use crate::settings::Settings;
pub(crate) fn check_noqa(
diagnostics: &mut Vec<Diagnostic>,
path: &Path,
locator: &Locator,
comment_ranges: &[TextRange],
noqa_line_for: &NoqaMapping,
analyze_directives: bool,
settings: &Settings,
) -> Vec<usize> {
// Identify any codes that are globally exempted (within the current file).
let exemption = FileExemption::try_extract(locator.contents(), comment_ranges, path, locator);
// Extract all `noqa` directives.
let mut noqa_directives = NoqaDirectives::from_commented_ranges(comment_ranges, path, locator);
// Indices of diagnostics that were ignored by a `noqa` directive.
let mut ignored_diagnostics = vec![];
// Remove any ignored diagnostics.
'outer: for (index, diagnostic) in diagnostics.iter().enumerate() {
if matches!(diagnostic.kind.rule(), Rule::BlanketNOQA) {
continue;
}
match &exemption {
Some(FileExemption::All) => {
// If the file is exempted, ignore all diagnostics.
ignored_diagnostics.push(index);
continue;
}
Some(FileExemption::Codes(codes)) => {
// If the diagnostic is ignored by a global exemption, ignore it.
if codes.contains(&diagnostic.kind.rule().noqa_code()) {
ignored_diagnostics.push(index);
continue;
}
}
None => {}
}
let noqa_offsets = diagnostic
.parent
.into_iter()
.chain(std::iter::once(diagnostic.start()))
.map(|position| noqa_line_for.resolve(position))
.unique();
for noqa_offset in noqa_offsets {
if let Some(directive_line) = noqa_directives.find_line_with_directive_mut(noqa_offset)
{
let suppressed = match &directive_line.directive {
Directive::All(_) => {
directive_line
.matches
.push(diagnostic.kind.rule().noqa_code());
ignored_diagnostics.push(index);
true
}
Directive::Codes(directive) => {
if noqa::includes(diagnostic.kind.rule(), directive.codes()) {
directive_line
.matches
.push(diagnostic.kind.rule().noqa_code());
ignored_diagnostics.push(index);
true
} else {
false
}
}
};
if suppressed {
continue 'outer;
}
}
}
}
// Enforce that the noqa directive was actually used (RUF100), unless RUF100 was itself
// suppressed.
if settings.rules.enabled(Rule::UnusedNOQA)
&& analyze_directives
&& !exemption.is_some_and(|exemption| match exemption {
FileExemption::All => true,
FileExemption::Codes(codes) => codes.contains(&Rule::UnusedNOQA.noqa_code()),
})
{
for line in noqa_directives.lines() {
match &line.directive {
Directive::All(directive) => {
if line.matches.is_empty() {
let mut diagnostic =
Diagnostic::new(UnusedNOQA { codes: None }, directive.range());
if settings.rules.should_fix(diagnostic.kind.rule()) {
diagnostic
.set_fix(Fix::suggested(delete_noqa(directive.range(), locator)));
}
diagnostics.push(diagnostic);
}
}
Directive::Codes(directive) => {
let mut disabled_codes = vec![];
let mut unknown_codes = vec![];
let mut unmatched_codes = vec![];
let mut valid_codes = vec![];
let mut self_ignore = false;
for code in directive.codes() {
let code = get_redirect_target(code).unwrap_or(code);
if Rule::UnusedNOQA.noqa_code() == code {
self_ignore = true;
break;
}
if line.matches.iter().any(|match_| *match_ == code)
|| settings.external.contains(code)
{
valid_codes.push(code);
} else {
if let Ok(rule) = Rule::from_code(code) {
if settings.rules.enabled(rule) {
unmatched_codes.push(code);
} else {
disabled_codes.push(code);
}
} else {
unknown_codes.push(code);
}
}
}
if self_ignore {
continue;
}
if !(disabled_codes.is_empty()
&& unknown_codes.is_empty()
&& unmatched_codes.is_empty())
{
let mut diagnostic = Diagnostic::new(
UnusedNOQA {
codes: Some(UnusedCodes {
disabled: disabled_codes
.iter()
.map(|code| (*code).to_string())
.collect(),
unknown: unknown_codes
.iter()
.map(|code| (*code).to_string())
.collect(),
unmatched: unmatched_codes
.iter()
.map(|code| (*code).to_string())
.collect(),
}),
},
directive.range(),
);
if settings.rules.should_fix(diagnostic.kind.rule()) {
if valid_codes.is_empty() {
diagnostic.set_fix(Fix::suggested(delete_noqa(
directive.range(),
locator,
)));
} else {
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
format!("# noqa: {}", valid_codes.join(", ")),
directive.range(),
)));
}
}
diagnostics.push(diagnostic);
}
}
}
}
}
ignored_diagnostics.sort_unstable();
ignored_diagnostics
}
/// Generate a [`Edit`] to delete a `noqa` directive.
fn delete_noqa(range: TextRange, locator: &Locator) -> Edit {
let line_range = locator.line_range(range.start());
// Compute the leading space.
let prefix = locator.slice(TextRange::new(line_range.start(), range.start()));
let leading_space = prefix
.rfind(|c: char| !c.is_whitespace())
.map_or(prefix.len(), |i| prefix.len() - i - 1);
let leading_space_len = TextSize::try_from(leading_space).unwrap();
// Compute the trailing space.
let suffix = locator.slice(TextRange::new(range.end(), line_range.end()));
let trailing_space = suffix
.find(|c: char| !c.is_whitespace())
.map_or(suffix.len(), |i| i);
let trailing_space_len = TextSize::try_from(trailing_space).unwrap();
// Ex) `# noqa`
if line_range
== TextRange::new(
range.start() - leading_space_len,
range.end() + trailing_space_len,
)
{
let full_line_end = locator.full_line_end(line_range.end());
Edit::deletion(line_range.start(), full_line_end)
}
// Ex) `x = 1 # noqa`
else if range.end() + trailing_space_len == line_range.end() {
Edit::deletion(range.start() - leading_space_len, line_range.end())
}
// Ex) `x = 1 # noqa # type: ignore`
else if locator.contents()[usize::from(range.end() + trailing_space_len)..].starts_with('#') {
Edit::deletion(range.start(), range.end() + trailing_space_len)
}
// Ex) `x = 1 # noqa here`
else {
Edit::deletion(
range.start() + "# ".text_len(),
range.end() + trailing_space_len,
)
}
}

View File

@@ -1,193 +0,0 @@
//! Lint rules based on token traversal.
use std::path::Path;
use ruff_python_parser::lexer::LexResult;
use ruff_python_parser::Tok;
use ruff_diagnostics::Diagnostic;
use ruff_python_index::Indexer;
use ruff_source_file::Locator;
use crate::directives::TodoComment;
use crate::lex::docstring_detection::StateMachine;
use crate::registry::{AsRule, Rule};
use crate::rules::ruff::rules::Context;
use crate::rules::{
eradicate, flake8_commas, flake8_executable, flake8_fixme, flake8_implicit_str_concat,
flake8_pyi, flake8_quotes, flake8_todos, pycodestyle, pygrep_hooks, pylint, pyupgrade, ruff,
};
use crate::settings::Settings;
pub(crate) fn check_tokens(
tokens: &[LexResult],
path: &Path,
locator: &Locator,
indexer: &Indexer,
settings: &Settings,
is_stub: bool,
) -> Vec<Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = vec![];
if settings.rules.enabled(Rule::BlanketNOQA) {
pygrep_hooks::rules::blanket_noqa(&mut diagnostics, indexer, locator);
}
if settings.rules.enabled(Rule::BlanketTypeIgnore) {
pygrep_hooks::rules::blanket_type_ignore(&mut diagnostics, indexer, locator);
}
if settings.rules.any_enabled(&[
Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring,
Rule::AmbiguousUnicodeCharacterComment,
]) {
let mut state_machine = StateMachine::default();
for &(ref tok, range) in tokens.iter().flatten() {
let is_docstring = state_machine.consume(tok);
if matches!(tok, Tok::String { .. } | Tok::Comment(_)) {
ruff::rules::ambiguous_unicode_character(
&mut diagnostics,
locator,
range,
if tok.is_string() {
if is_docstring {
Context::Docstring
} else {
Context::String
}
} else {
Context::Comment
},
settings,
);
}
}
}
if settings.rules.enabled(Rule::CommentedOutCode) {
eradicate::rules::commented_out_code(&mut diagnostics, locator, indexer, settings);
}
if settings.rules.enabled(Rule::UTF8EncodingDeclaration) {
pyupgrade::rules::unnecessary_coding_comment(&mut diagnostics, locator, indexer, settings);
}
if settings.rules.enabled(Rule::InvalidEscapeSequence) {
for (tok, range) in tokens.iter().flatten() {
if tok.is_string() {
pycodestyle::rules::invalid_escape_sequence(
&mut diagnostics,
locator,
*range,
settings.rules.should_fix(Rule::InvalidEscapeSequence),
);
}
}
}
if settings.rules.any_enabled(&[
Rule::InvalidCharacterBackspace,
Rule::InvalidCharacterSub,
Rule::InvalidCharacterEsc,
Rule::InvalidCharacterNul,
Rule::InvalidCharacterZeroWidthSpace,
]) {
for (tok, range) in tokens.iter().flatten() {
if tok.is_string() {
pylint::rules::invalid_string_characters(&mut diagnostics, *range, locator);
}
}
}
if settings.rules.any_enabled(&[
Rule::MultipleStatementsOnOneLineColon,
Rule::MultipleStatementsOnOneLineSemicolon,
Rule::UselessSemicolon,
]) {
pycodestyle::rules::compound_statements(
&mut diagnostics,
tokens,
locator,
indexer,
settings,
);
}
if settings.rules.any_enabled(&[
Rule::BadQuotesInlineString,
Rule::BadQuotesMultilineString,
Rule::BadQuotesDocstring,
Rule::AvoidableEscapedQuote,
]) {
flake8_quotes::rules::from_tokens(&mut diagnostics, tokens, locator, settings);
}
if settings.rules.any_enabled(&[
Rule::SingleLineImplicitStringConcatenation,
Rule::MultiLineImplicitStringConcatenation,
]) {
flake8_implicit_str_concat::rules::implicit(
&mut diagnostics,
tokens,
&settings.flake8_implicit_str_concat,
locator,
);
}
if settings.rules.any_enabled(&[
Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma,
]) {
flake8_commas::rules::trailing_commas(&mut diagnostics, tokens, locator, settings);
}
if settings.rules.enabled(Rule::ExtraneousParentheses) {
pyupgrade::rules::extraneous_parentheses(&mut diagnostics, tokens, locator, settings);
}
if is_stub && settings.rules.enabled(Rule::TypeCommentInStub) {
flake8_pyi::rules::type_comment_in_stub(&mut diagnostics, locator, indexer);
}
if settings.rules.any_enabled(&[
Rule::ShebangNotExecutable,
Rule::ShebangMissingExecutableFile,
Rule::ShebangLeadingWhitespace,
Rule::ShebangNotFirstLine,
Rule::ShebangMissingPython,
]) {
flake8_executable::rules::from_tokens(tokens, path, locator, settings, &mut diagnostics);
}
if settings.rules.any_enabled(&[
Rule::InvalidTodoTag,
Rule::MissingTodoAuthor,
Rule::MissingTodoLink,
Rule::MissingTodoColon,
Rule::MissingTodoDescription,
Rule::InvalidTodoCapitalization,
Rule::MissingSpaceAfterTodoColon,
Rule::LineContainsFixme,
Rule::LineContainsXxx,
Rule::LineContainsTodo,
Rule::LineContainsHack,
]) {
let todo_comments: Vec<TodoComment> = indexer
.comment_ranges()
.iter()
.enumerate()
.filter_map(|(i, comment_range)| {
let comment = locator.slice(*comment_range);
TodoComment::from_comment(comment, *comment_range, i)
})
.collect();
flake8_todos::rules::todos(&mut diagnostics, &todo_comments, locator, indexer, settings);
flake8_fixme::rules::todos(&mut diagnostics, &todo_comments);
}
diagnostics.retain(|diagnostic| settings.rules.enabled(diagnostic.kind.rule()));
diagnostics
}

View File

@@ -1,922 +0,0 @@
/// In this module we generate [`Rule`], an enum of all rules, and [`RuleCodePrefix`], an enum of
/// all rules categories. A rule category is something like pyflakes or flake8-todos. Each rule
/// category contains all rules and their common prefixes, i.e. everything you can specify in
/// `--select`. For pylint this is e.g. C0414 and E0118 but also C and E01.
use std::fmt::Formatter;
use strum_macros::{AsRefStr, EnumIter};
use ruff_diagnostics::Violation;
use crate::registry::{AsRule, Linter};
use crate::rule_selector::is_single_rule_selector;
use crate::rules;
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub struct NoqaCode(&'static str, &'static str);
impl NoqaCode {
/// Return the prefix for the [`NoqaCode`], e.g., `SIM` for `SIM101`.
pub fn prefix(&self) -> &str {
self.0
}
/// Return the suffix for the [`NoqaCode`], e.g., `101` for `SIM101`.
pub fn suffix(&self) -> &str {
self.1
}
}
impl std::fmt::Debug for NoqaCode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
impl std::fmt::Display for NoqaCode {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{}{}", self.0, self.1)
}
}
impl PartialEq<&str> for NoqaCode {
fn eq(&self, other: &&str) -> bool {
match other.strip_prefix(self.0) {
Some(suffix) => suffix == self.1,
None => false,
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum RuleGroup {
/// The rule has not been assigned to any specific group.
Unspecified,
/// The rule is unstable, and preview mode must be enabled for usage.
Preview,
/// Legacy category for unstable rules, supports backwards compatible selection.
#[deprecated(note = "Use `RuleGroup::Preview` for new rules instead")]
Nursery,
}
#[ruff_macros::map_codes]
pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
#[allow(clippy::enum_glob_use)]
use Linter::*;
#[rustfmt::skip]
Some(match (linter, code) {
// pycodestyle errors
(Pycodestyle, "E101") => (RuleGroup::Unspecified, rules::pycodestyle::rules::MixedSpacesAndTabs),
#[allow(deprecated)]
(Pycodestyle, "E111") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultiple),
#[allow(deprecated)]
(Pycodestyle, "E112") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoIndentedBlock),
#[allow(deprecated)]
(Pycodestyle, "E113") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedIndentation),
#[allow(deprecated)]
(Pycodestyle, "E114") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultipleComment),
#[allow(deprecated)]
(Pycodestyle, "E115") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoIndentedBlockComment),
#[allow(deprecated)]
(Pycodestyle, "E116") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedIndentationComment),
#[allow(deprecated)]
(Pycodestyle, "E117") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::OverIndented),
#[allow(deprecated)]
(Pycodestyle, "E201") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceAfterOpenBracket),
#[allow(deprecated)]
(Pycodestyle, "E202") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeCloseBracket),
#[allow(deprecated)]
(Pycodestyle, "E203") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforePunctuation),
#[allow(deprecated)]
(Pycodestyle, "E211") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeParameters),
#[allow(deprecated)]
(Pycodestyle, "E221") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeOperator),
#[allow(deprecated)]
(Pycodestyle, "E222") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterOperator),
#[allow(deprecated)]
(Pycodestyle, "E223") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabBeforeOperator),
#[allow(deprecated)]
(Pycodestyle, "E224") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterOperator),
#[allow(deprecated)]
(Pycodestyle, "E225") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundOperator),
#[allow(deprecated)]
(Pycodestyle, "E226") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundArithmeticOperator),
#[allow(deprecated)]
(Pycodestyle, "E227") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundBitwiseOrShiftOperator),
#[allow(deprecated)]
(Pycodestyle, "E228") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundModuloOperator),
#[allow(deprecated)]
(Pycodestyle, "E231") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespace),
#[allow(deprecated)]
(Pycodestyle, "E241") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterComma),
#[allow(deprecated)]
(Pycodestyle, "E242") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterComma),
#[allow(deprecated)]
(Pycodestyle, "E251") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedSpacesAroundKeywordParameterEquals),
#[allow(deprecated)]
(Pycodestyle, "E252") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundParameterEquals),
#[allow(deprecated)]
(Pycodestyle, "E261") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TooFewSpacesBeforeInlineComment),
#[allow(deprecated)]
(Pycodestyle, "E262") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoSpaceAfterInlineComment),
#[allow(deprecated)]
(Pycodestyle, "E265") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoSpaceAfterBlockComment),
#[allow(deprecated)]
(Pycodestyle, "E266") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleLeadingHashesForBlockComment),
#[allow(deprecated)]
(Pycodestyle, "E271") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterKeyword),
#[allow(deprecated)]
(Pycodestyle, "E272") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeKeyword),
#[allow(deprecated)]
(Pycodestyle, "E273") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterKeyword),
#[allow(deprecated)]
(Pycodestyle, "E274") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabBeforeKeyword),
#[allow(deprecated)]
(Pycodestyle, "E275") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAfterKeyword),
(Pycodestyle, "E401") => (RuleGroup::Unspecified, rules::pycodestyle::rules::MultipleImportsOnOneLine),
(Pycodestyle, "E402") => (RuleGroup::Unspecified, rules::pycodestyle::rules::ModuleImportNotAtTopOfFile),
(Pycodestyle, "E501") => (RuleGroup::Unspecified, rules::pycodestyle::rules::LineTooLong),
(Pycodestyle, "E701") => (RuleGroup::Unspecified, rules::pycodestyle::rules::MultipleStatementsOnOneLineColon),
(Pycodestyle, "E702") => (RuleGroup::Unspecified, rules::pycodestyle::rules::MultipleStatementsOnOneLineSemicolon),
(Pycodestyle, "E703") => (RuleGroup::Unspecified, rules::pycodestyle::rules::UselessSemicolon),
(Pycodestyle, "E711") => (RuleGroup::Unspecified, rules::pycodestyle::rules::NoneComparison),
(Pycodestyle, "E712") => (RuleGroup::Unspecified, rules::pycodestyle::rules::TrueFalseComparison),
(Pycodestyle, "E713") => (RuleGroup::Unspecified, rules::pycodestyle::rules::NotInTest),
(Pycodestyle, "E714") => (RuleGroup::Unspecified, rules::pycodestyle::rules::NotIsTest),
(Pycodestyle, "E721") => (RuleGroup::Unspecified, rules::pycodestyle::rules::TypeComparison),
(Pycodestyle, "E722") => (RuleGroup::Unspecified, rules::pycodestyle::rules::BareExcept),
(Pycodestyle, "E731") => (RuleGroup::Unspecified, rules::pycodestyle::rules::LambdaAssignment),
(Pycodestyle, "E741") => (RuleGroup::Unspecified, rules::pycodestyle::rules::AmbiguousVariableName),
(Pycodestyle, "E742") => (RuleGroup::Unspecified, rules::pycodestyle::rules::AmbiguousClassName),
(Pycodestyle, "E743") => (RuleGroup::Unspecified, rules::pycodestyle::rules::AmbiguousFunctionName),
(Pycodestyle, "E902") => (RuleGroup::Unspecified, rules::pycodestyle::rules::IOError),
(Pycodestyle, "E999") => (RuleGroup::Unspecified, rules::pycodestyle::rules::SyntaxError),
// pycodestyle warnings
(Pycodestyle, "W191") => (RuleGroup::Unspecified, rules::pycodestyle::rules::TabIndentation),
(Pycodestyle, "W291") => (RuleGroup::Unspecified, rules::pycodestyle::rules::TrailingWhitespace),
(Pycodestyle, "W292") => (RuleGroup::Unspecified, rules::pycodestyle::rules::MissingNewlineAtEndOfFile),
(Pycodestyle, "W293") => (RuleGroup::Unspecified, rules::pycodestyle::rules::BlankLineWithWhitespace),
(Pycodestyle, "W505") => (RuleGroup::Unspecified, rules::pycodestyle::rules::DocLineTooLong),
(Pycodestyle, "W605") => (RuleGroup::Unspecified, rules::pycodestyle::rules::InvalidEscapeSequence),
// pyflakes
(Pyflakes, "401") => (RuleGroup::Unspecified, rules::pyflakes::rules::UnusedImport),
(Pyflakes, "402") => (RuleGroup::Unspecified, rules::pyflakes::rules::ImportShadowedByLoopVar),
(Pyflakes, "403") => (RuleGroup::Unspecified, rules::pyflakes::rules::UndefinedLocalWithImportStar),
(Pyflakes, "404") => (RuleGroup::Unspecified, rules::pyflakes::rules::LateFutureImport),
(Pyflakes, "405") => (RuleGroup::Unspecified, rules::pyflakes::rules::UndefinedLocalWithImportStarUsage),
(Pyflakes, "406") => (RuleGroup::Unspecified, rules::pyflakes::rules::UndefinedLocalWithNestedImportStarUsage),
(Pyflakes, "407") => (RuleGroup::Unspecified, rules::pyflakes::rules::FutureFeatureNotDefined),
(Pyflakes, "501") => (RuleGroup::Unspecified, rules::pyflakes::rules::PercentFormatInvalidFormat),
(Pyflakes, "502") => (RuleGroup::Unspecified, rules::pyflakes::rules::PercentFormatExpectedMapping),
(Pyflakes, "503") => (RuleGroup::Unspecified, rules::pyflakes::rules::PercentFormatExpectedSequence),
(Pyflakes, "504") => (RuleGroup::Unspecified, rules::pyflakes::rules::PercentFormatExtraNamedArguments),
(Pyflakes, "505") => (RuleGroup::Unspecified, rules::pyflakes::rules::PercentFormatMissingArgument),
(Pyflakes, "506") => (RuleGroup::Unspecified, rules::pyflakes::rules::PercentFormatMixedPositionalAndNamed),
(Pyflakes, "507") => (RuleGroup::Unspecified, rules::pyflakes::rules::PercentFormatPositionalCountMismatch),
(Pyflakes, "508") => (RuleGroup::Unspecified, rules::pyflakes::rules::PercentFormatStarRequiresSequence),
(Pyflakes, "509") => (RuleGroup::Unspecified, rules::pyflakes::rules::PercentFormatUnsupportedFormatCharacter),
(Pyflakes, "521") => (RuleGroup::Unspecified, rules::pyflakes::rules::StringDotFormatInvalidFormat),
(Pyflakes, "522") => (RuleGroup::Unspecified, rules::pyflakes::rules::StringDotFormatExtraNamedArguments),
(Pyflakes, "523") => (RuleGroup::Unspecified, rules::pyflakes::rules::StringDotFormatExtraPositionalArguments),
(Pyflakes, "524") => (RuleGroup::Unspecified, rules::pyflakes::rules::StringDotFormatMissingArguments),
(Pyflakes, "525") => (RuleGroup::Unspecified, rules::pyflakes::rules::StringDotFormatMixingAutomatic),
(Pyflakes, "541") => (RuleGroup::Unspecified, rules::pyflakes::rules::FStringMissingPlaceholders),
(Pyflakes, "601") => (RuleGroup::Unspecified, rules::pyflakes::rules::MultiValueRepeatedKeyLiteral),
(Pyflakes, "602") => (RuleGroup::Unspecified, rules::pyflakes::rules::MultiValueRepeatedKeyVariable),
(Pyflakes, "621") => (RuleGroup::Unspecified, rules::pyflakes::rules::ExpressionsInStarAssignment),
(Pyflakes, "622") => (RuleGroup::Unspecified, rules::pyflakes::rules::MultipleStarredExpressions),
(Pyflakes, "631") => (RuleGroup::Unspecified, rules::pyflakes::rules::AssertTuple),
(Pyflakes, "632") => (RuleGroup::Unspecified, rules::pyflakes::rules::IsLiteral),
(Pyflakes, "633") => (RuleGroup::Unspecified, rules::pyflakes::rules::InvalidPrintSyntax),
(Pyflakes, "634") => (RuleGroup::Unspecified, rules::pyflakes::rules::IfTuple),
(Pyflakes, "701") => (RuleGroup::Unspecified, rules::pyflakes::rules::BreakOutsideLoop),
(Pyflakes, "702") => (RuleGroup::Unspecified, rules::pyflakes::rules::ContinueOutsideLoop),
(Pyflakes, "704") => (RuleGroup::Unspecified, rules::pyflakes::rules::YieldOutsideFunction),
(Pyflakes, "706") => (RuleGroup::Unspecified, rules::pyflakes::rules::ReturnOutsideFunction),
(Pyflakes, "707") => (RuleGroup::Unspecified, rules::pyflakes::rules::DefaultExceptNotLast),
(Pyflakes, "722") => (RuleGroup::Unspecified, rules::pyflakes::rules::ForwardAnnotationSyntaxError),
(Pyflakes, "811") => (RuleGroup::Unspecified, rules::pyflakes::rules::RedefinedWhileUnused),
(Pyflakes, "821") => (RuleGroup::Unspecified, rules::pyflakes::rules::UndefinedName),
(Pyflakes, "822") => (RuleGroup::Unspecified, rules::pyflakes::rules::UndefinedExport),
(Pyflakes, "823") => (RuleGroup::Unspecified, rules::pyflakes::rules::UndefinedLocal),
(Pyflakes, "841") => (RuleGroup::Unspecified, rules::pyflakes::rules::UnusedVariable),
(Pyflakes, "842") => (RuleGroup::Unspecified, rules::pyflakes::rules::UnusedAnnotation),
(Pyflakes, "901") => (RuleGroup::Unspecified, rules::pyflakes::rules::RaiseNotImplemented),
// pylint
(Pylint, "C0105") => (RuleGroup::Unspecified, rules::pylint::rules::TypeNameIncorrectVariance),
(Pylint, "C0131") => (RuleGroup::Unspecified, rules::pylint::rules::TypeBivariance),
(Pylint, "C0132") => (RuleGroup::Unspecified, rules::pylint::rules::TypeParamNameMismatch),
(Pylint, "C0205") => (RuleGroup::Unspecified, rules::pylint::rules::SingleStringSlots),
(Pylint, "C0208") => (RuleGroup::Unspecified, rules::pylint::rules::IterationOverSet),
(Pylint, "C0414") => (RuleGroup::Unspecified, rules::pylint::rules::UselessImportAlias),
#[allow(deprecated)]
(Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString),
(Pylint, "C3002") => (RuleGroup::Unspecified, rules::pylint::rules::UnnecessaryDirectLambdaCall),
(Pylint, "E0100") => (RuleGroup::Unspecified, rules::pylint::rules::YieldInInit),
(Pylint, "E0101") => (RuleGroup::Unspecified, rules::pylint::rules::ReturnInInit),
(Pylint, "E0116") => (RuleGroup::Unspecified, rules::pylint::rules::ContinueInFinally),
(Pylint, "E0117") => (RuleGroup::Unspecified, rules::pylint::rules::NonlocalWithoutBinding),
(Pylint, "E0118") => (RuleGroup::Unspecified, rules::pylint::rules::LoadBeforeGlobalDeclaration),
(Pylint, "E0241") => (RuleGroup::Unspecified, rules::pylint::rules::DuplicateBases),
(Pylint, "E0302") => (RuleGroup::Unspecified, rules::pylint::rules::UnexpectedSpecialMethodSignature),
(Pylint, "E0307") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidStrReturnType),
(Pylint, "E0604") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidAllObject),
(Pylint, "E0605") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidAllFormat),
(Pylint, "E1142") => (RuleGroup::Unspecified, rules::pylint::rules::AwaitOutsideAsync),
(Pylint, "E1205") => (RuleGroup::Unspecified, rules::pylint::rules::LoggingTooManyArgs),
(Pylint, "E1206") => (RuleGroup::Unspecified, rules::pylint::rules::LoggingTooFewArgs),
(Pylint, "E1300") => (RuleGroup::Unspecified, rules::pylint::rules::BadStringFormatCharacter),
(Pylint, "E1307") => (RuleGroup::Unspecified, rules::pylint::rules::BadStringFormatType),
(Pylint, "E1310") => (RuleGroup::Unspecified, rules::pylint::rules::BadStrStripCall),
(Pylint, "E1507") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidEnvvarValue),
(Pylint, "E1700") => (RuleGroup::Unspecified, rules::pylint::rules::YieldFromInAsyncFunction),
(Pylint, "E2502") => (RuleGroup::Unspecified, rules::pylint::rules::BidirectionalUnicode),
(Pylint, "E2510") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidCharacterBackspace),
(Pylint, "E2512") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidCharacterSub),
(Pylint, "E2513") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidCharacterEsc),
(Pylint, "E2514") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidCharacterNul),
(Pylint, "E2515") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidCharacterZeroWidthSpace),
(Pylint, "R0124") => (RuleGroup::Unspecified, rules::pylint::rules::ComparisonWithItself),
(Pylint, "R0133") => (RuleGroup::Unspecified, rules::pylint::rules::ComparisonOfConstant),
(Pylint, "R0206") => (RuleGroup::Unspecified, rules::pylint::rules::PropertyWithParameters),
(Pylint, "R0402") => (RuleGroup::Unspecified, rules::pylint::rules::ManualFromImport),
(Pylint, "R0911") => (RuleGroup::Unspecified, rules::pylint::rules::TooManyReturnStatements),
(Pylint, "R0912") => (RuleGroup::Unspecified, rules::pylint::rules::TooManyBranches),
(Pylint, "R0913") => (RuleGroup::Unspecified, rules::pylint::rules::TooManyArguments),
(Pylint, "R0915") => (RuleGroup::Unspecified, rules::pylint::rules::TooManyStatements),
(Pylint, "R1701") => (RuleGroup::Unspecified, rules::pylint::rules::RepeatedIsinstanceCalls),
(Pylint, "R1711") => (RuleGroup::Unspecified, rules::pylint::rules::UselessReturn),
(Pylint, "R1714") => (RuleGroup::Unspecified, rules::pylint::rules::RepeatedEqualityComparison),
(Pylint, "R1722") => (RuleGroup::Unspecified, rules::pylint::rules::SysExitAlias),
(Pylint, "R2004") => (RuleGroup::Unspecified, rules::pylint::rules::MagicValueComparison),
(Pylint, "R5501") => (RuleGroup::Unspecified, rules::pylint::rules::CollapsibleElseIf),
#[allow(deprecated)]
(Pylint, "R6301") => (RuleGroup::Nursery, rules::pylint::rules::NoSelfUse),
(Pylint, "W0120") => (RuleGroup::Unspecified, rules::pylint::rules::UselessElseOnLoop),
(Pylint, "W0127") => (RuleGroup::Unspecified, rules::pylint::rules::SelfAssigningVariable),
(Pylint, "W0129") => (RuleGroup::Unspecified, rules::pylint::rules::AssertOnStringLiteral),
(Pylint, "W0131") => (RuleGroup::Unspecified, rules::pylint::rules::NamedExprWithoutContext),
(Pylint, "W0406") => (RuleGroup::Unspecified, rules::pylint::rules::ImportSelf),
(Pylint, "W0602") => (RuleGroup::Unspecified, rules::pylint::rules::GlobalVariableNotAssigned),
(Pylint, "W0603") => (RuleGroup::Unspecified, rules::pylint::rules::GlobalStatement),
(Pylint, "W0711") => (RuleGroup::Unspecified, rules::pylint::rules::BinaryOpException),
(Pylint, "W1508") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidEnvvarDefault),
(Pylint, "W1509") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessPopenPreexecFn),
(Pylint, "W1510") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessRunWithoutCheck),
#[allow(deprecated)]
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
(Pylint, "W2901") => (RuleGroup::Unspecified, rules::pylint::rules::RedefinedLoopName),
#[allow(deprecated)]
(Pylint, "W3201") => (RuleGroup::Nursery, rules::pylint::rules::BadDunderMethodName),
(Pylint, "W3301") => (RuleGroup::Unspecified, rules::pylint::rules::NestedMinMax),
// flake8-async
(Flake8Async, "100") => (RuleGroup::Unspecified, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction),
(Flake8Async, "101") => (RuleGroup::Unspecified, rules::flake8_async::rules::OpenSleepOrSubprocessInAsyncFunction),
(Flake8Async, "102") => (RuleGroup::Unspecified, rules::flake8_async::rules::BlockingOsCallInAsyncFunction),
// flake8-builtins
(Flake8Builtins, "001") => (RuleGroup::Unspecified, rules::flake8_builtins::rules::BuiltinVariableShadowing),
(Flake8Builtins, "002") => (RuleGroup::Unspecified, rules::flake8_builtins::rules::BuiltinArgumentShadowing),
(Flake8Builtins, "003") => (RuleGroup::Unspecified, rules::flake8_builtins::rules::BuiltinAttributeShadowing),
// flake8-bugbear
(Flake8Bugbear, "002") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::UnaryPrefixIncrementDecrement),
(Flake8Bugbear, "003") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::AssignmentToOsEnviron),
(Flake8Bugbear, "004") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::UnreliableCallableCheck),
(Flake8Bugbear, "005") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::StripWithMultiCharacters),
(Flake8Bugbear, "006") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::MutableArgumentDefault),
(Flake8Bugbear, "007") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::UnusedLoopControlVariable),
(Flake8Bugbear, "008") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::FunctionCallInDefaultArgument),
(Flake8Bugbear, "009") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::GetAttrWithConstant),
(Flake8Bugbear, "010") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::SetAttrWithConstant),
(Flake8Bugbear, "011") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::AssertFalse),
(Flake8Bugbear, "012") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::JumpStatementInFinally),
(Flake8Bugbear, "013") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::RedundantTupleInExceptionHandler),
(Flake8Bugbear, "014") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::DuplicateHandlerException),
(Flake8Bugbear, "015") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::UselessComparison),
(Flake8Bugbear, "016") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::RaiseLiteral),
(Flake8Bugbear, "017") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::AssertRaisesException),
(Flake8Bugbear, "018") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::UselessExpression),
(Flake8Bugbear, "019") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::CachedInstanceMethod),
(Flake8Bugbear, "020") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::LoopVariableOverridesIterator),
(Flake8Bugbear, "021") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::FStringDocstring),
(Flake8Bugbear, "022") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::UselessContextlibSuppress),
(Flake8Bugbear, "023") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::FunctionUsesLoopVariable),
(Flake8Bugbear, "024") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::AbstractBaseClassWithoutAbstractMethod),
(Flake8Bugbear, "025") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::DuplicateTryBlockException),
(Flake8Bugbear, "026") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::StarArgUnpackingAfterKeywordArg),
(Flake8Bugbear, "027") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::EmptyMethodWithoutAbstractDecorator),
(Flake8Bugbear, "028") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::NoExplicitStacklevel),
(Flake8Bugbear, "029") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::ExceptWithEmptyTuple),
(Flake8Bugbear, "030") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::ExceptWithNonExceptionClasses),
(Flake8Bugbear, "031") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::ReuseOfGroupbyGenerator),
(Flake8Bugbear, "032") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::UnintentionalTypeAnnotation),
(Flake8Bugbear, "033") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::DuplicateValue),
(Flake8Bugbear, "034") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::ReSubPositionalArgs),
(Flake8Bugbear, "904") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::RaiseWithoutFromInsideExcept),
(Flake8Bugbear, "905") => (RuleGroup::Unspecified, rules::flake8_bugbear::rules::ZipWithoutExplicitStrict),
// flake8-blind-except
(Flake8BlindExcept, "001") => (RuleGroup::Unspecified, rules::flake8_blind_except::rules::BlindExcept),
// flake8-comprehensions
(Flake8Comprehensions, "00") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryGeneratorList),
(Flake8Comprehensions, "01") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryGeneratorSet),
(Flake8Comprehensions, "02") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryGeneratorDict),
(Flake8Comprehensions, "03") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryListComprehensionSet),
(Flake8Comprehensions, "04") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryListComprehensionDict),
(Flake8Comprehensions, "05") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryLiteralSet),
(Flake8Comprehensions, "06") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryLiteralDict),
(Flake8Comprehensions, "08") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryCollectionCall),
(Flake8Comprehensions, "09") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryLiteralWithinTupleCall),
(Flake8Comprehensions, "10") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryLiteralWithinListCall),
(Flake8Comprehensions, "11") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryListCall),
(Flake8Comprehensions, "13") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryCallAroundSorted),
(Flake8Comprehensions, "14") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryDoubleCastOrProcess),
(Flake8Comprehensions, "15") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessarySubscriptReversal),
(Flake8Comprehensions, "16") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryComprehension),
(Flake8Comprehensions, "17") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryMap),
(Flake8Comprehensions, "18") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryLiteralWithinDictCall),
(Flake8Comprehensions, "19") => (RuleGroup::Unspecified, rules::flake8_comprehensions::rules::UnnecessaryComprehensionAnyAll),
// flake8-debugger
(Flake8Debugger, "0") => (RuleGroup::Unspecified, rules::flake8_debugger::rules::Debugger),
// mccabe
(McCabe, "1") => (RuleGroup::Unspecified, rules::mccabe::rules::ComplexStructure),
// flake8-tidy-imports
(Flake8TidyImports, "251") => (RuleGroup::Unspecified, rules::flake8_tidy_imports::rules::BannedApi),
(Flake8TidyImports, "252") => (RuleGroup::Unspecified, rules::flake8_tidy_imports::rules::RelativeImports),
(Flake8TidyImports, "253") => (RuleGroup::Unspecified, rules::flake8_tidy_imports::rules::BannedModuleLevelImports),
// flake8-return
(Flake8Return, "501") => (RuleGroup::Unspecified, rules::flake8_return::rules::UnnecessaryReturnNone),
(Flake8Return, "502") => (RuleGroup::Unspecified, rules::flake8_return::rules::ImplicitReturnValue),
(Flake8Return, "503") => (RuleGroup::Unspecified, rules::flake8_return::rules::ImplicitReturn),
(Flake8Return, "504") => (RuleGroup::Unspecified, rules::flake8_return::rules::UnnecessaryAssign),
(Flake8Return, "505") => (RuleGroup::Unspecified, rules::flake8_return::rules::SuperfluousElseReturn),
(Flake8Return, "506") => (RuleGroup::Unspecified, rules::flake8_return::rules::SuperfluousElseRaise),
(Flake8Return, "507") => (RuleGroup::Unspecified, rules::flake8_return::rules::SuperfluousElseContinue),
(Flake8Return, "508") => (RuleGroup::Unspecified, rules::flake8_return::rules::SuperfluousElseBreak),
// flake8-gettext
(Flake8GetText, "001") => (RuleGroup::Unspecified, rules::flake8_gettext::rules::FStringInGetTextFuncCall),
(Flake8GetText, "002") => (RuleGroup::Unspecified, rules::flake8_gettext::rules::FormatInGetTextFuncCall),
(Flake8GetText, "003") => (RuleGroup::Unspecified, rules::flake8_gettext::rules::PrintfInGetTextFuncCall),
// flake8-implicit-str-concat
(Flake8ImplicitStrConcat, "001") => (RuleGroup::Unspecified, rules::flake8_implicit_str_concat::rules::SingleLineImplicitStringConcatenation),
(Flake8ImplicitStrConcat, "002") => (RuleGroup::Unspecified, rules::flake8_implicit_str_concat::rules::MultiLineImplicitStringConcatenation),
(Flake8ImplicitStrConcat, "003") => (RuleGroup::Unspecified, rules::flake8_implicit_str_concat::rules::ExplicitStringConcatenation),
// flake8-print
(Flake8Print, "1") => (RuleGroup::Unspecified, rules::flake8_print::rules::Print),
(Flake8Print, "3") => (RuleGroup::Unspecified, rules::flake8_print::rules::PPrint),
// flake8-quotes
(Flake8Quotes, "000") => (RuleGroup::Unspecified, rules::flake8_quotes::rules::BadQuotesInlineString),
(Flake8Quotes, "001") => (RuleGroup::Unspecified, rules::flake8_quotes::rules::BadQuotesMultilineString),
(Flake8Quotes, "002") => (RuleGroup::Unspecified, rules::flake8_quotes::rules::BadQuotesDocstring),
(Flake8Quotes, "003") => (RuleGroup::Unspecified, rules::flake8_quotes::rules::AvoidableEscapedQuote),
// flake8-annotations
(Flake8Annotations, "001") => (RuleGroup::Unspecified, rules::flake8_annotations::rules::MissingTypeFunctionArgument),
(Flake8Annotations, "002") => (RuleGroup::Unspecified, rules::flake8_annotations::rules::MissingTypeArgs),
(Flake8Annotations, "003") => (RuleGroup::Unspecified, rules::flake8_annotations::rules::MissingTypeKwargs),
(Flake8Annotations, "101") => (RuleGroup::Unspecified, rules::flake8_annotations::rules::MissingTypeSelf),
(Flake8Annotations, "102") => (RuleGroup::Unspecified, rules::flake8_annotations::rules::MissingTypeCls),
(Flake8Annotations, "201") => (RuleGroup::Unspecified, rules::flake8_annotations::rules::MissingReturnTypeUndocumentedPublicFunction),
(Flake8Annotations, "202") => (RuleGroup::Unspecified, rules::flake8_annotations::rules::MissingReturnTypePrivateFunction),
(Flake8Annotations, "204") => (RuleGroup::Unspecified, rules::flake8_annotations::rules::MissingReturnTypeSpecialMethod),
(Flake8Annotations, "205") => (RuleGroup::Unspecified, rules::flake8_annotations::rules::MissingReturnTypeStaticMethod),
(Flake8Annotations, "206") => (RuleGroup::Unspecified, rules::flake8_annotations::rules::MissingReturnTypeClassMethod),
(Flake8Annotations, "401") => (RuleGroup::Unspecified, rules::flake8_annotations::rules::AnyType),
// flake8-future-annotations
(Flake8FutureAnnotations, "100") => (RuleGroup::Unspecified, rules::flake8_future_annotations::rules::FutureRewritableTypeAnnotation),
(Flake8FutureAnnotations, "102") => (RuleGroup::Unspecified, rules::flake8_future_annotations::rules::FutureRequiredTypeAnnotation),
// flake8-2020
(Flake82020, "101") => (RuleGroup::Unspecified, rules::flake8_2020::rules::SysVersionSlice3),
(Flake82020, "102") => (RuleGroup::Unspecified, rules::flake8_2020::rules::SysVersion2),
(Flake82020, "103") => (RuleGroup::Unspecified, rules::flake8_2020::rules::SysVersionCmpStr3),
(Flake82020, "201") => (RuleGroup::Unspecified, rules::flake8_2020::rules::SysVersionInfo0Eq3),
(Flake82020, "202") => (RuleGroup::Unspecified, rules::flake8_2020::rules::SixPY3),
(Flake82020, "203") => (RuleGroup::Unspecified, rules::flake8_2020::rules::SysVersionInfo1CmpInt),
(Flake82020, "204") => (RuleGroup::Unspecified, rules::flake8_2020::rules::SysVersionInfoMinorCmpInt),
(Flake82020, "301") => (RuleGroup::Unspecified, rules::flake8_2020::rules::SysVersion0),
(Flake82020, "302") => (RuleGroup::Unspecified, rules::flake8_2020::rules::SysVersionCmpStr10),
(Flake82020, "303") => (RuleGroup::Unspecified, rules::flake8_2020::rules::SysVersionSlice1),
// flake8-simplify
(Flake8Simplify, "101") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::DuplicateIsinstanceCall),
(Flake8Simplify, "102") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::CollapsibleIf),
(Flake8Simplify, "103") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::NeedlessBool),
(Flake8Simplify, "105") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::SuppressibleException),
(Flake8Simplify, "107") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::ReturnInTryExceptFinally),
(Flake8Simplify, "108") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::IfElseBlockInsteadOfIfExp),
(Flake8Simplify, "109") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::CompareWithTuple),
(Flake8Simplify, "110") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::ReimplementedBuiltin),
(Flake8Simplify, "112") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::UncapitalizedEnvironmentVariables),
(Flake8Simplify, "114") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::IfWithSameArms),
(Flake8Simplify, "115") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::OpenFileWithContextHandler),
(Flake8Simplify, "116") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::IfElseBlockInsteadOfDictLookup),
(Flake8Simplify, "117") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::MultipleWithStatements),
(Flake8Simplify, "118") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::InDictKeys),
(Flake8Simplify, "201") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::NegateEqualOp),
(Flake8Simplify, "202") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::NegateNotEqualOp),
(Flake8Simplify, "208") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::DoubleNegation),
(Flake8Simplify, "210") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::IfExprWithTrueFalse),
(Flake8Simplify, "211") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::IfExprWithFalseTrue),
(Flake8Simplify, "212") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::IfExprWithTwistedArms),
(Flake8Simplify, "220") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::ExprAndNotExpr),
(Flake8Simplify, "221") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::ExprOrNotExpr),
(Flake8Simplify, "222") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::ExprOrTrue),
(Flake8Simplify, "223") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::ExprAndFalse),
(Flake8Simplify, "300") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::YodaConditions),
(Flake8Simplify, "401") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::IfElseBlockInsteadOfDictGet),
(Flake8Simplify, "910") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::DictGetWithNoneDefault),
// flake8-copyright
#[allow(deprecated)]
(Flake8Copyright, "001") => (RuleGroup::Nursery, rules::flake8_copyright::rules::MissingCopyrightNotice),
// pyupgrade
(Pyupgrade, "001") => (RuleGroup::Unspecified, rules::pyupgrade::rules::UselessMetaclassType),
(Pyupgrade, "003") => (RuleGroup::Unspecified, rules::pyupgrade::rules::TypeOfPrimitive),
(Pyupgrade, "004") => (RuleGroup::Unspecified, rules::pyupgrade::rules::UselessObjectInheritance),
(Pyupgrade, "005") => (RuleGroup::Unspecified, rules::pyupgrade::rules::DeprecatedUnittestAlias),
(Pyupgrade, "006") => (RuleGroup::Unspecified, rules::pyupgrade::rules::NonPEP585Annotation),
(Pyupgrade, "007") => (RuleGroup::Unspecified, rules::pyupgrade::rules::NonPEP604Annotation),
(Pyupgrade, "008") => (RuleGroup::Unspecified, rules::pyupgrade::rules::SuperCallWithParameters),
(Pyupgrade, "009") => (RuleGroup::Unspecified, rules::pyupgrade::rules::UTF8EncodingDeclaration),
(Pyupgrade, "010") => (RuleGroup::Unspecified, rules::pyupgrade::rules::UnnecessaryFutureImport),
(Pyupgrade, "011") => (RuleGroup::Unspecified, rules::pyupgrade::rules::LRUCacheWithoutParameters),
(Pyupgrade, "012") => (RuleGroup::Unspecified, rules::pyupgrade::rules::UnnecessaryEncodeUTF8),
(Pyupgrade, "013") => (RuleGroup::Unspecified, rules::pyupgrade::rules::ConvertTypedDictFunctionalToClass),
(Pyupgrade, "014") => (RuleGroup::Unspecified, rules::pyupgrade::rules::ConvertNamedTupleFunctionalToClass),
(Pyupgrade, "015") => (RuleGroup::Unspecified, rules::pyupgrade::rules::RedundantOpenModes),
(Pyupgrade, "017") => (RuleGroup::Unspecified, rules::pyupgrade::rules::DatetimeTimezoneUTC),
(Pyupgrade, "018") => (RuleGroup::Unspecified, rules::pyupgrade::rules::NativeLiterals),
(Pyupgrade, "019") => (RuleGroup::Unspecified, rules::pyupgrade::rules::TypingTextStrAlias),
(Pyupgrade, "020") => (RuleGroup::Unspecified, rules::pyupgrade::rules::OpenAlias),
(Pyupgrade, "021") => (RuleGroup::Unspecified, rules::pyupgrade::rules::ReplaceUniversalNewlines),
(Pyupgrade, "022") => (RuleGroup::Unspecified, rules::pyupgrade::rules::ReplaceStdoutStderr),
(Pyupgrade, "023") => (RuleGroup::Unspecified, rules::pyupgrade::rules::DeprecatedCElementTree),
(Pyupgrade, "024") => (RuleGroup::Unspecified, rules::pyupgrade::rules::OSErrorAlias),
(Pyupgrade, "025") => (RuleGroup::Unspecified, rules::pyupgrade::rules::UnicodeKindPrefix),
(Pyupgrade, "026") => (RuleGroup::Unspecified, rules::pyupgrade::rules::DeprecatedMockImport),
(Pyupgrade, "027") => (RuleGroup::Unspecified, rules::pyupgrade::rules::UnpackedListComprehension),
(Pyupgrade, "028") => (RuleGroup::Unspecified, rules::pyupgrade::rules::YieldInForLoop),
(Pyupgrade, "029") => (RuleGroup::Unspecified, rules::pyupgrade::rules::UnnecessaryBuiltinImport),
(Pyupgrade, "030") => (RuleGroup::Unspecified, rules::pyupgrade::rules::FormatLiterals),
(Pyupgrade, "031") => (RuleGroup::Unspecified, rules::pyupgrade::rules::PrintfStringFormatting),
(Pyupgrade, "032") => (RuleGroup::Unspecified, rules::pyupgrade::rules::FString),
(Pyupgrade, "033") => (RuleGroup::Unspecified, rules::pyupgrade::rules::LRUCacheWithMaxsizeNone),
(Pyupgrade, "034") => (RuleGroup::Unspecified, rules::pyupgrade::rules::ExtraneousParentheses),
(Pyupgrade, "035") => (RuleGroup::Unspecified, rules::pyupgrade::rules::DeprecatedImport),
(Pyupgrade, "036") => (RuleGroup::Unspecified, rules::pyupgrade::rules::OutdatedVersionBlock),
(Pyupgrade, "037") => (RuleGroup::Unspecified, rules::pyupgrade::rules::QuotedAnnotation),
(Pyupgrade, "038") => (RuleGroup::Unspecified, rules::pyupgrade::rules::NonPEP604Isinstance),
(Pyupgrade, "039") => (RuleGroup::Unspecified, rules::pyupgrade::rules::UnnecessaryClassParentheses),
(Pyupgrade, "040") => (RuleGroup::Unspecified, rules::pyupgrade::rules::NonPEP695TypeAlias),
// pydocstyle
(Pydocstyle, "100") => (RuleGroup::Unspecified, rules::pydocstyle::rules::UndocumentedPublicModule),
(Pydocstyle, "101") => (RuleGroup::Unspecified, rules::pydocstyle::rules::UndocumentedPublicClass),
(Pydocstyle, "102") => (RuleGroup::Unspecified, rules::pydocstyle::rules::UndocumentedPublicMethod),
(Pydocstyle, "103") => (RuleGroup::Unspecified, rules::pydocstyle::rules::UndocumentedPublicFunction),
(Pydocstyle, "104") => (RuleGroup::Unspecified, rules::pydocstyle::rules::UndocumentedPublicPackage),
(Pydocstyle, "105") => (RuleGroup::Unspecified, rules::pydocstyle::rules::UndocumentedMagicMethod),
(Pydocstyle, "106") => (RuleGroup::Unspecified, rules::pydocstyle::rules::UndocumentedPublicNestedClass),
(Pydocstyle, "107") => (RuleGroup::Unspecified, rules::pydocstyle::rules::UndocumentedPublicInit),
(Pydocstyle, "200") => (RuleGroup::Unspecified, rules::pydocstyle::rules::FitsOnOneLine),
(Pydocstyle, "201") => (RuleGroup::Unspecified, rules::pydocstyle::rules::NoBlankLineBeforeFunction),
(Pydocstyle, "202") => (RuleGroup::Unspecified, rules::pydocstyle::rules::NoBlankLineAfterFunction),
(Pydocstyle, "203") => (RuleGroup::Unspecified, rules::pydocstyle::rules::OneBlankLineBeforeClass),
(Pydocstyle, "204") => (RuleGroup::Unspecified, rules::pydocstyle::rules::OneBlankLineAfterClass),
(Pydocstyle, "205") => (RuleGroup::Unspecified, rules::pydocstyle::rules::BlankLineAfterSummary),
(Pydocstyle, "206") => (RuleGroup::Unspecified, rules::pydocstyle::rules::IndentWithSpaces),
(Pydocstyle, "207") => (RuleGroup::Unspecified, rules::pydocstyle::rules::UnderIndentation),
(Pydocstyle, "208") => (RuleGroup::Unspecified, rules::pydocstyle::rules::OverIndentation),
(Pydocstyle, "209") => (RuleGroup::Unspecified, rules::pydocstyle::rules::NewLineAfterLastParagraph),
(Pydocstyle, "210") => (RuleGroup::Unspecified, rules::pydocstyle::rules::SurroundingWhitespace),
(Pydocstyle, "211") => (RuleGroup::Unspecified, rules::pydocstyle::rules::BlankLineBeforeClass),
(Pydocstyle, "212") => (RuleGroup::Unspecified, rules::pydocstyle::rules::MultiLineSummaryFirstLine),
(Pydocstyle, "213") => (RuleGroup::Unspecified, rules::pydocstyle::rules::MultiLineSummarySecondLine),
(Pydocstyle, "214") => (RuleGroup::Unspecified, rules::pydocstyle::rules::SectionNotOverIndented),
(Pydocstyle, "215") => (RuleGroup::Unspecified, rules::pydocstyle::rules::SectionUnderlineNotOverIndented),
(Pydocstyle, "300") => (RuleGroup::Unspecified, rules::pydocstyle::rules::TripleSingleQuotes),
(Pydocstyle, "301") => (RuleGroup::Unspecified, rules::pydocstyle::rules::EscapeSequenceInDocstring),
(Pydocstyle, "400") => (RuleGroup::Unspecified, rules::pydocstyle::rules::EndsInPeriod),
(Pydocstyle, "401") => (RuleGroup::Unspecified, rules::pydocstyle::rules::NonImperativeMood),
(Pydocstyle, "402") => (RuleGroup::Unspecified, rules::pydocstyle::rules::NoSignature),
(Pydocstyle, "403") => (RuleGroup::Unspecified, rules::pydocstyle::rules::FirstLineCapitalized),
(Pydocstyle, "404") => (RuleGroup::Unspecified, rules::pydocstyle::rules::DocstringStartsWithThis),
(Pydocstyle, "405") => (RuleGroup::Unspecified, rules::pydocstyle::rules::CapitalizeSectionName),
(Pydocstyle, "406") => (RuleGroup::Unspecified, rules::pydocstyle::rules::NewLineAfterSectionName),
(Pydocstyle, "407") => (RuleGroup::Unspecified, rules::pydocstyle::rules::DashedUnderlineAfterSection),
(Pydocstyle, "408") => (RuleGroup::Unspecified, rules::pydocstyle::rules::SectionUnderlineAfterName),
(Pydocstyle, "409") => (RuleGroup::Unspecified, rules::pydocstyle::rules::SectionUnderlineMatchesSectionLength),
(Pydocstyle, "410") => (RuleGroup::Unspecified, rules::pydocstyle::rules::NoBlankLineAfterSection),
(Pydocstyle, "411") => (RuleGroup::Unspecified, rules::pydocstyle::rules::NoBlankLineBeforeSection),
(Pydocstyle, "412") => (RuleGroup::Unspecified, rules::pydocstyle::rules::BlankLinesBetweenHeaderAndContent),
(Pydocstyle, "413") => (RuleGroup::Unspecified, rules::pydocstyle::rules::BlankLineAfterLastSection),
(Pydocstyle, "414") => (RuleGroup::Unspecified, rules::pydocstyle::rules::EmptyDocstringSection),
(Pydocstyle, "415") => (RuleGroup::Unspecified, rules::pydocstyle::rules::EndsInPunctuation),
(Pydocstyle, "416") => (RuleGroup::Unspecified, rules::pydocstyle::rules::SectionNameEndsInColon),
(Pydocstyle, "417") => (RuleGroup::Unspecified, rules::pydocstyle::rules::UndocumentedParam),
(Pydocstyle, "418") => (RuleGroup::Unspecified, rules::pydocstyle::rules::OverloadWithDocstring),
(Pydocstyle, "419") => (RuleGroup::Unspecified, rules::pydocstyle::rules::EmptyDocstring),
// pep8-naming
(PEP8Naming, "801") => (RuleGroup::Unspecified, rules::pep8_naming::rules::InvalidClassName),
(PEP8Naming, "802") => (RuleGroup::Unspecified, rules::pep8_naming::rules::InvalidFunctionName),
(PEP8Naming, "803") => (RuleGroup::Unspecified, rules::pep8_naming::rules::InvalidArgumentName),
(PEP8Naming, "804") => (RuleGroup::Unspecified, rules::pep8_naming::rules::InvalidFirstArgumentNameForClassMethod),
(PEP8Naming, "805") => (RuleGroup::Unspecified, rules::pep8_naming::rules::InvalidFirstArgumentNameForMethod),
(PEP8Naming, "806") => (RuleGroup::Unspecified, rules::pep8_naming::rules::NonLowercaseVariableInFunction),
(PEP8Naming, "807") => (RuleGroup::Unspecified, rules::pep8_naming::rules::DunderFunctionName),
(PEP8Naming, "811") => (RuleGroup::Unspecified, rules::pep8_naming::rules::ConstantImportedAsNonConstant),
(PEP8Naming, "812") => (RuleGroup::Unspecified, rules::pep8_naming::rules::LowercaseImportedAsNonLowercase),
(PEP8Naming, "813") => (RuleGroup::Unspecified, rules::pep8_naming::rules::CamelcaseImportedAsLowercase),
(PEP8Naming, "814") => (RuleGroup::Unspecified, rules::pep8_naming::rules::CamelcaseImportedAsConstant),
(PEP8Naming, "815") => (RuleGroup::Unspecified, rules::pep8_naming::rules::MixedCaseVariableInClassScope),
(PEP8Naming, "816") => (RuleGroup::Unspecified, rules::pep8_naming::rules::MixedCaseVariableInGlobalScope),
(PEP8Naming, "817") => (RuleGroup::Unspecified, rules::pep8_naming::rules::CamelcaseImportedAsAcronym),
(PEP8Naming, "818") => (RuleGroup::Unspecified, rules::pep8_naming::rules::ErrorSuffixOnExceptionName),
(PEP8Naming, "999") => (RuleGroup::Unspecified, rules::pep8_naming::rules::InvalidModuleName),
// isort
(Isort, "001") => (RuleGroup::Unspecified, rules::isort::rules::UnsortedImports),
(Isort, "002") => (RuleGroup::Unspecified, rules::isort::rules::MissingRequiredImport),
// eradicate
(Eradicate, "001") => (RuleGroup::Unspecified, rules::eradicate::rules::CommentedOutCode),
// flake8-bandit
(Flake8Bandit, "101") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::Assert),
(Flake8Bandit, "102") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::ExecBuiltin),
(Flake8Bandit, "103") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::BadFilePermissions),
(Flake8Bandit, "104") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::HardcodedBindAllInterfaces),
(Flake8Bandit, "105") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::HardcodedPasswordString),
(Flake8Bandit, "106") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::HardcodedPasswordFuncArg),
(Flake8Bandit, "107") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::HardcodedPasswordDefault),
(Flake8Bandit, "108") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::HardcodedTempFile),
(Flake8Bandit, "110") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::TryExceptPass),
(Flake8Bandit, "112") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::TryExceptContinue),
(Flake8Bandit, "113") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::RequestWithoutTimeout),
(Flake8Bandit, "301") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousPickleUsage),
(Flake8Bandit, "302") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousMarshalUsage),
(Flake8Bandit, "303") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousInsecureHashUsage),
(Flake8Bandit, "304") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousInsecureCipherUsage),
(Flake8Bandit, "305") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousInsecureCipherModeUsage),
(Flake8Bandit, "306") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousMktempUsage),
(Flake8Bandit, "307") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousEvalUsage),
(Flake8Bandit, "308") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousMarkSafeUsage),
(Flake8Bandit, "310") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousURLOpenUsage),
(Flake8Bandit, "311") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousNonCryptographicRandomUsage),
(Flake8Bandit, "312") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousTelnetUsage),
(Flake8Bandit, "313") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousXMLCElementTreeUsage),
(Flake8Bandit, "314") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousXMLElementTreeUsage),
(Flake8Bandit, "315") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousXMLExpatReaderUsage),
(Flake8Bandit, "316") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousXMLExpatBuilderUsage),
(Flake8Bandit, "317") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousXMLSaxUsage),
(Flake8Bandit, "318") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousXMLMiniDOMUsage),
(Flake8Bandit, "319") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousXMLPullDOMUsage),
(Flake8Bandit, "320") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousXMLETreeUsage),
(Flake8Bandit, "321") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousFTPLibUsage),
(Flake8Bandit, "323") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousUnverifiedContextUsage),
(Flake8Bandit, "324") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::HashlibInsecureHashFunction),
(Flake8Bandit, "501") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::RequestWithNoCertValidation),
(Flake8Bandit, "506") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::UnsafeYAMLLoad),
(Flake8Bandit, "508") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SnmpInsecureVersion),
(Flake8Bandit, "509") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SnmpWeakCryptography),
(Flake8Bandit, "601") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::ParamikoCall),
(Flake8Bandit, "602") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SubprocessPopenWithShellEqualsTrue),
(Flake8Bandit, "603") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SubprocessWithoutShellEqualsTrue),
(Flake8Bandit, "604") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::CallWithShellEqualsTrue),
(Flake8Bandit, "605") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::StartProcessWithAShell),
(Flake8Bandit, "606") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::StartProcessWithNoShell),
(Flake8Bandit, "607") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::StartProcessWithPartialPath),
(Flake8Bandit, "608") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::HardcodedSQLExpression),
(Flake8Bandit, "609") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::UnixCommandWildcardInjection),
(Flake8Bandit, "612") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::LoggingConfigInsecureListen),
(Flake8Bandit, "701") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::Jinja2AutoescapeFalse),
// flake8-boolean-trap
(Flake8BooleanTrap, "001") => (RuleGroup::Unspecified, rules::flake8_boolean_trap::rules::BooleanTypeHintPositionalArgument),
(Flake8BooleanTrap, "002") => (RuleGroup::Unspecified, rules::flake8_boolean_trap::rules::BooleanDefaultValuePositionalArgument),
(Flake8BooleanTrap, "003") => (RuleGroup::Unspecified, rules::flake8_boolean_trap::rules::BooleanPositionalValueInCall),
// flake8-unused-arguments
(Flake8UnusedArguments, "001") => (RuleGroup::Unspecified, rules::flake8_unused_arguments::rules::UnusedFunctionArgument),
(Flake8UnusedArguments, "002") => (RuleGroup::Unspecified, rules::flake8_unused_arguments::rules::UnusedMethodArgument),
(Flake8UnusedArguments, "003") => (RuleGroup::Unspecified, rules::flake8_unused_arguments::rules::UnusedClassMethodArgument),
(Flake8UnusedArguments, "004") => (RuleGroup::Unspecified, rules::flake8_unused_arguments::rules::UnusedStaticMethodArgument),
(Flake8UnusedArguments, "005") => (RuleGroup::Unspecified, rules::flake8_unused_arguments::rules::UnusedLambdaArgument),
// flake8-import-conventions
(Flake8ImportConventions, "001") => (RuleGroup::Unspecified, rules::flake8_import_conventions::rules::UnconventionalImportAlias),
(Flake8ImportConventions, "002") => (RuleGroup::Unspecified, rules::flake8_import_conventions::rules::BannedImportAlias),
(Flake8ImportConventions, "003") => (RuleGroup::Unspecified, rules::flake8_import_conventions::rules::BannedImportFrom),
// flake8-datetimez
(Flake8Datetimez, "001") => (RuleGroup::Unspecified, rules::flake8_datetimez::rules::CallDatetimeWithoutTzinfo),
(Flake8Datetimez, "002") => (RuleGroup::Unspecified, rules::flake8_datetimez::rules::CallDatetimeToday),
(Flake8Datetimez, "003") => (RuleGroup::Unspecified, rules::flake8_datetimez::rules::CallDatetimeUtcnow),
(Flake8Datetimez, "004") => (RuleGroup::Unspecified, rules::flake8_datetimez::rules::CallDatetimeUtcfromtimestamp),
(Flake8Datetimez, "005") => (RuleGroup::Unspecified, rules::flake8_datetimez::rules::CallDatetimeNowWithoutTzinfo),
(Flake8Datetimez, "006") => (RuleGroup::Unspecified, rules::flake8_datetimez::rules::CallDatetimeFromtimestamp),
(Flake8Datetimez, "007") => (RuleGroup::Unspecified, rules::flake8_datetimez::rules::CallDatetimeStrptimeWithoutZone),
(Flake8Datetimez, "011") => (RuleGroup::Unspecified, rules::flake8_datetimez::rules::CallDateToday),
(Flake8Datetimez, "012") => (RuleGroup::Unspecified, rules::flake8_datetimez::rules::CallDateFromtimestamp),
// pygrep-hooks
(PygrepHooks, "001") => (RuleGroup::Unspecified, rules::pygrep_hooks::rules::Eval),
(PygrepHooks, "002") => (RuleGroup::Unspecified, rules::pygrep_hooks::rules::DeprecatedLogWarn),
(PygrepHooks, "003") => (RuleGroup::Unspecified, rules::pygrep_hooks::rules::BlanketTypeIgnore),
(PygrepHooks, "004") => (RuleGroup::Unspecified, rules::pygrep_hooks::rules::BlanketNOQA),
(PygrepHooks, "005") => (RuleGroup::Unspecified, rules::pygrep_hooks::rules::InvalidMockAccess),
// pandas-vet
(PandasVet, "002") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfInplaceArgument),
(PandasVet, "003") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfDotIsNull),
(PandasVet, "004") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfDotNotNull),
(PandasVet, "007") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfDotIx),
(PandasVet, "008") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfDotAt),
(PandasVet, "009") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfDotIat),
(PandasVet, "010") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfDotPivotOrUnstack),
(PandasVet, "011") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfDotValues),
(PandasVet, "012") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfDotReadTable),
(PandasVet, "013") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfDotStack),
(PandasVet, "015") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfPdMerge),
(PandasVet, "101") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasNuniqueConstantSeriesCheck),
(PandasVet, "901") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasDfVariableName),
// flake8-errmsg
(Flake8ErrMsg, "101") => (RuleGroup::Unspecified, rules::flake8_errmsg::rules::RawStringInException),
(Flake8ErrMsg, "102") => (RuleGroup::Unspecified, rules::flake8_errmsg::rules::FStringInException),
(Flake8ErrMsg, "103") => (RuleGroup::Unspecified, rules::flake8_errmsg::rules::DotFormatInException),
// flake8-pyi
(Flake8Pyi, "001") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnprefixedTypeParam),
(Flake8Pyi, "002") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::ComplexIfStatementInStub),
(Flake8Pyi, "003") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnrecognizedVersionInfoCheck),
(Flake8Pyi, "004") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::PatchVersionComparison),
(Flake8Pyi, "005") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::WrongTupleLengthVersionComparison),
(Flake8Pyi, "006") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::BadVersionInfoComparison),
(Flake8Pyi, "007") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnrecognizedPlatformCheck),
(Flake8Pyi, "008") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnrecognizedPlatformName),
(Flake8Pyi, "009") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::PassStatementStubBody),
(Flake8Pyi, "010") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NonEmptyStubBody),
(Flake8Pyi, "011") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::TypedArgumentDefaultInStub),
(Flake8Pyi, "012") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::PassInClassBody),
(Flake8Pyi, "013") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::EllipsisInNonEmptyClassBody),
(Flake8Pyi, "014") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::ArgumentDefaultInStub),
(Flake8Pyi, "015") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::AssignmentDefaultInStub),
(Flake8Pyi, "016") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::DuplicateUnionMember),
(Flake8Pyi, "017") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::ComplexAssignmentInStub),
(Flake8Pyi, "018") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnusedPrivateTypeVar),
(Flake8Pyi, "019") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::CustomTypeVarReturnType),
(Flake8Pyi, "020") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::QuotedAnnotationInStub),
(Flake8Pyi, "021") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::DocstringInStub),
(Flake8Pyi, "024") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::CollectionsNamedTuple),
(Flake8Pyi, "025") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnaliasedCollectionsAbcSetImport),
(Flake8Pyi, "026") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::TypeAliasWithoutAnnotation),
(Flake8Pyi, "029") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::StrOrReprDefinedInStub),
(Flake8Pyi, "030") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnnecessaryLiteralUnion),
(Flake8Pyi, "032") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::AnyEqNeAnnotation),
(Flake8Pyi, "033") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::TypeCommentInStub),
(Flake8Pyi, "034") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NonSelfReturnType),
(Flake8Pyi, "035") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnassignedSpecialVariableInStub),
(Flake8Pyi, "036") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::BadExitAnnotation),
(Flake8Pyi, "041") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::RedundantNumericUnion),
(Flake8Pyi, "042") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::SnakeCaseTypeAlias),
(Flake8Pyi, "043") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::TSuffixedTypeAlias),
(Flake8Pyi, "044") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::FutureAnnotationsInStub),
(Flake8Pyi, "045") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::IterMethodReturnIterable),
(Flake8Pyi, "046") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnusedPrivateProtocol),
(Flake8Pyi, "047") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnusedPrivateTypeAlias),
(Flake8Pyi, "048") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::StubBodyMultipleStatements),
(Flake8Pyi, "049") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnusedPrivateTypedDict),
(Flake8Pyi, "050") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NoReturnArgumentAnnotationInStub),
(Flake8Pyi, "051") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::RedundantLiteralUnion),
(Flake8Pyi, "052") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnannotatedAssignmentInStub),
(Flake8Pyi, "054") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NumericLiteralTooLong),
(Flake8Pyi, "053") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::StringOrBytesTooLong),
(Flake8Pyi, "055") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnnecessaryTypeUnion),
(Flake8Pyi, "056") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnsupportedMethodCallOnAll),
// flake8-pytest-style
(Flake8PytestStyle, "001") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestFixtureIncorrectParenthesesStyle),
(Flake8PytestStyle, "002") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestFixturePositionalArgs),
(Flake8PytestStyle, "003") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestExtraneousScopeFunction),
(Flake8PytestStyle, "004") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestMissingFixtureNameUnderscore),
(Flake8PytestStyle, "005") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestIncorrectFixtureNameUnderscore),
(Flake8PytestStyle, "006") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestParametrizeNamesWrongType),
(Flake8PytestStyle, "007") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestParametrizeValuesWrongType),
(Flake8PytestStyle, "008") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestPatchWithLambda),
(Flake8PytestStyle, "009") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestUnittestAssertion),
(Flake8PytestStyle, "010") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestRaisesWithoutException),
(Flake8PytestStyle, "011") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestRaisesTooBroad),
(Flake8PytestStyle, "012") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestRaisesWithMultipleStatements),
(Flake8PytestStyle, "013") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestIncorrectPytestImport),
(Flake8PytestStyle, "014") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestDuplicateParametrizeTestCases),
(Flake8PytestStyle, "015") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestAssertAlwaysFalse),
(Flake8PytestStyle, "016") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestFailWithoutMessage),
(Flake8PytestStyle, "017") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestAssertInExcept),
(Flake8PytestStyle, "018") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestCompositeAssertion),
(Flake8PytestStyle, "019") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestFixtureParamWithoutValue),
(Flake8PytestStyle, "020") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestDeprecatedYieldFixture),
(Flake8PytestStyle, "021") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestFixtureFinalizerCallback),
(Flake8PytestStyle, "022") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestUselessYieldFixture),
(Flake8PytestStyle, "023") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestIncorrectMarkParenthesesStyle),
(Flake8PytestStyle, "024") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestUnnecessaryAsyncioMarkOnFixture),
(Flake8PytestStyle, "025") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestErroneousUseFixturesOnFixture),
(Flake8PytestStyle, "026") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestUseFixturesWithoutParameters),
(Flake8PytestStyle, "027") => (RuleGroup::Unspecified, rules::flake8_pytest_style::rules::PytestUnittestRaisesAssertion),
// flake8-pie
(Flake8Pie, "790") => (RuleGroup::Unspecified, rules::flake8_pie::rules::UnnecessaryPass),
(Flake8Pie, "794") => (RuleGroup::Unspecified, rules::flake8_pie::rules::DuplicateClassFieldDefinition),
(Flake8Pie, "796") => (RuleGroup::Unspecified, rules::flake8_pie::rules::NonUniqueEnums),
(Flake8Pie, "800") => (RuleGroup::Unspecified, rules::flake8_pie::rules::UnnecessarySpread),
(Flake8Pie, "804") => (RuleGroup::Unspecified, rules::flake8_pie::rules::UnnecessaryDictKwargs),
(Flake8Pie, "807") => (RuleGroup::Unspecified, rules::flake8_pie::rules::ReimplementedListBuiltin),
(Flake8Pie, "808") => (RuleGroup::Unspecified, rules::flake8_pie::rules::UnnecessaryRangeStart),
(Flake8Pie, "810") => (RuleGroup::Unspecified, rules::flake8_pie::rules::MultipleStartsEndsWith),
// flake8-commas
(Flake8Commas, "812") => (RuleGroup::Unspecified, rules::flake8_commas::rules::MissingTrailingComma),
(Flake8Commas, "818") => (RuleGroup::Unspecified, rules::flake8_commas::rules::TrailingCommaOnBareTuple),
(Flake8Commas, "819") => (RuleGroup::Unspecified, rules::flake8_commas::rules::ProhibitedTrailingComma),
// flake8-no-pep420
(Flake8NoPep420, "001") => (RuleGroup::Unspecified, rules::flake8_no_pep420::rules::ImplicitNamespacePackage),
// flake8-executable
(Flake8Executable, "001") => (RuleGroup::Unspecified, rules::flake8_executable::rules::ShebangNotExecutable),
(Flake8Executable, "002") => (RuleGroup::Unspecified, rules::flake8_executable::rules::ShebangMissingExecutableFile),
(Flake8Executable, "003") => (RuleGroup::Unspecified, rules::flake8_executable::rules::ShebangMissingPython),
(Flake8Executable, "004") => (RuleGroup::Unspecified, rules::flake8_executable::rules::ShebangLeadingWhitespace),
(Flake8Executable, "005") => (RuleGroup::Unspecified, rules::flake8_executable::rules::ShebangNotFirstLine),
// flake8-type-checking
(Flake8TypeChecking, "001") => (RuleGroup::Unspecified, rules::flake8_type_checking::rules::TypingOnlyFirstPartyImport),
(Flake8TypeChecking, "002") => (RuleGroup::Unspecified, rules::flake8_type_checking::rules::TypingOnlyThirdPartyImport),
(Flake8TypeChecking, "003") => (RuleGroup::Unspecified, rules::flake8_type_checking::rules::TypingOnlyStandardLibraryImport),
(Flake8TypeChecking, "004") => (RuleGroup::Unspecified, rules::flake8_type_checking::rules::RuntimeImportInTypeCheckingBlock),
(Flake8TypeChecking, "005") => (RuleGroup::Unspecified, rules::flake8_type_checking::rules::EmptyTypeCheckingBlock),
// tryceratops
(Tryceratops, "002") => (RuleGroup::Unspecified, rules::tryceratops::rules::RaiseVanillaClass),
(Tryceratops, "003") => (RuleGroup::Unspecified, rules::tryceratops::rules::RaiseVanillaArgs),
(Tryceratops, "004") => (RuleGroup::Unspecified, rules::tryceratops::rules::TypeCheckWithoutTypeError),
(Tryceratops, "200") => (RuleGroup::Unspecified, rules::tryceratops::rules::ReraiseNoCause),
(Tryceratops, "201") => (RuleGroup::Unspecified, rules::tryceratops::rules::VerboseRaise),
(Tryceratops, "300") => (RuleGroup::Unspecified, rules::tryceratops::rules::TryConsiderElse),
(Tryceratops, "301") => (RuleGroup::Unspecified, rules::tryceratops::rules::RaiseWithinTry),
(Tryceratops, "302") => (RuleGroup::Unspecified, rules::tryceratops::rules::UselessTryExcept),
(Tryceratops, "400") => (RuleGroup::Unspecified, rules::tryceratops::rules::ErrorInsteadOfException),
(Tryceratops, "401") => (RuleGroup::Unspecified, rules::tryceratops::rules::VerboseLogMessage),
// flake8-use-pathlib
(Flake8UsePathlib, "100") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsPathAbspath),
(Flake8UsePathlib, "101") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsChmod),
(Flake8UsePathlib, "102") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsMkdir),
(Flake8UsePathlib, "103") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsMakedirs),
(Flake8UsePathlib, "104") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsRename),
(Flake8UsePathlib, "105") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsReplace),
(Flake8UsePathlib, "106") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsRmdir),
(Flake8UsePathlib, "107") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsRemove),
(Flake8UsePathlib, "108") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsUnlink),
(Flake8UsePathlib, "109") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsGetcwd),
(Flake8UsePathlib, "110") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsPathExists),
(Flake8UsePathlib, "111") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsPathExpanduser),
(Flake8UsePathlib, "112") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsPathIsdir),
(Flake8UsePathlib, "113") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsPathIsfile),
(Flake8UsePathlib, "114") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsPathIslink),
(Flake8UsePathlib, "115") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsReadlink),
(Flake8UsePathlib, "116") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsStat),
(Flake8UsePathlib, "117") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsPathIsabs),
(Flake8UsePathlib, "118") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsPathJoin),
(Flake8UsePathlib, "119") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsPathBasename),
(Flake8UsePathlib, "120") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsPathDirname),
(Flake8UsePathlib, "121") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsPathSamefile),
(Flake8UsePathlib, "122") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::OsPathSplitext),
(Flake8UsePathlib, "123") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::BuiltinOpen),
(Flake8UsePathlib, "124") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::violations::PyPath),
(Flake8UsePathlib, "201") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::PathConstructorCurrentDirectory),
(Flake8UsePathlib, "202") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::OsPathGetsize),
(Flake8UsePathlib, "202") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::OsPathGetsize),
(Flake8UsePathlib, "203") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::OsPathGetatime),
(Flake8UsePathlib, "204") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::OsPathGetmtime),
(Flake8UsePathlib, "205") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::OsPathGetctime),
(Flake8UsePathlib, "206") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::OsSepSplit),
(Flake8UsePathlib, "207") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::Glob),
// flake8-logging-format
(Flake8LoggingFormat, "001") => (RuleGroup::Unspecified, rules::flake8_logging_format::violations::LoggingStringFormat),
(Flake8LoggingFormat, "002") => (RuleGroup::Unspecified, rules::flake8_logging_format::violations::LoggingPercentFormat),
(Flake8LoggingFormat, "003") => (RuleGroup::Unspecified, rules::flake8_logging_format::violations::LoggingStringConcat),
(Flake8LoggingFormat, "004") => (RuleGroup::Unspecified, rules::flake8_logging_format::violations::LoggingFString),
(Flake8LoggingFormat, "010") => (RuleGroup::Unspecified, rules::flake8_logging_format::violations::LoggingWarn),
(Flake8LoggingFormat, "101") => (RuleGroup::Unspecified, rules::flake8_logging_format::violations::LoggingExtraAttrClash),
(Flake8LoggingFormat, "201") => (RuleGroup::Unspecified, rules::flake8_logging_format::violations::LoggingExcInfo),
(Flake8LoggingFormat, "202") => (RuleGroup::Unspecified, rules::flake8_logging_format::violations::LoggingRedundantExcInfo),
// flake8-raise
(Flake8Raise, "102") => (RuleGroup::Unspecified, rules::flake8_raise::rules::UnnecessaryParenOnRaiseException),
// flake8-self
(Flake8Self, "001") => (RuleGroup::Unspecified, rules::flake8_self::rules::PrivateMemberAccess),
// numpy
(Numpy, "001") => (RuleGroup::Unspecified, rules::numpy::rules::NumpyDeprecatedTypeAlias),
(Numpy, "002") => (RuleGroup::Unspecified, rules::numpy::rules::NumpyLegacyRandom),
(Numpy, "003") => (RuleGroup::Unspecified, rules::numpy::rules::NumpyDeprecatedFunction),
// ruff
(Ruff, "001") => (RuleGroup::Unspecified, rules::ruff::rules::AmbiguousUnicodeCharacterString),
(Ruff, "002") => (RuleGroup::Unspecified, rules::ruff::rules::AmbiguousUnicodeCharacterDocstring),
(Ruff, "003") => (RuleGroup::Unspecified, rules::ruff::rules::AmbiguousUnicodeCharacterComment),
(Ruff, "005") => (RuleGroup::Unspecified, rules::ruff::rules::CollectionLiteralConcatenation),
(Ruff, "006") => (RuleGroup::Unspecified, rules::ruff::rules::AsyncioDanglingTask),
(Ruff, "007") => (RuleGroup::Unspecified, rules::ruff::rules::PairwiseOverZipped),
(Ruff, "008") => (RuleGroup::Unspecified, rules::ruff::rules::MutableDataclassDefault),
(Ruff, "009") => (RuleGroup::Unspecified, rules::ruff::rules::FunctionCallInDataclassDefaultArgument),
(Ruff, "010") => (RuleGroup::Unspecified, rules::ruff::rules::ExplicitFStringTypeConversion),
(Ruff, "011") => (RuleGroup::Unspecified, rules::ruff::rules::StaticKeyDictComprehension),
(Ruff, "012") => (RuleGroup::Unspecified, rules::ruff::rules::MutableClassDefault),
(Ruff, "013") => (RuleGroup::Unspecified, rules::ruff::rules::ImplicitOptional),
#[cfg(feature = "unreachable-code")] // When removing this feature gate, also update rules_selector.rs
#[allow(deprecated)]
(Ruff, "014") => (RuleGroup::Nursery, rules::ruff::rules::UnreachableCode),
(Ruff, "015") => (RuleGroup::Unspecified, rules::ruff::rules::UnnecessaryIterableAllocationForFirstElement),
(Ruff, "016") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidIndexType),
#[allow(deprecated)]
(Ruff, "017") => (RuleGroup::Nursery, rules::ruff::rules::QuadraticListSummation),
(Ruff, "100") => (RuleGroup::Unspecified, rules::ruff::rules::UnusedNOQA),
(Ruff, "200") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidPyprojectToml),
// flake8-django
(Flake8Django, "001") => (RuleGroup::Unspecified, rules::flake8_django::rules::DjangoNullableModelStringField),
(Flake8Django, "003") => (RuleGroup::Unspecified, rules::flake8_django::rules::DjangoLocalsInRenderFunction),
(Flake8Django, "006") => (RuleGroup::Unspecified, rules::flake8_django::rules::DjangoExcludeWithModelForm),
(Flake8Django, "007") => (RuleGroup::Unspecified, rules::flake8_django::rules::DjangoAllWithModelForm),
(Flake8Django, "008") => (RuleGroup::Unspecified, rules::flake8_django::rules::DjangoModelWithoutDunderStr),
(Flake8Django, "012") => (RuleGroup::Unspecified, rules::flake8_django::rules::DjangoUnorderedBodyContentInModel),
(Flake8Django, "013") => (RuleGroup::Unspecified, rules::flake8_django::rules::DjangoNonLeadingReceiverDecorator),
// flynt
// Reserved: (Flynt, "001") => (RuleGroup::Unspecified, Rule: :StringConcatenationToFString),
(Flynt, "002") => (RuleGroup::Unspecified, rules::flynt::rules::StaticJoinToFString),
// flake8-todos
(Flake8Todos, "001") => (RuleGroup::Unspecified, rules::flake8_todos::rules::InvalidTodoTag),
(Flake8Todos, "002") => (RuleGroup::Unspecified, rules::flake8_todos::rules::MissingTodoAuthor),
(Flake8Todos, "003") => (RuleGroup::Unspecified, rules::flake8_todos::rules::MissingTodoLink),
(Flake8Todos, "004") => (RuleGroup::Unspecified, rules::flake8_todos::rules::MissingTodoColon),
(Flake8Todos, "005") => (RuleGroup::Unspecified, rules::flake8_todos::rules::MissingTodoDescription),
(Flake8Todos, "006") => (RuleGroup::Unspecified, rules::flake8_todos::rules::InvalidTodoCapitalization),
(Flake8Todos, "007") => (RuleGroup::Unspecified, rules::flake8_todos::rules::MissingSpaceAfterTodoColon),
// airflow
(Airflow, "001") => (RuleGroup::Unspecified, rules::airflow::rules::AirflowVariableNameTaskIdMismatch),
// perflint
(Perflint, "101") => (RuleGroup::Unspecified, rules::perflint::rules::UnnecessaryListCast),
(Perflint, "102") => (RuleGroup::Unspecified, rules::perflint::rules::IncorrectDictIterator),
(Perflint, "203") => (RuleGroup::Unspecified, rules::perflint::rules::TryExceptInLoop),
(Perflint, "401") => (RuleGroup::Unspecified, rules::perflint::rules::ManualListComprehension),
(Perflint, "402") => (RuleGroup::Unspecified, rules::perflint::rules::ManualListCopy),
// flake8-fixme
(Flake8Fixme, "001") => (RuleGroup::Unspecified, rules::flake8_fixme::rules::LineContainsFixme),
(Flake8Fixme, "002") => (RuleGroup::Unspecified, rules::flake8_fixme::rules::LineContainsTodo),
(Flake8Fixme, "003") => (RuleGroup::Unspecified, rules::flake8_fixme::rules::LineContainsXxx),
(Flake8Fixme, "004") => (RuleGroup::Unspecified, rules::flake8_fixme::rules::LineContainsHack),
// flake8-slots
(Flake8Slots, "000") => (RuleGroup::Unspecified, rules::flake8_slots::rules::NoSlotsInStrSubclass),
(Flake8Slots, "001") => (RuleGroup::Unspecified, rules::flake8_slots::rules::NoSlotsInTupleSubclass),
(Flake8Slots, "002") => (RuleGroup::Unspecified, rules::flake8_slots::rules::NoSlotsInNamedtupleSubclass),
// refurb
#[allow(deprecated)]
(Refurb, "113") => (RuleGroup::Nursery, rules::refurb::rules::RepeatedAppend),
#[allow(deprecated)]
(Refurb, "131") => (RuleGroup::Nursery, rules::refurb::rules::DeleteFullSlice),
#[allow(deprecated)]
(Refurb, "132") => (RuleGroup::Nursery, rules::refurb::rules::CheckAndRemoveFromSet),
_ => return None,
})
}

View File

@@ -1,5 +0,0 @@
---
source: crates/ruff/src/comments/shebang.rs
expression: "ShebangDirective::try_extract(source)"
---
None

View File

@@ -1,5 +0,0 @@
---
source: crates/ruff/src/comments/shebang.rs
expression: "ShebangDirective::try_extract(source)"
---
None

View File

@@ -1,9 +0,0 @@
---
source: crates/ruff/src/comments/shebang.rs
expression: "ShebangDirective::try_extract(source)"
---
Some(
ShebangDirective(
"/usr/bin/env python",
),
)

View File

@@ -1,9 +0,0 @@
---
source: crates/ruff/src/comments/shebang.rs
expression: "ShebangDirective::try_extract(source)"
---
Some(
ShebangDirective(
"/usr/bin/env python # trailing comment",
),
)

View File

@@ -1,5 +0,0 @@
---
source: crates/ruff/src/comments/shebang.rs
expression: "ShebangDirective::try_extract(source)"
---
None

View File

@@ -1,52 +0,0 @@
use libcst_native::{Expression, NameOrAttribute, ParenthesizableWhitespace, SimpleWhitespace};
fn compose_call_path_inner<'a>(expr: &'a Expression, parts: &mut Vec<&'a str>) {
match expr {
Expression::Call(expr) => {
compose_call_path_inner(&expr.func, parts);
}
Expression::Attribute(expr) => {
compose_call_path_inner(&expr.value, parts);
parts.push(expr.attr.value);
}
Expression::Name(expr) => {
parts.push(expr.value);
}
_ => {}
}
}
pub(crate) fn compose_call_path(expr: &Expression) -> Option<String> {
let mut segments = vec![];
compose_call_path_inner(expr, &mut segments);
if segments.is_empty() {
None
} else {
Some(segments.join("."))
}
}
pub(crate) fn compose_module_path(module: &NameOrAttribute) -> String {
match module {
NameOrAttribute::N(name) => name.value.to_string(),
NameOrAttribute::A(attr) => {
let name = attr.attr.value;
let prefix = compose_call_path(&attr.value);
prefix.map_or_else(|| name.to_string(), |prefix| format!("{prefix}.{name}"))
}
}
}
/// Return a [`ParenthesizableWhitespace`] containing a single space.
pub(crate) fn space() -> ParenthesizableWhitespace<'static> {
ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" "))
}
/// Ensure that a [`ParenthesizableWhitespace`] contains at least one space.
pub(crate) fn or_space(whitespace: ParenthesizableWhitespace) -> ParenthesizableWhitespace {
if whitespace == ParenthesizableWhitespace::default() {
space()
} else {
whitespace
}
}

View File

@@ -1,73 +0,0 @@
use std::fmt::{Debug, Formatter};
use std::ops::Deref;
use ruff_python_ast::Expr;
use ruff_python_semantic::Definition;
use ruff_text_size::{Ranged, TextRange};
pub(crate) mod extraction;
pub(crate) mod google;
pub(crate) mod numpy;
pub(crate) mod sections;
pub(crate) mod styles;
#[derive(Debug)]
pub(crate) struct Docstring<'a> {
pub(crate) definition: &'a Definition<'a>,
pub(crate) expr: &'a Expr,
/// The content of the docstring, including the leading and trailing quotes.
pub(crate) contents: &'a str,
/// The range of the docstring body (without the quotes). The range is relative to [`Self::contents`].
pub(crate) body_range: TextRange,
pub(crate) indentation: &'a str,
}
impl<'a> Docstring<'a> {
pub(crate) fn body(&self) -> DocstringBody {
DocstringBody { docstring: self }
}
pub(crate) fn leading_quote(&self) -> &'a str {
&self.contents[TextRange::up_to(self.body_range.start())]
}
}
impl Ranged for Docstring<'_> {
fn range(&self) -> TextRange {
self.expr.range()
}
}
#[derive(Copy, Clone)]
pub(crate) struct DocstringBody<'a> {
docstring: &'a Docstring<'a>,
}
impl<'a> DocstringBody<'a> {
pub(crate) fn as_str(self) -> &'a str {
&self.docstring.contents[self.docstring.body_range]
}
}
impl Ranged for DocstringBody<'_> {
fn range(&self) -> TextRange {
self.docstring.body_range + self.docstring.start()
}
}
impl Deref for DocstringBody<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl Debug for DocstringBody<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DocstringBody")
.field("text", &self.as_str())
.field("range", &self.range())
.finish()
}
}

View File

@@ -1,500 +0,0 @@
//! Code modification struct to add and modify import statements.
//!
//! Enables rules to make module members available (that may be not yet be imported) during fix
//! execution.
use std::error::Error;
use anyhow::Result;
use libcst_native::{ImportAlias, Name, NameOrAttribute};
use ruff_python_ast::{self as ast, PySourceType, Stmt, Suite};
use ruff_text_size::{Ranged, TextSize};
use ruff_diagnostics::Edit;
use ruff_python_ast::imports::{AnyImport, Import, ImportFrom};
use ruff_python_codegen::Stylist;
use ruff_python_semantic::SemanticModel;
use ruff_python_trivia::textwrap::indent;
use ruff_source_file::Locator;
use crate::autofix;
use crate::autofix::codemods::CodegenStylist;
use crate::cst::matchers::{match_aliases, match_import_from, match_statement};
use crate::importer::insertion::Insertion;
mod insertion;
pub(crate) struct Importer<'a> {
/// The Python AST to which we are adding imports.
python_ast: &'a Suite,
/// The [`Locator`] for the Python AST.
locator: &'a Locator<'a>,
/// The [`Stylist`] for the Python AST.
stylist: &'a Stylist<'a>,
/// The list of visited, top-level runtime imports in the Python AST.
runtime_imports: Vec<&'a Stmt>,
/// The list of visited, top-level `if TYPE_CHECKING:` blocks in the Python AST.
type_checking_blocks: Vec<&'a Stmt>,
}
impl<'a> Importer<'a> {
pub(crate) fn new(
python_ast: &'a Suite,
locator: &'a Locator<'a>,
stylist: &'a Stylist<'a>,
) -> Self {
Self {
python_ast,
locator,
stylist,
runtime_imports: Vec::default(),
type_checking_blocks: Vec::default(),
}
}
/// Visit a top-level import statement.
pub(crate) fn visit_import(&mut self, import: &'a Stmt) {
self.runtime_imports.push(import);
}
/// Visit a top-level type-checking block.
pub(crate) fn visit_type_checking_block(&mut self, type_checking_block: &'a Stmt) {
self.type_checking_blocks.push(type_checking_block);
}
/// Add an import statement to import the given module.
///
/// If there are no existing imports, the new import will be added at the top
/// of the file. Otherwise, it will be added after the most recent top-level
/// import statement.
pub(crate) fn add_import(&self, import: &AnyImport, at: TextSize) -> Edit {
let required_import = import.to_string();
if let Some(stmt) = self.preceding_import(at) {
// Insert after the last top-level import.
Insertion::end_of_statement(stmt, self.locator, self.stylist)
.into_edit(&required_import)
} else {
// Insert at the start of the file.
Insertion::start_of_file(self.python_ast, self.locator, self.stylist)
.into_edit(&required_import)
}
}
/// Move an existing import to the top-level, thereby making it available at runtime.
///
/// If there are no existing imports, the new import will be added at the top
/// of the file. Otherwise, it will be added after the most recent top-level
/// import statement.
pub(crate) fn runtime_import_edit(
&self,
import: &ImportedMembers,
at: TextSize,
) -> Result<RuntimeImportEdit> {
// Generate the modified import statement.
let content = autofix::codemods::retain_imports(
&import.names,
import.statement,
self.locator,
self.stylist,
)?;
// Add the import to the top-level.
let insertion = if let Some(stmt) = self.preceding_import(at) {
// Insert after the last top-level import.
Insertion::end_of_statement(stmt, self.locator, self.stylist)
} else {
// Insert at the start of the file.
Insertion::start_of_file(self.python_ast, self.locator, self.stylist)
};
let add_import_edit = insertion.into_edit(&content);
Ok(RuntimeImportEdit { add_import_edit })
}
/// Move an existing import into a `TYPE_CHECKING` block.
///
/// If there are no existing `TYPE_CHECKING` blocks, a new one will be added at the top
/// of the file. Otherwise, it will be added after the most recent top-level
/// `TYPE_CHECKING` block.
pub(crate) fn typing_import_edit(
&self,
import: &ImportedMembers,
at: TextSize,
semantic: &SemanticModel,
source_type: PySourceType,
) -> Result<TypingImportEdit> {
// Generate the modified import statement.
let content = autofix::codemods::retain_imports(
&import.names,
import.statement,
self.locator,
self.stylist,
)?;
// Import the `TYPE_CHECKING` symbol from the typing module.
let (type_checking_edit, type_checking) = self.get_or_import_symbol(
&ImportRequest::import_from("typing", "TYPE_CHECKING"),
at,
semantic,
)?;
// Add the import to a `TYPE_CHECKING` block.
let add_import_edit = if let Some(block) = self.preceding_type_checking_block(at) {
// Add the import to the `TYPE_CHECKING` block.
self.add_to_type_checking_block(&content, block.start(), source_type)
} else {
// Add the import to a new `TYPE_CHECKING` block.
self.add_type_checking_block(
&format!(
"{}if {type_checking}:{}{}",
self.stylist.line_ending().as_str(),
self.stylist.line_ending().as_str(),
indent(&content, self.stylist.indentation())
),
at,
)?
};
Ok(TypingImportEdit {
type_checking_edit,
add_import_edit,
})
}
/// Generate an [`Edit`] to reference the given symbol. Returns the [`Edit`] necessary to make
/// the symbol available in the current scope along with the bound name of the symbol.
///
/// Attempts to reuse existing imports when possible.
pub(crate) fn get_or_import_symbol(
&self,
symbol: &ImportRequest,
at: TextSize,
semantic: &SemanticModel,
) -> Result<(Edit, String), ResolutionError> {
self.get_symbol(symbol, at, semantic)?
.map_or_else(|| self.import_symbol(symbol, at, semantic), Ok)
}
/// Return an [`Edit`] to reference an existing symbol, if it's present in the given [`SemanticModel`].
fn get_symbol(
&self,
symbol: &ImportRequest,
at: TextSize,
semantic: &SemanticModel,
) -> Result<Option<(Edit, String)>, ResolutionError> {
// If the symbol is already available in the current scope, use it.
let Some(imported_name) =
semantic.resolve_qualified_import_name(symbol.module, symbol.member)
else {
return Ok(None);
};
// If the symbol source (i.e., the import statement) comes after the current location,
// abort. For example, we could be generating an edit within a function, and the import
// could be defined in the module scope, but after the function definition. In this case,
// it's unclear whether we can use the symbol (the function could be called between the
// import and the current location, and thus the symbol would not be available). It's also
// unclear whether should add an import statement at the start of the file, since it could
// be shadowed between the import and the current location.
if imported_name.start() > at {
return Err(ResolutionError::ImportAfterUsage);
}
// If the symbol source (i.e., the import statement) is in a typing-only context, but we're
// in a runtime context, abort.
if imported_name.context().is_typing() && semantic.execution_context().is_runtime() {
return Err(ResolutionError::IncompatibleContext);
}
// We also add a no-op edit to force conflicts with any other fixes that might try to
// remove the import. Consider:
//
// ```py
// import sys
//
// quit()
// ```
//
// Assume you omit this no-op edit. If you run Ruff with `unused-imports` and
// `sys-exit-alias` over this snippet, it will generate two fixes: (1) remove the unused
// `sys` import; and (2) replace `quit()` with `sys.exit()`, under the assumption that `sys`
// is already imported and available.
//
// By adding this no-op edit, we force the `unused-imports` fix to conflict with the
// `sys-exit-alias` fix, and thus will avoid applying both fixes in the same pass.
let import_edit = Edit::range_replacement(
self.locator.slice(imported_name.range()).to_string(),
imported_name.range(),
);
Ok(Some((import_edit, imported_name.into_name())))
}
/// Generate an [`Edit`] to reference the given symbol. Returns the [`Edit`] necessary to make
/// the symbol available in the current scope along with the bound name of the symbol.
///
/// For example, assuming `module` is `"functools"` and `member` is `"lru_cache"`, this function
/// could return an [`Edit`] to add `import functools` to the start of the file, alongside with
/// the name on which the `lru_cache` symbol would be made available (`"functools.lru_cache"`).
fn import_symbol(
&self,
symbol: &ImportRequest,
at: TextSize,
semantic: &SemanticModel,
) -> Result<(Edit, String), ResolutionError> {
if let Some(stmt) = self.find_import_from(symbol.module, at) {
// Case 1: `from functools import lru_cache` is in scope, and we're trying to reference
// `functools.cache`; thus, we add `cache` to the import, and return `"cache"` as the
// bound name.
if semantic.is_available(symbol.member) {
let Ok(import_edit) = self.add_member(stmt, symbol.member) else {
return Err(ResolutionError::InvalidEdit);
};
Ok((import_edit, symbol.member.to_string()))
} else {
Err(ResolutionError::ConflictingName(symbol.member.to_string()))
}
} else {
match symbol.style {
ImportStyle::Import => {
// Case 2a: No `functools` import is in scope; thus, we add `import functools`,
// and return `"functools.cache"` as the bound name.
if semantic.is_available(symbol.module) {
let import_edit =
self.add_import(&AnyImport::Import(Import::module(symbol.module)), at);
Ok((
import_edit,
format!(
"{module}.{member}",
module = symbol.module,
member = symbol.member
),
))
} else {
Err(ResolutionError::ConflictingName(symbol.module.to_string()))
}
}
ImportStyle::ImportFrom => {
// Case 2b: No `functools` import is in scope; thus, we add
// `from functools import cache`, and return `"cache"` as the bound name.
if semantic.is_available(symbol.member) {
let import_edit = self.add_import(
&AnyImport::ImportFrom(ImportFrom::member(
symbol.module,
symbol.member,
)),
at,
);
Ok((import_edit, symbol.member.to_string()))
} else {
Err(ResolutionError::ConflictingName(symbol.member.to_string()))
}
}
}
}
}
/// Return the top-level [`Stmt`] that imports the given module using `Stmt::ImportFrom`
/// preceding the given position, if any.
fn find_import_from(&self, module: &str, at: TextSize) -> Option<&Stmt> {
let mut import_from = None;
for stmt in &self.runtime_imports {
if stmt.start() >= at {
break;
}
if let Stmt::ImportFrom(ast::StmtImportFrom {
module: name,
names,
level,
range: _,
}) = stmt
{
if level.map_or(true, |level| level.to_u32() == 0)
&& name.as_ref().is_some_and(|name| name == module)
&& names.iter().all(|alias| alias.name.as_str() != "*")
{
import_from = Some(*stmt);
}
}
}
import_from
}
/// Add the given member to an existing `Stmt::ImportFrom` statement.
fn add_member(&self, stmt: &Stmt, member: &str) -> Result<Edit> {
let mut statement = match_statement(self.locator.slice(stmt))?;
let import_from = match_import_from(&mut statement)?;
let aliases = match_aliases(import_from)?;
aliases.push(ImportAlias {
name: NameOrAttribute::N(Box::new(Name {
value: member,
lpar: vec![],
rpar: vec![],
})),
asname: None,
comma: aliases.last().and_then(|alias| alias.comma.clone()),
});
Ok(Edit::range_replacement(
statement.codegen_stylist(self.stylist),
stmt.range(),
))
}
/// Add a `TYPE_CHECKING` block to the given module.
fn add_type_checking_block(&self, content: &str, at: TextSize) -> Result<Edit> {
let insertion = if let Some(stmt) = self.preceding_import(at) {
// Insert after the last top-level import.
Insertion::end_of_statement(stmt, self.locator, self.stylist)
} else {
// Insert at the start of the file.
Insertion::start_of_file(self.python_ast, self.locator, self.stylist)
};
if insertion.is_inline() {
Err(anyhow::anyhow!(
"Cannot insert `TYPE_CHECKING` block inline"
))
} else {
Ok(insertion.into_edit(content))
}
}
/// Add an import statement to an existing `TYPE_CHECKING` block.
fn add_to_type_checking_block(
&self,
content: &str,
at: TextSize,
source_type: PySourceType,
) -> Edit {
Insertion::start_of_block(at, self.locator, self.stylist, source_type).into_edit(content)
}
/// Return the import statement that precedes the given position, if any.
fn preceding_import(&self, at: TextSize) -> Option<&'a Stmt> {
self.runtime_imports
.partition_point(|stmt| stmt.start() < at)
.checked_sub(1)
.map(|idx| self.runtime_imports[idx])
}
/// Return the `TYPE_CHECKING` block that precedes the given position, if any.
fn preceding_type_checking_block(&self, at: TextSize) -> Option<&'a Stmt> {
let block = self.type_checking_blocks.first()?;
if block.start() <= at {
Some(block)
} else {
None
}
}
}
/// An edit to the top-level of a module, making it available at runtime.
#[derive(Debug)]
pub(crate) struct RuntimeImportEdit {
/// The edit to add the import to the top-level of the module.
add_import_edit: Edit,
}
impl RuntimeImportEdit {
pub(crate) fn into_edits(self) -> Vec<Edit> {
vec![self.add_import_edit]
}
}
/// An edit to an import to a typing-only context.
#[derive(Debug)]
pub(crate) struct TypingImportEdit {
/// The edit to add the `TYPE_CHECKING` symbol to the module.
type_checking_edit: Edit,
/// The edit to add the import to a `TYPE_CHECKING` block.
add_import_edit: Edit,
}
impl TypingImportEdit {
pub(crate) fn into_edits(self) -> Vec<Edit> {
vec![self.type_checking_edit, self.add_import_edit]
}
}
#[derive(Debug)]
enum ImportStyle {
/// Import the symbol using the `import` statement (e.g. `import foo; foo.bar`).
Import,
/// Import the symbol using the `from` statement (e.g. `from foo import bar; bar`).
ImportFrom,
}
#[derive(Debug)]
pub(crate) struct ImportRequest<'a> {
/// The module from which the symbol can be imported (e.g., `foo`, in `from foo import bar`).
module: &'a str,
/// The member to import (e.g., `bar`, in `from foo import bar`).
member: &'a str,
/// The preferred style to use when importing the symbol (e.g., `import foo` or
/// `from foo import bar`), if it's not already in scope.
style: ImportStyle,
}
impl<'a> ImportRequest<'a> {
/// Create a new `ImportRequest` from a module and member. If not present in the scope,
/// the symbol should be imported using the "import" statement.
pub(crate) fn import(module: &'a str, member: &'a str) -> Self {
Self {
module,
member,
style: ImportStyle::Import,
}
}
/// Create a new `ImportRequest` from a module and member. If not present in the scope,
/// the symbol should be imported using the "import from" statement.
pub(crate) fn import_from(module: &'a str, member: &'a str) -> Self {
Self {
module,
member,
style: ImportStyle::ImportFrom,
}
}
}
/// An existing list of module or member imports, located within an import statement.
pub(crate) struct ImportedMembers<'a> {
/// The import statement.
pub(crate) statement: &'a Stmt,
/// The "names" of the imported members.
pub(crate) names: Vec<&'a str>,
}
/// The result of an [`Importer::get_or_import_symbol`] call.
#[derive(Debug)]
pub(crate) enum ResolutionError {
/// The symbol is imported, but the import came after the current location.
ImportAfterUsage,
/// The symbol is imported, but in an incompatible context (e.g., in typing-only context, while
/// we're in a runtime context).
IncompatibleContext,
/// The symbol can't be imported, because another symbol is bound to the same name.
ConflictingName(String),
/// The symbol can't be imported due to an error in editing an existing import statement.
InvalidEdit,
}
impl std::fmt::Display for ResolutionError {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResolutionError::ImportAfterUsage => {
fmt.write_str("Unable to use existing symbol due to late binding")
}
ResolutionError::IncompatibleContext => {
fmt.write_str("Unable to use existing symbol due to incompatible context")
}
ResolutionError::ConflictingName(binding) => std::write!(
fmt,
"Unable to insert `{binding}` into scope due to name conflict"
),
ResolutionError::InvalidEdit => {
fmt.write_str("Unable to modify existing import statement")
}
}
}
}
impl Error for ResolutionError {}

View File

@@ -1,293 +0,0 @@
use std::fmt::{Display, Formatter, Write};
use std::path::Path;
use std::sync::Mutex;
use anyhow::Result;
use colored::Colorize;
use fern;
use log::Level;
use once_cell::sync::Lazy;
use ruff_python_parser::{ParseError, ParseErrorType};
use ruff_source_file::{OneIndexed, SourceCode, SourceLocation};
use crate::fs;
use crate::source_kind::SourceKind;
use ruff_notebook::Notebook;
pub static WARNINGS: Lazy<Mutex<Vec<&'static str>>> = Lazy::new(Mutex::default);
/// Warn a user once, with uniqueness determined by the given ID.
#[macro_export]
macro_rules! warn_user_once_by_id {
($id:expr, $($arg:tt)*) => {
use colored::Colorize;
use log::warn;
if let Ok(mut states) = $crate::logging::WARNINGS.lock() {
if !states.contains(&$id) {
let message = format!("{}", format_args!($($arg)*));
warn!("{}", message.bold());
states.push($id);
}
}
};
}
/// Warn a user once, with uniqueness determined by the calling location itself.
#[macro_export]
macro_rules! warn_user_once {
($($arg:tt)*) => {
use colored::Colorize;
use log::warn;
static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
if !WARNED.swap(true, std::sync::atomic::Ordering::SeqCst) {
let message = format!("{}", format_args!($($arg)*));
warn!("{}", message.bold());
}
};
}
#[macro_export]
macro_rules! warn_user {
($($arg:tt)*) => {{
use colored::Colorize;
use log::warn;
let message = format!("{}", format_args!($($arg)*));
warn!("{}", message.bold());
}};
}
#[macro_export]
macro_rules! notify_user {
($($arg:tt)*) => {
println!(
"[{}] {}",
chrono::Local::now()
.format("%H:%M:%S %p")
.to_string()
.dimmed(),
format_args!($($arg)*)
)
}
}
#[derive(Debug, Default, PartialOrd, Ord, PartialEq, Eq, Copy, Clone)]
pub enum LogLevel {
/// No output ([`log::LevelFilter::Off`]).
Silent,
/// Only show lint violations, with no decorative output
/// ([`log::LevelFilter::Off`]).
Quiet,
/// All user-facing output ([`log::LevelFilter::Info`]).
#[default]
Default,
/// All user-facing output ([`log::LevelFilter::Debug`]).
Verbose,
}
impl LogLevel {
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn level_filter(&self) -> log::LevelFilter {
match self {
LogLevel::Default => log::LevelFilter::Info,
LogLevel::Verbose => log::LevelFilter::Debug,
LogLevel::Quiet => log::LevelFilter::Off,
LogLevel::Silent => log::LevelFilter::Off,
}
}
}
pub fn set_up_logging(level: &LogLevel) -> Result<()> {
fern::Dispatch::new()
.format(|out, message, record| match record.level() {
Level::Error => {
out.finish(format_args!(
"{}{} {}",
"error".red().bold(),
":".bold(),
message
));
}
Level::Warn => {
out.finish(format_args!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
message
));
}
Level::Info | Level::Debug | Level::Trace => {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
record.target(),
record.level(),
message
));
}
})
.level(level.level_filter())
.level_for("globset", log::LevelFilter::Warn)
.chain(std::io::stderr())
.apply()?;
Ok(())
}
pub struct DisplayParseError<'a> {
error: ParseError,
source_code: SourceCode<'a, 'a>,
source_kind: &'a SourceKind,
}
impl<'a> DisplayParseError<'a> {
pub fn new(
error: ParseError,
source_code: SourceCode<'a, 'a>,
source_kind: &'a SourceKind,
) -> Self {
Self {
error,
source_code,
source_kind,
}
}
}
impl Display for DisplayParseError<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{header} {path}{colon}",
header = "Failed to parse".bold(),
path = fs::relativize_path(Path::new(&self.error.source_path)).bold(),
colon = ":".cyan(),
)?;
let source_location = self.source_code.source_location(self.error.offset);
// If we're working on a Jupyter notebook, translate the positions
// with respect to the cell and row in the cell. This is the same
// format as the `TextEmitter`.
let error_location =
if let Some(jupyter_index) = self.source_kind.as_ipy_notebook().map(Notebook::index) {
write!(
f,
"cell {cell}{colon}",
cell = jupyter_index
.cell(source_location.row.get())
.unwrap_or_default(),
colon = ":".cyan(),
)?;
SourceLocation {
row: OneIndexed::new(
jupyter_index
.cell_row(source_location.row.get())
.unwrap_or(1) as usize,
)
.unwrap(),
column: source_location.column,
}
} else {
source_location
};
write!(
f,
"{row}{colon}{column}{colon} {inner}",
row = error_location.row,
column = error_location.column,
colon = ":".cyan(),
inner = &DisplayParseErrorType(&self.error.error)
)
}
}
pub(crate) struct DisplayParseErrorType<'a>(&'a ParseErrorType);
impl<'a> DisplayParseErrorType<'a> {
pub(crate) fn new(error: &'a ParseErrorType) -> Self {
Self(error)
}
}
impl Display for DisplayParseErrorType<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.0 {
ParseErrorType::Eof => write!(f, "Expected token but reached end of file."),
ParseErrorType::ExtraToken(ref tok) => write!(
f,
"Got extraneous token: {tok}",
tok = TruncateAtNewline(&tok)
),
ParseErrorType::InvalidToken => write!(f, "Got invalid token"),
ParseErrorType::UnrecognizedToken(ref tok, ref expected) => {
if let Some(expected) = expected.as_ref() {
write!(
f,
"Expected '{expected}', but got {tok}",
tok = TruncateAtNewline(&tok)
)
} else {
write!(f, "Unexpected token {tok}", tok = TruncateAtNewline(&tok))
}
}
ParseErrorType::Lexical(ref error) => write!(f, "{error}"),
}
}
}
/// Truncates the display text before the first newline character to avoid line breaks.
struct TruncateAtNewline<'a>(&'a dyn Display);
impl Display for TruncateAtNewline<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
struct TruncateAdapter<'a> {
inner: &'a mut dyn Write,
after_new_line: bool,
}
impl Write for TruncateAdapter<'_> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
if self.after_new_line {
Ok(())
} else {
if let Some(end) = s.find(['\n', '\r']) {
self.inner.write_str(&s[..end])?;
self.inner.write_str("\u{23ce}...")?;
self.after_new_line = true;
Ok(())
} else {
self.inner.write_str(s)
}
}
}
}
write!(
TruncateAdapter {
inner: f,
after_new_line: false,
},
"{}",
self.0
)
}
}
#[cfg(test)]
mod tests {
use crate::logging::LogLevel;
#[test]
fn ordering() {
assert!(LogLevel::Default > LogLevel::Silent);
assert!(LogLevel::Default >= LogLevel::Default);
assert!(LogLevel::Quiet > LogLevel::Silent);
assert!(LogLevel::Verbose > LogLevel::Default);
assert!(LogLevel::Verbose > LogLevel::Silent);
}
}

View File

@@ -1,117 +0,0 @@
use std::io::Write;
use serde::ser::SerializeSeq;
use serde::{Serialize, Serializer};
use serde_json::{json, Value};
use ruff_diagnostics::Edit;
use ruff_source_file::SourceCode;
use ruff_text_size::Ranged;
use crate::message::{Emitter, EmitterContext, Message};
use crate::registry::AsRule;
#[derive(Default)]
pub struct JsonEmitter;
impl Emitter for JsonEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
messages: &[Message],
_context: &EmitterContext,
) -> anyhow::Result<()> {
serde_json::to_writer_pretty(writer, &ExpandedMessages { messages })?;
Ok(())
}
}
struct ExpandedMessages<'a> {
messages: &'a [Message],
}
impl Serialize for ExpandedMessages<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_seq(Some(self.messages.len()))?;
for message in self.messages {
let value = message_to_json_value(message);
s.serialize_element(&value)?;
}
s.end()
}
}
pub(crate) fn message_to_json_value(message: &Message) -> Value {
let source_code = message.file.to_source_code();
let fix = message.fix.as_ref().map(|fix| {
json!({
"applicability": fix.applicability(),
"message": message.kind.suggestion.as_deref(),
"edits": &ExpandedEdits { edits: fix.edits(), source_code: &source_code },
})
});
let start_location = source_code.source_location(message.start());
let end_location = source_code.source_location(message.end());
let noqa_location = source_code.source_location(message.noqa_offset);
json!({
"code": message.kind.rule().noqa_code().to_string(),
"url": message.kind.rule().url(),
"message": message.kind.body,
"fix": fix,
"location": start_location,
"end_location": end_location,
"filename": message.filename(),
"noqa_row": noqa_location.row
})
}
struct ExpandedEdits<'a> {
edits: &'a [Edit],
source_code: &'a SourceCode<'a, 'a>,
}
impl Serialize for ExpandedEdits<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_seq(Some(self.edits.len()))?;
for edit in self.edits {
let value = json!({
"content": edit.content().unwrap_or_default(),
"location": self.source_code.source_location(edit.start()),
"end_location": self.source_code.source_location(edit.end())
});
s.serialize_element(&value)?;
}
s.end()
}
}
#[cfg(test)]
mod tests {
use insta::assert_snapshot;
use crate::message::tests::{capture_emitter_output, create_messages};
use crate::message::JsonEmitter;
#[test]
fn output() {
let mut emitter = JsonEmitter;
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
}

View File

@@ -1,39 +0,0 @@
use std::io::Write;
use crate::message::json::message_to_json_value;
use crate::message::{Emitter, EmitterContext, Message};
#[derive(Default)]
pub struct JsonLinesEmitter;
impl Emitter for JsonLinesEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
messages: &[Message],
_context: &EmitterContext,
) -> anyhow::Result<()> {
let mut w = writer;
for message in messages {
serde_json::to_writer(&mut w, &message_to_json_value(message))?;
w.write_all(b"\n")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use insta::assert_snapshot;
use crate::message::json_lines::JsonLinesEmitter;
use crate::message::tests::{capture_emitter_output, create_messages};
#[test]
fn output() {
let mut emitter = JsonLinesEmitter;
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
}

View File

@@ -1,235 +0,0 @@
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::io::Write;
use std::ops::Deref;
use rustc_hash::FxHashMap;
pub use azure::AzureEmitter;
pub use github::GithubEmitter;
pub use gitlab::GitlabEmitter;
pub use grouped::GroupedEmitter;
pub use json::JsonEmitter;
pub use json_lines::JsonLinesEmitter;
pub use junit::JunitEmitter;
pub use pylint::PylintEmitter;
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix};
use ruff_notebook::NotebookIndex;
use ruff_source_file::{SourceFile, SourceLocation};
use ruff_text_size::{Ranged, TextRange, TextSize};
pub use text::TextEmitter;
mod azure;
mod diff;
mod github;
mod gitlab;
mod grouped;
mod json;
mod json_lines;
mod junit;
mod pylint;
mod text;
#[derive(Debug, PartialEq, Eq)]
pub struct Message {
pub kind: DiagnosticKind,
pub range: TextRange,
pub fix: Option<Fix>,
pub file: SourceFile,
pub noqa_offset: TextSize,
}
impl Message {
pub fn from_diagnostic(
diagnostic: Diagnostic,
file: SourceFile,
noqa_offset: TextSize,
) -> Self {
Self {
range: diagnostic.range(),
kind: diagnostic.kind,
fix: diagnostic.fix,
file,
noqa_offset,
}
}
pub fn filename(&self) -> &str {
self.file.name()
}
pub fn compute_start_location(&self) -> SourceLocation {
self.file.to_source_code().source_location(self.start())
}
pub fn compute_end_location(&self) -> SourceLocation {
self.file.to_source_code().source_location(self.end())
}
}
impl Ord for Message {
fn cmp(&self, other: &Self) -> Ordering {
(&self.file, self.start()).cmp(&(&other.file, other.start()))
}
}
impl PartialOrd for Message {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ranged for Message {
fn range(&self) -> TextRange {
self.range
}
}
struct MessageWithLocation<'a> {
message: &'a Message,
start_location: SourceLocation,
}
impl Deref for MessageWithLocation<'_> {
type Target = Message;
fn deref(&self) -> &Self::Target {
self.message
}
}
fn group_messages_by_filename(messages: &[Message]) -> BTreeMap<&str, Vec<MessageWithLocation>> {
let mut grouped_messages = BTreeMap::default();
for message in messages {
grouped_messages
.entry(message.filename())
.or_insert_with(Vec::new)
.push(MessageWithLocation {
message,
start_location: message.compute_start_location(),
});
}
grouped_messages
}
/// Display format for a [`Message`]s.
///
/// The emitter serializes a slice of [`Message`]'s and writes them to a [`Write`].
pub trait Emitter {
/// Serializes the `messages` and writes the output to `writer`.
fn emit(
&mut self,
writer: &mut dyn Write,
messages: &[Message],
context: &EmitterContext,
) -> anyhow::Result<()>;
}
/// Context passed to [`Emitter`].
pub struct EmitterContext<'a> {
notebook_indexes: &'a FxHashMap<String, NotebookIndex>,
}
impl<'a> EmitterContext<'a> {
pub fn new(notebook_indexes: &'a FxHashMap<String, NotebookIndex>) -> Self {
Self { notebook_indexes }
}
/// Tests if the file with `name` is a jupyter notebook.
pub fn is_notebook(&self, name: &str) -> bool {
self.notebook_indexes.contains_key(name)
}
pub fn notebook_index(&self, name: &str) -> Option<&NotebookIndex> {
self.notebook_indexes.get(name)
}
}
#[cfg(test)]
mod tests {
use rustc_hash::FxHashMap;
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Edit, Fix};
use ruff_source_file::SourceFileBuilder;
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::message::{Emitter, EmitterContext, Message};
pub(super) fn create_messages() -> Vec<Message> {
let fib = r#"import os
def fibonacci(n):
"""Compute the nth number in the Fibonacci sequence."""
x = 1
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n - 1) + fibonacci(n - 2)
"#;
let unused_import = Diagnostic::new(
DiagnosticKind {
name: "UnusedImport".to_string(),
body: "`os` imported but unused".to_string(),
suggestion: Some("Remove unused import: `os`".to_string()),
},
TextRange::new(TextSize::from(7), TextSize::from(9)),
)
.with_fix(Fix::suggested(Edit::range_deletion(TextRange::new(
TextSize::from(0),
TextSize::from(10),
))));
let fib_source = SourceFileBuilder::new("fib.py", fib).finish();
let unused_variable = Diagnostic::new(
DiagnosticKind {
name: "UnusedVariable".to_string(),
body: "Local variable `x` is assigned to but never used".to_string(),
suggestion: Some("Remove assignment to unused variable `x`".to_string()),
},
TextRange::new(TextSize::from(94), TextSize::from(95)),
)
.with_fix(Fix::suggested(Edit::deletion(
TextSize::from(94),
TextSize::from(99),
)));
let file_2 = r#"if a == 1: pass"#;
let undefined_name = Diagnostic::new(
DiagnosticKind {
name: "UndefinedName".to_string(),
body: "Undefined name `a`".to_string(),
suggestion: None,
},
TextRange::new(TextSize::from(3), TextSize::from(4)),
);
let file_2_source = SourceFileBuilder::new("undef.py", file_2).finish();
let unused_import_start = unused_import.start();
let unused_variable_start = unused_variable.start();
let undefined_name_start = undefined_name.start();
vec![
Message::from_diagnostic(unused_import, fib_source.clone(), unused_import_start),
Message::from_diagnostic(unused_variable, fib_source, unused_variable_start),
Message::from_diagnostic(undefined_name, file_2_source, undefined_name_start),
]
}
pub(super) fn capture_emitter_output(
emitter: &mut dyn Emitter,
messages: &[Message],
) -> String {
let notebook_indexes = FxHashMap::default();
let context = EmitterContext::new(&notebook_indexes);
let mut output: Vec<u8> = Vec::new();
emitter.emit(&mut output, messages, &context).unwrap();
String::from_utf8(output).expect("Output to be valid UTF-8")
}
}

View File

@@ -1,8 +0,0 @@
---
source: crates/ruff/src/message/azure.rs
expression: content
---
##vso[task.logissue type=error;sourcepath=fib.py;linenumber=1;columnnumber=8;code=F401;]`os` imported but unused
##vso[task.logissue type=error;sourcepath=fib.py;linenumber=6;columnnumber=5;code=F841;]Local variable `x` is assigned to but never used
##vso[task.logissue type=error;sourcepath=undef.py;linenumber=1;columnnumber=4;code=F821;]Undefined name `a`

View File

@@ -1,8 +0,0 @@
---
source: crates/ruff/src/message/github.rs
expression: content
---
::error title=Ruff (F401),file=fib.py,line=1,col=8,endLine=1,endColumn=10::fib.py:1:8: F401 `os` imported but unused
::error title=Ruff (F841),file=fib.py,line=6,col=5,endLine=6,endColumn=6::fib.py:6:5: F841 Local variable `x` is assigned to but never used
::error title=Ruff (F821),file=undef.py,line=1,col=4,endLine=1,endColumn=5::undef.py:1:4: F821 Undefined name `a`

View File

@@ -1,42 +0,0 @@
---
source: crates/ruff/src/message/gitlab.rs
expression: redact_fingerprint(&content)
---
[
{
"description": "(F401) `os` imported but unused",
"fingerprint": "<redacted>",
"location": {
"lines": {
"begin": 1,
"end": 1
},
"path": "fib.py"
},
"severity": "major"
},
{
"description": "(F841) Local variable `x` is assigned to but never used",
"fingerprint": "<redacted>",
"location": {
"lines": {
"begin": 6,
"end": 6
},
"path": "fib.py"
},
"severity": "major"
},
{
"description": "(F821) Undefined name `a`",
"fingerprint": "<redacted>",
"location": {
"lines": {
"begin": 1,
"end": 1
},
"path": "undef.py"
},
"severity": "major"
}
]

View File

@@ -1,12 +0,0 @@
---
source: crates/ruff/src/message/grouped.rs
expression: content
---
fib.py:
1:8 F401 `os` imported but unused
6:5 F841 Local variable `x` is assigned to but never used
undef.py:
1:4 F821 Undefined name `a`

View File

@@ -1,31 +0,0 @@
---
source: crates/ruff/src/message/grouped.rs
expression: content
---
fib.py:
1:8 F401 [*] `os` imported but unused
|
1 | import os
| ^^ F401
|
= help: Remove unused import: `os`
6:5 F841 [*] Local variable `x` is assigned to but never used
|
4 | def fibonacci(n):
5 | """Compute the nth number in the Fibonacci sequence."""
6 | x = 1
| ^ F841
7 | if n == 0:
8 | return 0
|
= help: Remove assignment to unused variable `x`
undef.py:
1:4 F821 Undefined name `a`
|
1 | if a == 1: pass
| ^ F821
|

View File

@@ -1,31 +0,0 @@
---
source: crates/ruff/src/message/grouped.rs
expression: content
---
fib.py:
1:8 F401 `os` imported but unused
|
1 | import os
| ^^ F401
|
= help: Remove unused import: `os`
6:5 F841 Local variable `x` is assigned to but never used
|
4 | def fibonacci(n):
5 | """Compute the nth number in the Fibonacci sequence."""
6 | x = 1
| ^ F841
7 | if n == 0:
8 | return 0
|
= help: Remove assignment to unused variable `x`
undef.py:
1:4 F821 Undefined name `a`
|
1 | if a == 1: pass
| ^ F821
|

View File

@@ -1,86 +0,0 @@
---
source: crates/ruff/src/message/json.rs
expression: content
---
[
{
"code": "F401",
"end_location": {
"column": 10,
"row": 1
},
"filename": "fib.py",
"fix": {
"applicability": "Suggested",
"edits": [
{
"content": "",
"end_location": {
"column": 1,
"row": 2
},
"location": {
"column": 1,
"row": 1
}
}
],
"message": "Remove unused import: `os`"
},
"location": {
"column": 8,
"row": 1
},
"message": "`os` imported but unused",
"noqa_row": 1,
"url": "https://beta.ruff.rs/docs/rules/unused-import"
},
{
"code": "F841",
"end_location": {
"column": 6,
"row": 6
},
"filename": "fib.py",
"fix": {
"applicability": "Suggested",
"edits": [
{
"content": "",
"end_location": {
"column": 10,
"row": 6
},
"location": {
"column": 5,
"row": 6
}
}
],
"message": "Remove assignment to unused variable `x`"
},
"location": {
"column": 5,
"row": 6
},
"message": "Local variable `x` is assigned to but never used",
"noqa_row": 6,
"url": "https://beta.ruff.rs/docs/rules/unused-variable"
},
{
"code": "F821",
"end_location": {
"column": 5,
"row": 1
},
"filename": "undef.py",
"fix": null,
"location": {
"column": 4,
"row": 1
},
"message": "Undefined name `a`",
"noqa_row": 1,
"url": "https://beta.ruff.rs/docs/rules/undefined-name"
}
]

View File

@@ -1,8 +0,0 @@
---
source: crates/ruff/src/message/json_lines.rs
expression: content
---
{"code":"F401","end_location":{"column":10,"row":1},"filename":"fib.py","fix":{"applicability":"Suggested","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":1},"message":"`os` imported but unused","noqa_row":1,"url":"https://beta.ruff.rs/docs/rules/unused-import"}
{"code":"F841","end_location":{"column":6,"row":6},"filename":"fib.py","fix":{"applicability":"Suggested","edits":[{"content":"","end_location":{"column":10,"row":6},"location":{"column":5,"row":6}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":6},"message":"Local variable `x` is assigned to but never used","noqa_row":6,"url":"https://beta.ruff.rs/docs/rules/unused-variable"}
{"code":"F821","end_location":{"column":5,"row":1},"filename":"undef.py","fix":null,"location":{"column":4,"row":1},"message":"Undefined name `a`","noqa_row":1,"url":"https://beta.ruff.rs/docs/rules/undefined-name"}

View File

@@ -1,21 +0,0 @@
---
source: crates/ruff/src/message/junit.rs
expression: content
---
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="ruff" tests="3" failures="3" errors="0">
<testsuite name="fib.py" tests="2" disabled="0" errors="0" failures="2" package="org.ruff">
<testcase name="org.ruff.F401" classname="fib" line="1" column="8">
<failure message="`os` imported but unused">line 1, col 8, `os` imported but unused</failure>
</testcase>
<testcase name="org.ruff.F841" classname="fib" line="6" column="5">
<failure message="Local variable `x` is assigned to but never used">line 6, col 5, Local variable `x` is assigned to but never used</failure>
</testcase>
</testsuite>
<testsuite name="undef.py" tests="1" disabled="0" errors="0" failures="1" package="org.ruff">
<testcase name="org.ruff.F821" classname="undef" line="1" column="4">
<failure message="Undefined name `a`">line 1, col 4, Undefined name `a`</failure>
</testcase>
</testsuite>
</testsuites>

View File

@@ -1,8 +0,0 @@
---
source: crates/ruff/src/message/pylint.rs
expression: content
---
fib.py:1: [F401] `os` imported but unused
fib.py:6: [F841] Local variable `x` is assigned to but never used
undef.py:1: [F821] Undefined name `a`

View File

@@ -1,29 +0,0 @@
---
source: crates/ruff/src/message/text.rs
expression: content
---
fib.py:1:8: F401 `os` imported but unused
|
1 | import os
| ^^ F401
|
= help: Remove unused import: `os`
fib.py:6:5: F841 Local variable `x` is assigned to but never used
|
4 | def fibonacci(n):
5 | """Compute the nth number in the Fibonacci sequence."""
6 | x = 1
| ^ F841
7 | if n == 0:
8 | return 0
|
= help: Remove assignment to unused variable `x`
undef.py:1:4: F821 Undefined name `a`
|
1 | if a == 1: pass
| ^ F821
|

View File

@@ -1,29 +0,0 @@
---
source: crates/ruff/src/message/text.rs
expression: content
---
fib.py:1:8: F401 [*] `os` imported but unused
|
1 | import os
| ^^ F401
|
= help: Remove unused import: `os`
fib.py:6:5: F841 [*] Local variable `x` is assigned to but never used
|
4 | def fibonacci(n):
5 | """Compute the nth number in the Fibonacci sequence."""
6 | x = 1
| ^ F841
7 | if n == 0:
8 | return 0
|
= help: Remove assignment to unused variable `x`
undef.py:1:4: F821 Undefined name `a`
|
1 | if a == 1: pass
| ^ F821
|

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +0,0 @@
//! Airflow-specific rules.
pub(crate) mod rules;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::registry::Rule;
use crate::test::test_path;
use crate::{assert_messages, settings};
#[test_case(Rule::AirflowVariableNameTaskIdMismatch, Path::new("AIR001.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("airflow").join(path).as_path(),
&settings::Settings::for_rule(rule_code),
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -1,22 +0,0 @@
---
source: crates/ruff/src/rules/airflow/mod.rs
---
AIR001.py:11:1: AIR001 Task variable name should match the `task_id`: "my_task"
|
9 | my_task_2 = PythonOperator(callable=my_callable, task_id="my_task_2")
10 |
11 | incorrect_name = PythonOperator(task_id="my_task")
| ^^^^^^^^^^^^^^ AIR001
12 | incorrect_name_2 = PythonOperator(callable=my_callable, task_id="my_task_2")
|
AIR001.py:12:1: AIR001 Task variable name should match the `task_id`: "my_task_2"
|
11 | incorrect_name = PythonOperator(task_id="my_task")
12 | incorrect_name_2 = PythonOperator(callable=my_callable, task_id="my_task_2")
| ^^^^^^^^^^^^^^^^ AIR001
13 |
14 | from my_module import MyClass
|

View File

@@ -1,26 +0,0 @@
//! Rules from [eradicate](https://pypi.org/project/eradicate/).
pub(crate) mod detection;
pub(crate) mod rules;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::registry::Rule;
use crate::test::test_path;
use crate::{assert_messages, settings};
#[test_case(Rule::CommentedOutCode, Path::new("ERA001.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("eradicate").join(path).as_path(),
&settings::Settings::for_rule(rule_code),
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -1,72 +0,0 @@
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_index::Indexer;
use ruff_source_file::Locator;
use crate::registry::Rule;
use crate::settings::Settings;
use super::super::detection::comment_contains_code;
/// ## What it does
/// Checks for commented-out Python code.
///
/// ## Why is this bad?
/// Commented-out code is dead code, and is often included inadvertently.
/// It should be removed.
///
/// ## Example
/// ```python
/// # print('foo')
/// ```
///
/// ## Options
/// - `task-tags`
#[violation]
pub struct CommentedOutCode;
impl AlwaysAutofixableViolation for CommentedOutCode {
#[derive_message_formats]
fn message(&self) -> String {
format!("Found commented-out code")
}
fn autofix_title(&self) -> String {
"Remove commented-out code".to_string()
}
}
fn is_standalone_comment(line: &str) -> bool {
for char in line.chars() {
if char == '#' {
return true;
} else if !char.is_whitespace() {
return false;
}
}
unreachable!("Comment should contain '#' character")
}
/// ERA001
pub(crate) fn commented_out_code(
diagnostics: &mut Vec<Diagnostic>,
locator: &Locator,
indexer: &Indexer,
settings: &Settings,
) {
for range in indexer.comment_ranges() {
let line = locator.full_lines(*range);
// Verify that the comment is on its own line, and that it contains code.
if is_standalone_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
let mut diagnostic = Diagnostic::new(CommentedOutCode, *range);
if settings.rules.should_fix(Rule::CommentedOutCode) {
diagnostic.set_fix(Fix::manual(Edit::range_deletion(
locator.full_lines_range(*range),
)));
}
diagnostics.push(diagnostic);
}
}
}

View File

@@ -1,151 +0,0 @@
---
source: crates/ruff/src/rules/eradicate/mod.rs
---
ERA001.py:1:1: ERA001 [*] Found commented-out code
|
1 | #import os
| ^^^^^^^^^^ ERA001
2 | # from foo import junk
3 | #a = 3
|
= help: Remove commented-out code
Possible fix
1 |-#import os
2 1 | # from foo import junk
3 2 | #a = 3
4 3 | a = 4
ERA001.py:2:1: ERA001 [*] Found commented-out code
|
1 | #import os
2 | # from foo import junk
| ^^^^^^^^^^^^^^^^^^^^^^ ERA001
3 | #a = 3
4 | a = 4
|
= help: Remove commented-out code
Possible fix
1 1 | #import os
2 |-# from foo import junk
3 2 | #a = 3
4 3 | a = 4
5 4 | #foo(1, 2, 3)
ERA001.py:3:1: ERA001 [*] Found commented-out code
|
1 | #import os
2 | # from foo import junk
3 | #a = 3
| ^^^^^^ ERA001
4 | a = 4
5 | #foo(1, 2, 3)
|
= help: Remove commented-out code
Possible fix
1 1 | #import os
2 2 | # from foo import junk
3 |-#a = 3
4 3 | a = 4
5 4 | #foo(1, 2, 3)
6 5 |
ERA001.py:5:1: ERA001 [*] Found commented-out code
|
3 | #a = 3
4 | a = 4
5 | #foo(1, 2, 3)
| ^^^^^^^^^^^^^ ERA001
6 |
7 | def foo(x, y, z):
|
= help: Remove commented-out code
Possible fix
2 2 | # from foo import junk
3 3 | #a = 3
4 4 | a = 4
5 |-#foo(1, 2, 3)
6 5 |
7 6 | def foo(x, y, z):
8 7 | content = 1 # print('hello')
ERA001.py:13:5: ERA001 [*] Found commented-out code
|
11 | # This is a real comment.
12 | # # This is a (nested) comment.
13 | #return True
| ^^^^^^^^^^^^ ERA001
14 | return False
|
= help: Remove commented-out code
Possible fix
10 10 |
11 11 | # This is a real comment.
12 12 | # # This is a (nested) comment.
13 |- #return True
14 13 | return False
15 14 |
16 15 | #import os # noqa: ERA001
ERA001.py:21:5: ERA001 [*] Found commented-out code
|
19 | class A():
20 | pass
21 | # b = c
| ^^^^^^^ ERA001
|
= help: Remove commented-out code
Possible fix
18 18 |
19 19 | class A():
20 20 | pass
21 |- # b = c
22 21 |
23 22 |
24 23 | dictionary = {
ERA001.py:26:5: ERA001 [*] Found commented-out code
|
24 | dictionary = {
25 | # "key1": 123, # noqa: ERA001
26 | # "key2": 456,
| ^^^^^^^^^^^^^^ ERA001
27 | # "key3": 789, # test
28 | }
|
= help: Remove commented-out code
Possible fix
23 23 |
24 24 | dictionary = {
25 25 | # "key1": 123, # noqa: ERA001
26 |- # "key2": 456,
27 26 | # "key3": 789, # test
28 27 | }
29 28 |
ERA001.py:27:5: ERA001 [*] Found commented-out code
|
25 | # "key1": 123, # noqa: ERA001
26 | # "key2": 456,
27 | # "key3": 789, # test
| ^^^^^^^^^^^^^^^^^^^^^^ ERA001
28 | }
|
= help: Remove commented-out code
Possible fix
24 24 | dictionary = {
25 25 | # "key1": 123, # noqa: ERA001
26 26 | # "key2": 456,
27 |- # "key3": 789, # test
28 27 | }
29 28 |
30 29 | #import os # noqa

View File

@@ -1,35 +0,0 @@
//! Rules from [flake8-2020](https://pypi.org/project/flake8-2020/).
mod helpers;
pub(crate) mod rules;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::registry::Rule;
use crate::test::test_path;
use crate::{assert_messages, settings};
#[test_case(Rule::SysVersionSlice3, Path::new("YTT101.py"))]
#[test_case(Rule::SysVersion2, Path::new("YTT102.py"))]
#[test_case(Rule::SysVersionCmpStr3, Path::new("YTT103.py"))]
#[test_case(Rule::SysVersionInfo0Eq3, Path::new("YTT201.py"))]
#[test_case(Rule::SixPY3, Path::new("YTT202.py"))]
#[test_case(Rule::SysVersionInfo1CmpInt, Path::new("YTT203.py"))]
#[test_case(Rule::SysVersionInfoMinorCmpInt, Path::new("YTT204.py"))]
#[test_case(Rule::SysVersion0, Path::new("YTT301.py"))]
#[test_case(Rule::SysVersionCmpStr10, Path::new("YTT302.py"))]
#[test_case(Rule::SysVersionSlice1, Path::new("YTT303.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_2020").join(path).as_path(),
&settings::Settings::for_rule(rule_code),
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -1,32 +0,0 @@
---
source: crates/ruff/src/rules/flake8_2020/mod.rs
---
YTT101.py:6:7: YTT101 `sys.version[:3]` referenced (python3.10), use `sys.version_info`
|
4 | print(sys.version)
5 |
6 | print(sys.version[:3])
| ^^^^^^^^^^^ YTT101
7 | print(version[:3])
8 | print(v[:3])
|
YTT101.py:7:7: YTT101 `sys.version[:3]` referenced (python3.10), use `sys.version_info`
|
6 | print(sys.version[:3])
7 | print(version[:3])
| ^^^^^^^ YTT101
8 | print(v[:3])
|
YTT101.py:8:7: YTT101 `sys.version[:3]` referenced (python3.10), use `sys.version_info`
|
6 | print(sys.version[:3])
7 | print(version[:3])
8 | print(v[:3])
| ^ YTT101
9 |
10 | # the tool is timid and only flags certain numeric slices
|

View File

@@ -1,20 +0,0 @@
---
source: crates/ruff/src/rules/flake8_2020/mod.rs
---
YTT102.py:4:12: YTT102 `sys.version[2]` referenced (python3.10), use `sys.version_info`
|
2 | from sys import version
3 |
4 | py_minor = sys.version[2]
| ^^^^^^^^^^^ YTT102
5 | py_minor = version[2]
|
YTT102.py:5:12: YTT102 `sys.version[2]` referenced (python3.10), use `sys.version_info`
|
4 | py_minor = sys.version[2]
5 | py_minor = version[2]
| ^^^^^^^ YTT102
|

View File

@@ -1,50 +0,0 @@
---
source: crates/ruff/src/rules/flake8_2020/mod.rs
---
YTT103.py:4:1: YTT103 `sys.version` compared to string (python3.10), use `sys.version_info`
|
2 | from sys import version
3 |
4 | version < "3.5"
| ^^^^^^^ YTT103
5 | sys.version < "3.5"
6 | sys.version <= "3.5"
|
YTT103.py:5:1: YTT103 `sys.version` compared to string (python3.10), use `sys.version_info`
|
4 | version < "3.5"
5 | sys.version < "3.5"
| ^^^^^^^^^^^ YTT103
6 | sys.version <= "3.5"
7 | sys.version > "3.5"
|
YTT103.py:6:1: YTT103 `sys.version` compared to string (python3.10), use `sys.version_info`
|
4 | version < "3.5"
5 | sys.version < "3.5"
6 | sys.version <= "3.5"
| ^^^^^^^^^^^ YTT103
7 | sys.version > "3.5"
8 | sys.version >= "3.5"
|
YTT103.py:7:1: YTT103 `sys.version` compared to string (python3.10), use `sys.version_info`
|
5 | sys.version < "3.5"
6 | sys.version <= "3.5"
7 | sys.version > "3.5"
| ^^^^^^^^^^^ YTT103
8 | sys.version >= "3.5"
|
YTT103.py:8:1: YTT103 `sys.version` compared to string (python3.10), use `sys.version_info`
|
6 | sys.version <= "3.5"
7 | sys.version > "3.5"
8 | sys.version >= "3.5"
| ^^^^^^^^^^^ YTT103
|

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