Compare commits
108 Commits
v0.3.4
...
cjm/colors
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab9b38c0f1 | ||
|
|
af12ee1763 | ||
|
|
6b4fa17097 | ||
|
|
5e2482824c | ||
|
|
e0a8fb607a | ||
|
|
257964a8bc | ||
|
|
d467aa78c2 | ||
|
|
6e1c061e5f | ||
|
|
9872f51293 | ||
|
|
e54b591ec7 | ||
|
|
814b26f82e | ||
|
|
2a4084a2bb | ||
|
|
dff8f93457 | ||
|
|
859e3fc7fa | ||
|
|
0de23760ff | ||
|
|
b90e6df5cc | ||
|
|
0d20ec968f | ||
|
|
99dd3a8ab0 | ||
|
|
eee2d5b915 | ||
|
|
159bad73d5 | ||
|
|
7b48443624 | ||
|
|
d36f60999d | ||
|
|
67f0f615b2 | ||
|
|
200ebeebdc | ||
|
|
23e8279093 | ||
|
|
221b3236a8 | ||
|
|
a0e1544848 | ||
|
|
2740fab7ad | ||
|
|
7042b9b16d | ||
|
|
4047d456b6 | ||
|
|
20d69ea504 | ||
|
|
d021cac0c9 | ||
|
|
46369d48fe | ||
|
|
85d59198aa | ||
|
|
20a2e25cb0 | ||
|
|
a32e70d449 | ||
|
|
76d0edbbaa | ||
|
|
8d547ef83a | ||
|
|
d7a6978e05 | ||
|
|
786ff403b1 | ||
|
|
2ea0c3dce6 | ||
|
|
b9dfa7845f | ||
|
|
090f6580d3 | ||
|
|
eb884c8f76 | ||
|
|
f6b6f0df67 | ||
|
|
9f2127bf04 | ||
|
|
8cd096d9b5 | ||
|
|
716688d44e | ||
|
|
9ad9cea952 | ||
|
|
75e01420fa | ||
|
|
fc54f53662 | ||
|
|
7c2e9f71ea | ||
|
|
3c48913473 | ||
|
|
9f56902719 | ||
|
|
1bcdfe268d | ||
|
|
a0263ab472 | ||
|
|
6b580c1544 | ||
|
|
3f7d666e8b | ||
|
|
cce25ec116 | ||
|
|
fc7fa59e5f | ||
|
|
abbefae6f1 | ||
|
|
f9d0c6d9ae | ||
|
|
4d59142255 | ||
|
|
72aa1ce00f | ||
|
|
b6b737c937 | ||
|
|
b074e7dc9b | ||
|
|
e81f1f7971 | ||
|
|
83c3580346 | ||
|
|
f0662eea48 | ||
|
|
b49b861b2d | ||
|
|
877a9145ae | ||
|
|
ba24bd88cf | ||
|
|
825fd7c990 | ||
|
|
a28776e3aa | ||
|
|
80b46889ed | ||
|
|
fdd25f0d99 | ||
|
|
e0ab5629cc | ||
|
|
960e47423c | ||
|
|
4950ca4142 | ||
|
|
3e1f3b8132 | ||
|
|
21f63c57d5 | ||
|
|
bd07c13348 | ||
|
|
9e21e5918c | ||
|
|
f7aab5ac69 | ||
|
|
59ac3f48c8 | ||
|
|
9512bd66b5 | ||
|
|
5729dc3589 | ||
|
|
2890485785 | ||
|
|
a5f6e5dc88 | ||
|
|
d93db63a22 | ||
|
|
ecc11dcc12 | ||
|
|
e9115b8d8a | ||
|
|
d625f55c05 | ||
|
|
9856c1446b | ||
|
|
39fb6d9bfc | ||
|
|
22f237fec6 | ||
|
|
021f0bdccb | ||
|
|
7cc40d5621 | ||
|
|
c447454111 | ||
|
|
895d9df02f | ||
|
|
0c194f55e8 | ||
|
|
0a99bd84ce | ||
|
|
9feb9b0aa8 | ||
|
|
61b7982422 | ||
|
|
594b232e0f | ||
|
|
a06ffeb54e | ||
|
|
b74dd420fc | ||
|
|
4f06d59ff6 |
21
.github/dependabot.yml
vendored
21
.github/dependabot.yml
vendored
@@ -1,21 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
labels: ["internal"]
|
||||
groups:
|
||||
actions:
|
||||
patterns:
|
||||
- "*"
|
||||
ignore:
|
||||
# The latest versions of these are not compatible with our release workflow
|
||||
- dependency-name: "actions/upload-artifact"
|
||||
- dependency-name: "actions/download-artifact"
|
||||
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
labels: ["internal"]
|
||||
60
.github/renovate.json5
vendored
Normal file
60
.github/renovate.json5
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
$schema: "https://docs.renovatebot.com/renovate-schema.json",
|
||||
dependencyDashboard: true,
|
||||
suppressNotifications: ["prEditedNotification"],
|
||||
extends: ["config:recommended"],
|
||||
labels: ["internal"],
|
||||
schedule: ["on Monday"],
|
||||
separateMajorMinor: false,
|
||||
prHourlyLimit: 10,
|
||||
enabledManagers: ["github-actions", "pre-commit", "cargo", "pep621", "npm"],
|
||||
cargo: {
|
||||
// See https://docs.renovatebot.com/configuration-options/#rangestrategy
|
||||
rangeStrategy: "update-lockfile",
|
||||
},
|
||||
pep621: {
|
||||
fileMatch: ["^(python|scripts)/.*pyproject\\.toml$"],
|
||||
},
|
||||
npm: {
|
||||
fileMatch: ["^playground/.*package\\.json$"],
|
||||
},
|
||||
"pre-commit": {
|
||||
enabled: true,
|
||||
},
|
||||
packageRules: [
|
||||
{
|
||||
// Group upload/download artifact updates, the versions are dependent
|
||||
groupName: "Artifact GitHub Actions dependencies",
|
||||
matchManagers: ["github-actions"],
|
||||
matchPackagePatterns: ["actions/.*-artifact"],
|
||||
description: "Weekly update of artifact-related GitHub Actions dependencies",
|
||||
},
|
||||
{
|
||||
groupName: "pre-commit dependencies",
|
||||
matchManagers: ["pre-commit"],
|
||||
description: "Weekly update of pre-commit dependencies",
|
||||
},
|
||||
{
|
||||
groupName: "NPM Development dependencies",
|
||||
matchManagers: ["npm"],
|
||||
matchDepTypes: ["devDependencies"],
|
||||
description: "Weekly update of NPM development dependencies",
|
||||
},
|
||||
{
|
||||
groupName: "Monaco",
|
||||
matchManagers: ["npm"],
|
||||
matchPackagePatterns: ["monaco"],
|
||||
description: "Weekly update of the Monaco editor",
|
||||
},
|
||||
{
|
||||
groupName: "strum",
|
||||
matchManagers: ["cargo"],
|
||||
matchPackagePatterns: ["strum"],
|
||||
description: "Weekly update of strum dependencies",
|
||||
},
|
||||
],
|
||||
vulnerabilityAlerts: {
|
||||
commitMessageSuffix: "",
|
||||
labels: ["internal", "security"],
|
||||
},
|
||||
}
|
||||
19
.github/workflows/ci.yaml
vendored
19
.github/workflows/ci.yaml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: tj-actions/changed-files@v43
|
||||
- uses: tj-actions/changed-files@v44
|
||||
id: changed
|
||||
with:
|
||||
files_yaml: |
|
||||
@@ -525,8 +525,23 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
# Codspeed comes with a very ancient cargo version (1.66) that resolves features flags differently than what we use now.
|
||||
# This can result in build failures; see https://github.com/astral-sh/ruff/pull/10700.
|
||||
# There's a pending codspeed PR to upgrade to a newer cargo version, but until that's merged, we need to use the workaround below.
|
||||
# https://github.com/CodSpeedHQ/codspeed-rust/pull/31
|
||||
# What we do is to call cargo build manually with the correct feature flags and RUSTC settings. We'll have to
|
||||
# manually maintain the list of benchmarks to run with codspeed (the benefit is that we could detect which benchmarks to run and build based on the changes).
|
||||
# This is inspired by https://github.com/oxc-project/oxc/blob/a0532adc654039a0c7ead7b35216dfa0b0cb8e8f/.github/workflows/benchmark.yml
|
||||
- name: "Build benchmarks"
|
||||
run: cargo codspeed build --features codspeed -p ruff_benchmark
|
||||
env:
|
||||
RUSTFLAGS: "-C debuginfo=2 -C strip=none -g --cfg codspeed"
|
||||
shell: bash
|
||||
# Build all benchmarks, copy the binary to the codspeed directory, remove any `*.d` files that might have been created.
|
||||
run: |
|
||||
cargo build --release -p ruff_benchmark --bench parser --bench linter --bench formatter --bench lexer --features=codspeed
|
||||
mkdir -p ./target/codspeed/ruff_benchmark
|
||||
cp ./target/release/deps/{lexer,parser,linter,formatter}* target/codspeed/ruff_benchmark/
|
||||
rm -rf ./target/codspeed/ruff_benchmark/*.d
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@v2
|
||||
|
||||
62
CHANGELOG.md
62
CHANGELOG.md
@@ -1,5 +1,54 @@
|
||||
# Changelog
|
||||
|
||||
## 0.3.5
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`pylint`\] Implement `modified-iterating-set` (`E4703`) ([#10473](https://github.com/astral-sh/ruff/pull/10473))
|
||||
- \[`refurb`\] Implement `for-loop-set-mutations` (`FURB142`) ([#10583](https://github.com/astral-sh/ruff/pull/10583))
|
||||
- \[`refurb`\] Implement `unnecessary-from-float` (`FURB164`) ([#10647](https://github.com/astral-sh/ruff/pull/10647))
|
||||
- \[`refurb`\] Implement `verbose-decimal-constructor` (`FURB157`) ([#10533](https://github.com/astral-sh/ruff/pull/10533))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-comprehensions`\] Handled special case for `C401` which also matches `C416` ([#10596](https://github.com/astral-sh/ruff/pull/10596))
|
||||
- \[`flake8-pyi`\] Mark `unaliased-collections-abc-set-import` fix as "safe" for more cases in stub files (`PYI025`) ([#10547](https://github.com/astral-sh/ruff/pull/10547))
|
||||
- \[`numpy`\] Add `row_stack` to NumPy 2.0 migration rule ([#10646](https://github.com/astral-sh/ruff/pull/10646))
|
||||
- \[`pycodestyle`\] Allow cell magics before an import (`E402`) ([#10545](https://github.com/astral-sh/ruff/pull/10545))
|
||||
- \[`pycodestyle`\] Avoid blank line rules for the first logical line in cell ([#10291](https://github.com/astral-sh/ruff/pull/10291))
|
||||
|
||||
### Configuration
|
||||
|
||||
- Respected nested namespace packages ([#10541](https://github.com/astral-sh/ruff/pull/10541))
|
||||
- \[`flake8-boolean-trap`\] Add setting for user defined allowed boolean trap ([#10531](https://github.com/astral-sh/ruff/pull/10531))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Correctly handle references in `__all__` definitions when renaming symbols in autofixes ([#10527](https://github.com/astral-sh/ruff/pull/10527))
|
||||
- Track ranges of names inside `__all__` definitions ([#10525](https://github.com/astral-sh/ruff/pull/10525))
|
||||
- \[`flake8-bugbear`\] Avoid false positive for usage after `continue` (`B031`) ([#10539](https://github.com/astral-sh/ruff/pull/10539))
|
||||
- \[`flake8-copyright`\] Accept commas in default copyright pattern ([#9498](https://github.com/astral-sh/ruff/pull/9498))
|
||||
- \[`flake8-datetimez`\] Allow f-strings with `%z` for `DTZ007` ([#10651](https://github.com/astral-sh/ruff/pull/10651))
|
||||
- \[`flake8-pytest-style`\] Fix `PT014` autofix for last item in list ([#10532](https://github.com/astral-sh/ruff/pull/10532))
|
||||
- \[`flake8-quotes`\] Ignore `Q000`, `Q001` when string is inside forward ref ([#10585](https://github.com/astral-sh/ruff/pull/10585))
|
||||
- \[`isort`\] Always place non-relative imports after relative imports ([#10669](https://github.com/astral-sh/ruff/pull/10669))
|
||||
- \[`isort`\] Respect Unicode characters in import sorting ([#10529](https://github.com/astral-sh/ruff/pull/10529))
|
||||
- \[`pyflakes`\] Fix F821 false negatives when `from __future__ import annotations` is active (attempt 2) ([#10524](https://github.com/astral-sh/ruff/pull/10524))
|
||||
- \[`pyflakes`\] Make `unnecessary-lambda` an always-unsafe fix ([#10668](https://github.com/astral-sh/ruff/pull/10668))
|
||||
- \[`pylint`\] Fixed false-positive on the rule `PLW1641` (`eq-without-hash`) ([#10566](https://github.com/astral-sh/ruff/pull/10566))
|
||||
- \[`ruff`\] Fix panic in unused `# noqa` removal with multi-byte space (`RUF100`) ([#10682](https://github.com/astral-sh/ruff/pull/10682))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add PR title format to `CONTRIBUTING.md` ([#10665](https://github.com/astral-sh/ruff/pull/10665))
|
||||
- Fix list markup to include blank lines required ([#10591](https://github.com/astral-sh/ruff/pull/10591))
|
||||
- Put `flake8-logging` next to the other flake8 plugins in registry ([#10587](https://github.com/astral-sh/ruff/pull/10587))
|
||||
- \[`flake8-bandit`\] Update warning message for rule `S305` to address insecure block cipher mode use ([#10602](https://github.com/astral-sh/ruff/pull/10602))
|
||||
- \[`flake8-bugbear`\] Document use of anonymous assignment in `useless-expression` ([#10551](https://github.com/astral-sh/ruff/pull/10551))
|
||||
- \[`flake8-datetimez`\] Clarify error messages and docs for `DTZ` rules ([#10621](https://github.com/astral-sh/ruff/pull/10621))
|
||||
- \[`pycodestyle`\] Use same before vs. after numbers for `space-around-operator` ([#10640](https://github.com/astral-sh/ruff/pull/10640))
|
||||
- \[`ruff`\] Change `quadratic-list-summation` docs to use `iadd` consistently ([#10666](https://github.com/astral-sh/ruff/pull/10666))
|
||||
|
||||
## 0.3.4
|
||||
|
||||
### Preview features
|
||||
@@ -97,7 +146,7 @@
|
||||
- Fix unstable `with` items formatting ([#10274](https://github.com/astral-sh/ruff/pull/10274))
|
||||
- Avoid repeating function calls in f-string conversions ([#10265](https://github.com/astral-sh/ruff/pull/10265))
|
||||
- Fix E203 false positive for slices in format strings ([#10280](https://github.com/astral-sh/ruff/pull/10280))
|
||||
- Fix incorrect `Parameter` range for `*args` and `**kwargs` ([#10283](https://github.com/astral-sh/ruff/pull/10283))
|
||||
- Fix incorrect `Parameter` range for `*args` and `**kwargs` ([#10283](https://github.com/astral-sh/ruff/pull/10283))
|
||||
- Treat `typing.Annotated` subscripts as type definitions ([#10285](https://github.com/astral-sh/ruff/pull/10285))
|
||||
|
||||
## 0.3.1
|
||||
@@ -205,8 +254,7 @@ This release introduces the Ruff 2024.2 style, stabilizing the following changes
|
||||
Highlights include:
|
||||
|
||||
- Initial support formatting f-strings (in `--preview`).
|
||||
- Support for overriding arbitrary configuration options via the CLI through an expanded `--config`
|
||||
argument (e.g., `--config "lint.isort.combine-as-imports=false"`).
|
||||
- Support for overriding arbitrary configuration options via the CLI through an expanded `--config` argument (e.g., `--config "lint.isort.combine-as-imports=false"`).
|
||||
- Significant performance improvements in Ruff's lexer, parser, and lint rules.
|
||||
|
||||
### Preview features
|
||||
@@ -854,7 +902,7 @@ docstrings via the `docstring-code-format` setting.
|
||||
- \[`pylint`\] Default `max-positional-args` to `max-args` ([#8998](https://github.com/astral-sh/ruff/pull/8998))
|
||||
- \[`pylint`\] Add `allow-dunder-method-names` setting for `bad-dunder-method-name` (`PLW3201`) ([#8812](https://github.com/astral-sh/ruff/pull/8812))
|
||||
- \[`isort`\] Add support for `from-first` setting ([#8663](https://github.com/astral-sh/ruff/pull/8663))
|
||||
- \[`isort`\] Add support for `length-sort` settings ([#8841](https://github.com/astral-sh/ruff/pull/8841))
|
||||
- \[`isort`\] Add support for `length-sort` settings ([#8841](https://github.com/astral-sh/ruff/pull/8841))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
@@ -983,7 +1031,7 @@ docstrings via the `docstring-code-format` setting.
|
||||
- \[`flake8-trio`\] Implement `TRIO115` ([#8486](https://github.com/astral-sh/ruff/pull/8486))
|
||||
- \[`refurb`\] Implement `type-none-comparison` (`FURB169`) ([#8487](https://github.com/astral-sh/ruff/pull/8487))
|
||||
- Flag all comparisons against builtin types in `E721` ([#8491](https://github.com/astral-sh/ruff/pull/8491))
|
||||
- Make `SIM118` fix as safe when the expression is a known dictionary ([#8525](https://github.com/astral-sh/ruff/pull/8525))
|
||||
- Make `SIM118` fix as safe when the expression is a known dictionary ([#8525](https://github.com/astral-sh/ruff/pull/8525))
|
||||
|
||||
### Formatter
|
||||
|
||||
@@ -1151,7 +1199,7 @@ Try it today with `ruff format`! [Check out the blog post](https://astral.sh/blo
|
||||
- Add `backports.strenum` to `deprecated-imports` ([#8113](https://github.com/astral-sh/ruff/pull/8113))
|
||||
- Update `SIM112` to ignore `https_proxy`, `http_proxy`, and `no_proxy` ([#8140](https://github.com/astral-sh/ruff/pull/8140))
|
||||
- Update fix for `literal-membership` (`PLR6201`) to be unsafe ([#8097](https://github.com/astral-sh/ruff/pull/8097))
|
||||
- Update fix for `mutable-argument-defaults` (`B006`) to be unsafe ([#8108](https://github.com/astral-sh/ruff/pull/8108))
|
||||
- Update fix for `mutable-argument-defaults` (`B006`) to be unsafe ([#8108](https://github.com/astral-sh/ruff/pull/8108))
|
||||
|
||||
### Formatter
|
||||
|
||||
@@ -1279,7 +1327,7 @@ Read Ruff's new [versioning policy](https://docs.astral.sh/ruff/versioning/).
|
||||
- \[`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/).*
|
||||
_New rules are added in [preview](https://docs.astral.sh/ruff/preview/)._
|
||||
|
||||
### Configuration
|
||||
|
||||
|
||||
@@ -33,27 +33,18 @@ Welcome! We're happy to have you here. Thank you in advance for your contributio
|
||||
|
||||
## The Basics
|
||||
|
||||
Ruff welcomes contributions in the form of Pull Requests.
|
||||
Ruff welcomes contributions in the form of pull requests.
|
||||
|
||||
For small changes (e.g., bug fixes), feel free to submit a PR.
|
||||
|
||||
For larger changes (e.g., new lint rules, new functionality, new configuration options), consider
|
||||
creating an [**issue**](https://github.com/astral-sh/ruff/issues) outlining your proposed change.
|
||||
You can also join us on [**Discord**](https://discord.com/invite/astral-sh) to discuss your idea with the
|
||||
You can also join us on [Discord](https://discord.com/invite/astral-sh) to discuss your idea with the
|
||||
community. We've labeled [beginner-friendly tasks](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
||||
in the issue tracker, along with [bugs](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
||||
and [improvements](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Aaccepted)
|
||||
that are ready for contributions.
|
||||
|
||||
If you're looking for a place to start, we recommend implementing a new lint rule (see:
|
||||
[_Adding a new lint rule_](#example-adding-a-new-lint-rule), which will allow you to learn from and
|
||||
pattern-match against the examples in the existing codebase. Many lint rules are inspired by
|
||||
existing Python plugins, which can be used as a reference implementation.
|
||||
|
||||
As a concrete example: consider taking on one of the rules from the [`flake8-pyi`](https://github.com/astral-sh/ruff/issues/848)
|
||||
plugin, and looking to the originating [Python source](https://github.com/PyCQA/flake8-pyi) for
|
||||
guidance.
|
||||
|
||||
If you have suggestions on how we might improve the contributing documentation, [let us know](https://github.com/astral-sh/ruff/discussions/5693)!
|
||||
|
||||
### Prerequisites
|
||||
@@ -107,7 +98,7 @@ RUFF_UPDATE_SCHEMA=1 cargo test # Rust testing and updating ruff.schema.json
|
||||
pre-commit run --all-files --show-diff-on-failure # Rust and Python formatting, Markdown and Python linting, etc.
|
||||
```
|
||||
|
||||
These checks will run on GitHub Actions when you open your Pull Request, but running them locally
|
||||
These checks will run on GitHub Actions when you open your pull request, but running them locally
|
||||
will save you time and expedite the merge process.
|
||||
|
||||
Note that many code changes also require updating the snapshot tests, which is done interactively
|
||||
@@ -117,7 +108,14 @@ after running `cargo test` like so:
|
||||
cargo insta review
|
||||
```
|
||||
|
||||
Your Pull Request will be reviewed by a maintainer, which may involve a few rounds of iteration
|
||||
If your pull request relates to a specific lint rule, include the category and rule code in the
|
||||
title, as in the following examples:
|
||||
|
||||
- \[`flake8-bugbear`\] Avoid false positive for usage after `continue` (`B031`)
|
||||
- \[`flake8-simplify`\] Detect implicit `else` cases in `needless-bool` (`SIM103`)
|
||||
- \[`pycodestyle`\] Implement `redundant-backslash` (`E502`)
|
||||
|
||||
Your pull request will be reviewed by a maintainer, which may involve a few rounds of iteration
|
||||
prior to merging.
|
||||
|
||||
### Project Structure
|
||||
@@ -125,8 +123,8 @@ prior to merging.
|
||||
Ruff is structured as a monorepo with a [flat crate structure](https://matklad.github.io/2021/08/22/large-rust-workspaces.html),
|
||||
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_linter`). As a contributor, that's the crate that'll be most relevant to you.
|
||||
The vast majority of the code, including all lint rules, lives in the `ruff_linter` crate (located
|
||||
at `crates/ruff_linter`). As a contributor, that's the crate that'll be most relevant to you.
|
||||
|
||||
At the time of writing, the repository includes the following crates:
|
||||
|
||||
@@ -199,11 +197,14 @@ and calling out to lint rule analyzer functions as it goes.
|
||||
If you need to inspect the AST, you can run `cargo dev print-ast` with a Python file. Grep
|
||||
for the `Diagnostic::new` invocations to understand how other, similar rules are implemented.
|
||||
|
||||
Once you're satisfied with your code, add tests for your rule. See [rule testing](#rule-testing-fixtures-and-snapshots)
|
||||
for more details.
|
||||
Once you're satisfied with your code, add tests for your rule
|
||||
(see: [rule testing](#rule-testing-fixtures-and-snapshots)), and regenerate the documentation and
|
||||
associated assets (like our JSON Schema) with `cargo dev generate-all`.
|
||||
|
||||
Finally, regenerate the documentation and other generated assets (like our JSON Schema) with:
|
||||
`cargo dev generate-all`.
|
||||
Finally, submit a pull request, and include the category, rule name, and rule code in the title, as
|
||||
in:
|
||||
|
||||
> \[`pycodestyle`\] Implement `redundant-backslash` (`E502`)
|
||||
|
||||
#### Rule naming convention
|
||||
|
||||
@@ -813,8 +814,8 @@ To understand Ruff's import categorization system, we first need to define two c
|
||||
"project root".)
|
||||
- "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://docs.astral.sh/ruff/settings/#namespace-packages)); take the directory
|
||||
you reach a parent directory that doesn't contain an `__init__.py` file (and isn't in a subtree
|
||||
marked as 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:
|
||||
|
||||
278
Cargo.lock
generated
278
Cargo.lock
generated
@@ -29,9 +29,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -123,9 +123,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.80"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
|
||||
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
|
||||
|
||||
[[package]]
|
||||
name = "argfile"
|
||||
@@ -196,9 +196,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.2"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
@@ -255,9 +255,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.35"
|
||||
version = "0.4.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
|
||||
checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
@@ -294,9 +294,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.3"
|
||||
version = "4.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813"
|
||||
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -358,14 +358,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.3"
|
||||
version = "4.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f"
|
||||
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -596,7 +596,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -607,7 +607,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -712,16 +712,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.2"
|
||||
name = "env_filter"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
|
||||
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"humantime",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1020,9 +1030,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.4"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
|
||||
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
@@ -1046,9 +1056,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.35.1"
|
||||
version = "1.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c985c1bef99cf13c58fade470483d81a2bfe846ebde60ed28cc2dddec2df9e2"
|
||||
checksum = "3eab73f58e59ca6526037208f0e98851159ec1633cf17b6cd2e1f2c3fd5d53cc"
|
||||
dependencies = [
|
||||
"console",
|
||||
"globset",
|
||||
@@ -1058,14 +1068,13 @@ dependencies = [
|
||||
"serde",
|
||||
"similar",
|
||||
"walkdir",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "insta-cmd"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809d3023d1d6e8d5c2206f199251f75cb26180e41f18cb0f22dd119161cb5127"
|
||||
checksum = "1980f17994b79f75670aa90cfc8d35edc4aa248f16aa48b5e27835b080e452a2"
|
||||
dependencies = [
|
||||
"insta",
|
||||
"serde",
|
||||
@@ -1099,7 +1108,7 @@ dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1282,7 +1291,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dbd2f3cd9346422ebdc3a614aed6969d4e0b3e9c10517f33b30326acf894c11"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1301,7 +1310,7 @@ version = "0.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"bitflags 2.5.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
]
|
||||
@@ -1348,9 +1357,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lsp-types"
|
||||
version = "0.95.0"
|
||||
version = "0.95.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "158c1911354ef73e8fe42da6b10c0484cb65c7f1007f28022e847706c1ab6984"
|
||||
checksum = "8e34d33a8e9b006cd3fc4fe69a921affa097bae4bb65f76271f4644f9a334365"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"serde",
|
||||
@@ -1376,9 +1385,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.1"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||
|
||||
[[package]]
|
||||
name = "mimalloc"
|
||||
@@ -1461,7 +1470,7 @@ version = "6.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"bitflags 2.5.0",
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
@@ -1484,6 +1493,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.49.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.18"
|
||||
@@ -1542,6 +1560,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
@@ -1634,6 +1658,18 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pep440_rs"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15efd4d885c29126cc93e12af3087896e2518bd5ca0fb328c19c4ef9cecfa8be"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"serde",
|
||||
"unicode-width",
|
||||
"unscanny",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pep508_rs"
|
||||
version = "0.3.0"
|
||||
@@ -1641,7 +1677,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "910c513bea0f4f833122321c0f20e8c704e01de98692f6989c2ec21f43d88b1e"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pep440_rs",
|
||||
"pep440_rs 0.4.0",
|
||||
"regex",
|
||||
"serde",
|
||||
"thiserror",
|
||||
@@ -1727,7 +1763,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1774,7 +1810,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95c3dd745f99aa3c554b7bb00859f7d18c2f1d6afd749ccc86d60b61e702abd9"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"pep440_rs",
|
||||
"pep440_rs 0.4.0",
|
||||
"pep508_rs",
|
||||
"serde",
|
||||
"toml",
|
||||
@@ -1845,9 +1881,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.9.0"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
@@ -1885,9 +1921,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.3"
|
||||
version = "1.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
||||
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -1945,7 +1981,7 @@ dependencies = [
|
||||
"pmutil",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1965,18 +2001,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anyhow",
|
||||
"argfile",
|
||||
"bincode",
|
||||
"bitflags 2.4.2",
|
||||
"bitflags 2.5.0",
|
||||
"cachedir",
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap_complete_command",
|
||||
"clearscreen",
|
||||
"colored",
|
||||
"filetime",
|
||||
"ignore",
|
||||
"insta",
|
||||
@@ -1987,6 +2023,7 @@ dependencies = [
|
||||
"mimalloc",
|
||||
"notify",
|
||||
"num_cpus",
|
||||
"owo-colors",
|
||||
"path-absolutize",
|
||||
"rayon",
|
||||
"regex",
|
||||
@@ -2127,15 +2164,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
"anstream",
|
||||
"anyhow",
|
||||
"bitflags 2.4.2",
|
||||
"bitflags 2.5.0",
|
||||
"chrono",
|
||||
"clap",
|
||||
"colored",
|
||||
"fern",
|
||||
"glob",
|
||||
"globset",
|
||||
@@ -2149,9 +2186,10 @@ dependencies = [
|
||||
"memchr",
|
||||
"natord",
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"path-absolutize",
|
||||
"pathdiff",
|
||||
"pep440_rs",
|
||||
"pep440_rs 0.5.0",
|
||||
"pyproject-toml",
|
||||
"quick-junit",
|
||||
"regex",
|
||||
@@ -2195,7 +2233,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_python_trivia",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2222,7 +2260,7 @@ name = "ruff_python_ast"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bitflags 2.4.2",
|
||||
"bitflags 2.5.0",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools 0.12.1",
|
||||
@@ -2294,7 +2332,7 @@ dependencies = [
|
||||
name = "ruff_python_literal"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"bitflags 2.5.0",
|
||||
"hexf-parse",
|
||||
"itertools 0.12.1",
|
||||
"lexical-parse-float",
|
||||
@@ -2307,7 +2345,6 @@ name = "ruff_python_parser"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.2",
|
||||
"bstr",
|
||||
"insta",
|
||||
"is-macro",
|
||||
@@ -2339,7 +2376,7 @@ dependencies = [
|
||||
name = "ruff_python_semantic"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"bitflags 2.5.0",
|
||||
"is-macro",
|
||||
"ruff_index",
|
||||
"ruff_python_ast",
|
||||
@@ -2400,7 +2437,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_shrinking"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2464,7 +2501,6 @@ name = "ruff_workspace"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"colored",
|
||||
"dirs 5.0.1",
|
||||
"glob",
|
||||
"globset",
|
||||
@@ -2472,8 +2508,9 @@ dependencies = [
|
||||
"is-macro",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"owo-colors",
|
||||
"path-absolutize",
|
||||
"pep440_rs",
|
||||
"pep440_rs 0.5.0",
|
||||
"regex",
|
||||
"ruff_cache",
|
||||
"ruff_formatter",
|
||||
@@ -2513,7 +2550,7 @@ version = "0.38.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"bitflags 2.5.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
@@ -2642,7 +2679,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2658,9 +2695,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.114"
|
||||
version = "1.0.115"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
|
||||
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -2675,7 +2712,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2698,9 +2735,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.6.1"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270"
|
||||
checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -2709,14 +2746,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.6.1"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d"
|
||||
checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2745,9 +2782,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "2.4.0"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21"
|
||||
checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
@@ -2757,9 +2794,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.1"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
@@ -2809,24 +2846,24 @@ checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.25.0"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||
checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.25.3"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
|
||||
checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2848,9 +2885,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.52"
|
||||
version = "2.0.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
|
||||
checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2880,15 +2917,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.3.0"
|
||||
@@ -2930,7 +2958,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2941,7 +2969,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
"test-case-core",
|
||||
]
|
||||
|
||||
@@ -2962,7 +2990,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3031,9 +3059,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.11"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af06656561d28735e9c1cd63dfd57132c8155426aa6af24f36a00a351f88c48e"
|
||||
checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
@@ -3052,9 +3080,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.7"
|
||||
version = "0.22.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18769cd1cec395d70860ceb4d932812a0b4d06b1a4bb336745a4d21b9496e992"
|
||||
checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -3083,7 +3111,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3108,17 +3136,6 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
@@ -3137,7 +3154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"nu-ansi-term 0.46.0",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sharded-slab",
|
||||
@@ -3145,18 +3162,18 @@ dependencies = [
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log 0.2.0",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-tree"
|
||||
version = "0.2.5"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ec6adcab41b1391b08a308cc6302b79f8095d1673f6947c2dc65ffb028b0b2d"
|
||||
checksum = "65139ecd2c3f6484c3b99bc01c77afe21e95473630747c7aca525e78b0666675"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-ansi-term 0.49.0",
|
||||
"tracing-core",
|
||||
"tracing-log 0.1.4",
|
||||
"tracing-log",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
@@ -3263,6 +3280,12 @@ dependencies = [
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unscanny"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@@ -3306,9 +3329,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.7.0"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
|
||||
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"rand",
|
||||
@@ -3318,13 +3341,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uuid-macro-internal"
|
||||
version = "1.7.0"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7abb14ae1a50dad63eaa768a458ef43d298cd1bd44951677bd10b732a9ba2a2d"
|
||||
checksum = "9881bea7cbe687e36c9ab3b778c36cd0487402e270304e8b1296d5085303c1a2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3409,7 +3432,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3443,7 +3466,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -3476,7 +3499,7 @@ checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3700,15 +3723,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.1"
|
||||
@@ -3741,7 +3755,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
25
Cargo.toml
25
Cargo.toml
@@ -12,12 +12,13 @@ authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
license = "MIT"
|
||||
|
||||
[workspace.dependencies]
|
||||
aho-corasick = { version = "1.1.2" }
|
||||
aho-corasick = { version = "1.1.3" }
|
||||
annotate-snippets = { version = "0.9.2", features = ["color"] }
|
||||
anstream = { version = "0.6.13" }
|
||||
anyhow = { version = "1.0.80" }
|
||||
argfile = { version = "0.1.6" }
|
||||
bincode = { version = "1.3.3" }
|
||||
bitflags = { version = "2.4.1" }
|
||||
bitflags = { version = "2.5.0" }
|
||||
bstr = { version = "1.9.1" }
|
||||
cachedir = { version = "0.3.1" }
|
||||
chrono = { version = "0.4.35", default-features = false, features = ["clock"] }
|
||||
@@ -25,7 +26,6 @@ clap = { version = "4.5.3", features = ["derive"] }
|
||||
clap_complete_command = { version = "0.5.1" }
|
||||
clearscreen = { version = "2.0.0" }
|
||||
codspeed-criterion-compat = { version = "2.4.0", default-features = false }
|
||||
colored = { version = "2.1.0" }
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
countme = { version = "3.0.1" }
|
||||
@@ -33,7 +33,7 @@ criterion = { version = "0.5.1", default-features = false }
|
||||
crossbeam = { version = "0.8.4" }
|
||||
dirs = { version = "5.0.0" }
|
||||
drop_bomb = { version = "0.1.5" }
|
||||
env_logger = { version = "0.10.1" }
|
||||
env_logger = { version = "0.11.0" }
|
||||
fern = { version = "0.6.1" }
|
||||
filetime = { version = "0.2.23" }
|
||||
fs-err = { version = "2.11.0" }
|
||||
@@ -46,7 +46,7 @@ imperative = { version = "1.0.4" }
|
||||
indicatif = { version = "0.17.8" }
|
||||
indoc = { version = "2.0.4" }
|
||||
insta = { version = "1.35.1", feature = ["filters", "glob"] }
|
||||
insta-cmd = { version = "0.4.0" }
|
||||
insta-cmd = { version = "0.5.0" }
|
||||
is-macro = { version = "0.3.5" }
|
||||
is-wsl = { version = "0.4.0" }
|
||||
itertools = { version = "0.12.1" }
|
||||
@@ -65,16 +65,17 @@ natord = { version = "1.0.9" }
|
||||
notify = { version = "6.1.1" }
|
||||
num_cpus = { version = "1.16.0" }
|
||||
once_cell = { version = "1.19.0" }
|
||||
owo-colors = { version = "4.0.0" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { version = "0.4.0", features = ["serde"] }
|
||||
pep440_rs = { version = "0.5.0", features = ["serde"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
proc-macro2 = { version = "1.0.79" }
|
||||
pyproject-toml = { version = "0.9.0" }
|
||||
quick-junit = { version = "0.3.5" }
|
||||
quote = { version = "1.0.23" }
|
||||
rand = { version = "0.8.5" }
|
||||
rayon = { version = "1.8.1" }
|
||||
rayon = { version = "1.10.0" }
|
||||
regex = { version = "1.10.2" }
|
||||
result-like = { version = "0.5.0" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
@@ -88,11 +89,11 @@ serde_with = { version = "3.6.0", default-features = false, features = ["macros"
|
||||
shellexpand = { version = "3.0.0" }
|
||||
shlex = { version = "1.3.0" }
|
||||
similar = { version = "2.4.0", features = ["inline"] }
|
||||
smallvec = { version = "1.13.1" }
|
||||
smallvec = { version = "1.13.2" }
|
||||
static_assertions = "1.1.0"
|
||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.3" }
|
||||
syn = { version = "2.0.51" }
|
||||
strum = { version = "0.26.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.26.0" }
|
||||
syn = { version = "2.0.55" }
|
||||
tempfile = { version = "3.9.0" }
|
||||
test-case = { version = "3.3.1" }
|
||||
thiserror = { version = "1.0.58" }
|
||||
@@ -101,7 +102,7 @@ toml = { version = "0.8.11" }
|
||||
tracing = { version = "0.1.40" }
|
||||
tracing-indicatif = { version = "0.3.6" }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
tracing-tree = { version = "0.2.4" }
|
||||
tracing-tree = { version = "0.3.0" }
|
||||
typed-arena = { version = "2.0.2" }
|
||||
unic-ucd-category = { version = "0.9" }
|
||||
unicode-ident = { version = "1.0.12" }
|
||||
|
||||
@@ -151,7 +151,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.3.4
|
||||
rev: v0.3.5
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -25,6 +25,7 @@ ruff_source_file = { path = "../ruff_source_file" }
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
ruff_workspace = { path = "../ruff_workspace" }
|
||||
|
||||
anstream = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
argfile = { workspace = true }
|
||||
bincode = { workspace = true }
|
||||
@@ -34,7 +35,6 @@ chrono = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive", "env", "wrap_help"] }
|
||||
clap_complete_command = { workspace = true }
|
||||
clearscreen = { workspace = true }
|
||||
colored = { workspace = true }
|
||||
filetime = { workspace = true }
|
||||
ignore = { workspace = true }
|
||||
is-macro = { workspace = true }
|
||||
@@ -42,6 +42,7 @@ itertools = { workspace = true }
|
||||
log = { workspace = true }
|
||||
notify = { workspace = true }
|
||||
num_cpus = { workspace = true }
|
||||
owo-colors = { workspace = true }
|
||||
path-absolutize = { workspace = true, features = ["once_cell_cache"] }
|
||||
rayon = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
@@ -62,8 +63,6 @@ wild = { workspace = true }
|
||||
[dev-dependencies]
|
||||
# Enable test rules during development
|
||||
ruff_linter = { path = "../ruff_linter", features = ["clap", "test-rules"] }
|
||||
# Avoid writing colored snapshots when running tests from the terminal
|
||||
colored = { workspace = true, features = ["no-color"] }
|
||||
insta = { workspace = true, features = ["filters", "json"] }
|
||||
insta-cmd = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::sync::Arc;
|
||||
use anyhow::bail;
|
||||
use clap::builder::{TypedValueParser, ValueParserFactory};
|
||||
use clap::{command, Parser};
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
use path_absolutize::path_dedot;
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
@@ -1053,7 +1053,6 @@ pub enum FormatRangeParseError {
|
||||
|
||||
impl std::fmt::Display for FormatRangeParseError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let tip = " tip:".bold().green();
|
||||
match self {
|
||||
FormatRangeParseError::StartGreaterThanEnd(start, end) => {
|
||||
write!(
|
||||
@@ -1062,7 +1061,8 @@ impl std::fmt::Display for FormatRangeParseError {
|
||||
start_invalid=start.to_string().bold().yellow(),
|
||||
end_invalid=end.to_string().bold().yellow(),
|
||||
start=start.to_string().green().bold(),
|
||||
end=end.to_string().green().bold()
|
||||
end=end.to_string().green().bold(),
|
||||
tip=" tip:".bold().green()
|
||||
)
|
||||
}
|
||||
FormatRangeParseError::InvalidStart(inner) => inner.write(f, true),
|
||||
@@ -1148,7 +1148,8 @@ pub enum LineColumnParseError {
|
||||
|
||||
impl LineColumnParseError {
|
||||
fn write(&self, f: &mut std::fmt::Formatter, start_range: bool) -> std::fmt::Result {
|
||||
let tip = "tip:".bold().green();
|
||||
let tip = "tip:".bold();
|
||||
let tip = tip.green();
|
||||
|
||||
let range = if start_range { "start" } else { "end" };
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use ignore::Error;
|
||||
use log::{debug, error, warn};
|
||||
use owo_colors::OwoColorize;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rayon::prelude::*;
|
||||
use rustc_hash::FxHashMap;
|
||||
@@ -226,6 +226,7 @@ mod test {
|
||||
use rustc_hash::FxHashMap;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use ruff_linter::colors;
|
||||
use ruff_linter::message::{Emitter, EmitterContext, TextEmitter};
|
||||
use ruff_linter::registry::Rule;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
@@ -280,7 +281,7 @@ mod test {
|
||||
UnsafeFixes::Enabled,
|
||||
)
|
||||
.unwrap();
|
||||
let mut output = Vec::new();
|
||||
let mut output = colors::none(Vec::new());
|
||||
|
||||
TextEmitter::default()
|
||||
.with_show_fix_status(true)
|
||||
@@ -291,7 +292,7 @@ mod test {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let messages = String::from_utf8(output).unwrap();
|
||||
let messages = String::from_utf8(output.into_inner()).unwrap();
|
||||
|
||||
insta::with_settings!({
|
||||
omit_expression => true,
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fs::remove_dir_all;
|
||||
use std::io::{self, BufWriter, Write};
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
use path_absolutize::path_dedot;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use log::{error, warn};
|
||||
use owo_colors::OwoColorize;
|
||||
use rayon::iter::Either::{Left, Right};
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use rustc_hash::FxHashSet;
|
||||
@@ -16,6 +16,7 @@ use thiserror::Error;
|
||||
use tracing::debug;
|
||||
|
||||
use ruff_diagnostics::SourceMap;
|
||||
use ruff_linter::colors;
|
||||
use ruff_linter::fs;
|
||||
use ruff_linter::logging::{DisplayParseError, LogLevel};
|
||||
use ruff_linter::registry::Rule;
|
||||
@@ -189,10 +190,10 @@ pub(crate) fn format(
|
||||
match mode {
|
||||
FormatMode::Write => {}
|
||||
FormatMode::Check => {
|
||||
results.write_changed(&mut stdout().lock())?;
|
||||
results.write_changed(&mut colors::auto(stdout()).lock())?;
|
||||
}
|
||||
FormatMode::Diff => {
|
||||
results.write_diff(&mut stdout().lock())?;
|
||||
results.write_diff(&mut colors::auto(stdout()).lock())?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,9 +201,9 @@ pub(crate) fn format(
|
||||
if config_arguments.log_level >= LogLevel::Default {
|
||||
if mode.is_diff() {
|
||||
// Allow piping the diff to e.g. a file by writing the summary to stderr
|
||||
results.write_summary(&mut stderr().lock())?;
|
||||
results.write_summary(&mut colors::auto(stderr()).lock())?;
|
||||
} else {
|
||||
results.write_summary(&mut stdout().lock())?;
|
||||
results.write_summary(&mut colors::auto(stdout()).lock())?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::path::Path;
|
||||
use anyhow::Result;
|
||||
use log::error;
|
||||
|
||||
use ruff_linter::colors;
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, Resolver};
|
||||
@@ -111,7 +112,7 @@ fn format_source_code(
|
||||
match &formatted {
|
||||
FormattedSource::Formatted(formatted) => match mode {
|
||||
FormatMode::Write => {
|
||||
let mut writer = stdout().lock();
|
||||
let mut writer = colors::auto(stdout()).lock();
|
||||
formatted
|
||||
.write(&mut writer)
|
||||
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
|
||||
@@ -120,7 +121,7 @@ fn format_source_code(
|
||||
FormatMode::Diff => {
|
||||
use std::io::Write;
|
||||
write!(
|
||||
&mut stdout().lock(),
|
||||
&mut colors::auto(stdout()).lock(),
|
||||
"{}",
|
||||
source_kind.diff(formatted, path).unwrap()
|
||||
)
|
||||
@@ -130,7 +131,7 @@ fn format_source_code(
|
||||
FormattedSource::Unchanged => {
|
||||
// Write to stdout regardless of whether the source was formatted
|
||||
if mode.is_write() {
|
||||
let mut writer = stdout().lock();
|
||||
let mut writer = colors::auto(stdout()).lock();
|
||||
source_kind
|
||||
.write(&mut writer)
|
||||
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
|
||||
|
||||
@@ -8,11 +8,12 @@ use std::ops::{Add, AddAssign};
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use colored::Colorize;
|
||||
use log::{debug, error, warn};
|
||||
use owo_colors::OwoColorize;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_linter::colors;
|
||||
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult, ParseSource};
|
||||
use ruff_linter::logging::DisplayParseError;
|
||||
use ruff_linter::message::Message;
|
||||
@@ -444,7 +445,7 @@ pub(crate) fn lint_stdin(
|
||||
// But only write a diff if it's non-empty.
|
||||
if !fixed.is_empty() {
|
||||
write!(
|
||||
&mut io::stdout().lock(),
|
||||
&mut colors::auto(io::stdout()).lock(),
|
||||
"{}",
|
||||
source_kind.diff(&transformed, path).unwrap()
|
||||
)?;
|
||||
|
||||
@@ -10,10 +10,11 @@ use std::sync::mpsc::channel;
|
||||
use anyhow::Result;
|
||||
use args::{GlobalConfigArgs, ServerCommand};
|
||||
use clap::CommandFactory;
|
||||
use colored::Colorize;
|
||||
use log::warn;
|
||||
use notify::{recommended_watcher, RecursiveMode, Watcher};
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
use ruff_linter::colors;
|
||||
use ruff_linter::logging::{set_up_logging, LogLevel};
|
||||
use ruff_linter::settings::flags::FixMode;
|
||||
use ruff_linter::settings::types::SerializationFormat;
|
||||
@@ -145,10 +146,6 @@ pub fn run(
|
||||
}));
|
||||
}
|
||||
|
||||
// Enabled ANSI colors on Windows 10.
|
||||
#[cfg(windows)]
|
||||
assert!(colored::control::set_virtual_terminal(true).is_ok());
|
||||
|
||||
set_up_logging(global_options.log_level())?;
|
||||
|
||||
if let Some(deprecated_alias_warning) = deprecated_alias_warning {
|
||||
@@ -225,13 +222,12 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
||||
|
||||
let mut writer: Box<dyn Write> = match cli.output_file {
|
||||
Some(path) if !cli.watch => {
|
||||
colored::control::set_override(false);
|
||||
let file = File::create(path)?;
|
||||
Box::new(BufWriter::new(file))
|
||||
Box::new(BufWriter::new(colors::none(file)))
|
||||
}
|
||||
_ => Box::new(BufWriter::new(io::stdout())),
|
||||
_ => Box::new(BufWriter::new(colors::auto(io::stdout()))),
|
||||
};
|
||||
let stderr_writer = Box::new(BufWriter::new(io::stderr()));
|
||||
let stderr_writer = Box::new(BufWriter::new(colors::auto(io::stderr())));
|
||||
|
||||
let is_stdin = is_stdin(&cli.files, cli.stdin_filename.as_deref());
|
||||
let files = resolve_default_files(cli.files, is_stdin);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::process::ExitCode;
|
||||
|
||||
use anstream::eprintln;
|
||||
use clap::{Parser, Subcommand};
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
use ruff::args::{Args, Command};
|
||||
use ruff::{run, ExitStatus};
|
||||
|
||||
@@ -5,8 +5,8 @@ use std::io::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
use itertools::{iterate, Itertools};
|
||||
use owo_colors::OwoColorize;
|
||||
use serde::Serialize;
|
||||
|
||||
use ruff_linter::fs::relativize_path;
|
||||
|
||||
@@ -70,7 +70,7 @@ pub(crate) fn version() -> VersionInfo {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::{assert_display_snapshot, assert_json_snapshot};
|
||||
use insta::{assert_json_snapshot, assert_snapshot};
|
||||
|
||||
use super::{CommitInfo, VersionInfo};
|
||||
|
||||
@@ -80,7 +80,7 @@ mod tests {
|
||||
version: "0.0.0".to_string(),
|
||||
commit_info: None,
|
||||
};
|
||||
assert_display_snapshot!(version);
|
||||
assert_snapshot!(version);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -95,7 +95,7 @@ mod tests {
|
||||
commits_since_last_tag: 0,
|
||||
}),
|
||||
};
|
||||
assert_display_snapshot!(version);
|
||||
assert_snapshot!(version);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -110,7 +110,7 @@ mod tests {
|
||||
commits_since_last_tag: 24,
|
||||
}),
|
||||
};
|
||||
assert_display_snapshot!(version);
|
||||
assert_snapshot!(version);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -231,7 +231,7 @@ linter.flake8_bandit.check_typed_exception = false
|
||||
linter.flake8_bugbear.extend_immutable_calls = []
|
||||
linter.flake8_builtins.builtins_ignorelist = []
|
||||
linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false
|
||||
linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}(-\d{4})*
|
||||
linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|,\s)\d{4})*
|
||||
linter.flake8_copyright.author = none
|
||||
linter.flake8_copyright.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
|
||||
@@ -25,11 +25,6 @@ pub trait Violation: Debug + PartialEq + Eq {
|
||||
/// The message used to describe the violation.
|
||||
fn message(&self) -> String;
|
||||
|
||||
/// The explanation used in documentation and elsewhere.
|
||||
fn explanation() -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
// TODO(micha): Move `fix_title` to `Fix`, add new `advice` method that is shown as an advice.
|
||||
// Change the `Diagnostic` renderer to show the advice, and render the fix message after the `Suggested fix: <here>`
|
||||
|
||||
@@ -50,11 +45,6 @@ pub trait AlwaysFixableViolation: Debug + PartialEq + Eq {
|
||||
/// The message used to describe the violation.
|
||||
fn message(&self) -> String;
|
||||
|
||||
/// The explanation used in documentation and elsewhere.
|
||||
fn explanation() -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
/// The title displayed for the available fix.
|
||||
fn fix_title(&self) -> String;
|
||||
|
||||
@@ -71,10 +61,6 @@ impl<V: AlwaysFixableViolation> Violation for V {
|
||||
<Self as AlwaysFixableViolation>::message(self)
|
||||
}
|
||||
|
||||
fn explanation() -> Option<&'static str> {
|
||||
<Self as AlwaysFixableViolation>::explanation()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some(<Self as AlwaysFixableViolation>::fix_title(self))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -30,11 +30,11 @@ ruff_text_size = { path = "../ruff_text_size" }
|
||||
|
||||
aho-corasick = { workspace = true }
|
||||
annotate-snippets = { workspace = true, features = ["color"] }
|
||||
anstream = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive", "string"], optional = true }
|
||||
colored = { workspace = true }
|
||||
fern = { workspace = true }
|
||||
glob = { workspace = true }
|
||||
globset = { workspace = true }
|
||||
@@ -47,6 +47,7 @@ log = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
natord = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
owo-colors = { workspace = true }
|
||||
path-absolutize = { workspace = true, features = [
|
||||
"once_cell_cache",
|
||||
"use_unix_paths_on_wasm",
|
||||
@@ -75,8 +76,6 @@ url = { workspace = true }
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true }
|
||||
test-case = { workspace = true }
|
||||
# Disable colored output in tests
|
||||
colored = { workspace = true, features = ["no-color"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -31,8 +31,7 @@ def function(
|
||||
kwonly_nonboolvalued_boolhint: bool = 1,
|
||||
kwonly_nonboolvalued_boolstrhint: "bool" = 1,
|
||||
**kw,
|
||||
):
|
||||
...
|
||||
): ...
|
||||
|
||||
|
||||
def used(do):
|
||||
@@ -131,4 +130,27 @@ class Fit:
|
||||
def __post_init__(self, force: bool) -> None:
|
||||
print(force)
|
||||
|
||||
|
||||
Fit(force=True)
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/10356
|
||||
from django.db.models import Case, Q, Value, When
|
||||
|
||||
|
||||
qs.annotate(
|
||||
is_foo_or_bar=Case(
|
||||
When(Q(is_foo=True) | Q(is_bar=True)),
|
||||
then=Value(True),
|
||||
),
|
||||
default=Value(False),
|
||||
)
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/10485
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
foo: bool = Field(True, exclude=True)
|
||||
|
||||
@@ -174,6 +174,49 @@ for (_key1, _key2), (_value1, _value2) in groupby(
|
||||
collect_shop_items("Jane", group[1])
|
||||
collect_shop_items("Joe", group[1])
|
||||
|
||||
# Shouldn't trigger the warning when there is a continue, break statement.
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
continue
|
||||
elif _section == "frozen items":
|
||||
collect_shop_items(shopper, section_items)
|
||||
break
|
||||
collect_shop_items(shopper, section_items)
|
||||
|
||||
# Shouldn't trigger the warning when there is a return statement.
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
elif _section == "frozen items":
|
||||
return section_items
|
||||
collect_shop_items(shopper, section_items)
|
||||
|
||||
# Should trigger the warning for duplicate access, even if is a return statement after.
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
|
||||
# Should trigger the warning for duplicate access, even if is a return in another branch.
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
elif _section == "frozen items":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items)
|
||||
|
||||
# Should trigger, since only one branch has a return statement.
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
elif _section == "frozen items":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
|
||||
# Let's redefine the `groupby` function to make sure we pick up the correct one.
|
||||
# NOTE: This should always be at the end of the file.
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
x = set(x for x in range(3))
|
||||
x = set(x for x in range(3))
|
||||
y = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
_ = "{}".format(set(a if a < 6 else 0 for a in range(3)))
|
||||
print(f"Hello {set(a for a in range(3))} World")
|
||||
|
||||
# Cannot conbime with C416. Should use set comprehension here.
|
||||
even_nums = set(2 * x for x in range(3))
|
||||
odd_nums = set(
|
||||
2 * x + 1 for x in range(3)
|
||||
)
|
||||
small_nums = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
|
||||
def f(x):
|
||||
return x
|
||||
|
||||
|
||||
print(f'Hello {set(a for a in "abc")} World')
|
||||
print(f"Hello {set(a for a in 'abc')} World")
|
||||
print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
print(f"Hello { set(f(a) for a in 'abc') } World")
|
||||
|
||||
|
||||
# Short-circuit case, combine with C416 and should produce x = set(range(3))
|
||||
x = set(x for x in range(3))
|
||||
x = set(
|
||||
x for x in range(3)
|
||||
)
|
||||
print(f"Hello {set(a for a in range(3))} World")
|
||||
print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
|
||||
# The fix generated for this diagnostic is incorrect, as we add additional space
|
||||
# around the set comprehension.
|
||||
print(f"{ {set(a for a in 'abc')} }")
|
||||
|
||||
# Not built-in set.
|
||||
def set(*args, **kwargs):
|
||||
return None
|
||||
|
||||
set(2 * x for x in range(3))
|
||||
set(x for x in range(3))
|
||||
|
||||
@@ -13,6 +13,10 @@ all(x.id for x in bar)
|
||||
all(x.id for x in bar)
|
||||
any(x.id for x in bar)
|
||||
all((x.id for x in bar))
|
||||
# we don't lint on these in stable yet
|
||||
sum([x.val for x in bar])
|
||||
min([x.val for x in bar])
|
||||
max([x.val for x in bar])
|
||||
|
||||
|
||||
async def f() -> bool:
|
||||
|
||||
8
crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419_1.py
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419_1.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
sum([x.val for x in bar])
|
||||
min([x.val for x in bar])
|
||||
max([x.val for x in bar])
|
||||
|
||||
# Ok
|
||||
sum(x.val for x in bar)
|
||||
min(x.val for x in bar)
|
||||
max(x.val for x in bar)
|
||||
3
crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419_2.py
vendored
Normal file
3
crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419_2.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# no lint if shadowed
|
||||
def all(x): pass
|
||||
all([x.id for x in bar])
|
||||
@@ -33,3 +33,9 @@ from datetime import datetime
|
||||
|
||||
# no replace orastimezone unqualified
|
||||
datetime.strptime("something", "something")
|
||||
|
||||
# F-strings
|
||||
datetime.strptime("something", f"%Y-%m-%dT%H:%M:%S{('.%f' if millis else '')}%z")
|
||||
datetime.strptime("something", f"%Y-%m-%d %H:%M:%S%z")
|
||||
# F-string is implicitly concatenated to another string
|
||||
datetime.strptime("something", f"%Y-%m-%dT%H:%M:%S{('.%f' if millis else '')}" "%z")
|
||||
|
||||
@@ -21,6 +21,7 @@ def unconventional_aliases():
|
||||
import tkinter as tkr
|
||||
import networkx as nxy
|
||||
|
||||
|
||||
def conventional_aliases():
|
||||
import altair as alt
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
10
crates/ruff_linter/resources/test/fixtures/flake8_import_conventions/same_name.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/flake8_import_conventions/same_name.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
def no_alias():
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def conventional_alias():
|
||||
from django.conf import settings as settings
|
||||
|
||||
|
||||
def unconventional_alias():
|
||||
from django.conf import settings as s
|
||||
14
crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI025_2.py
vendored
Normal file
14
crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI025_2.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Tests to ensure we correctly rename references inside `__all__`"""
|
||||
|
||||
from collections.abc import Set
|
||||
|
||||
__all__ = ["Set"]
|
||||
|
||||
if True:
|
||||
__all__ += [r'''Set''']
|
||||
|
||||
if 1:
|
||||
__all__ += ["S" "e" "t"]
|
||||
|
||||
if not False:
|
||||
__all__ += ["Se" 't']
|
||||
14
crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI025_2.pyi
vendored
Normal file
14
crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI025_2.pyi
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Tests to ensure we correctly rename references inside `__all__`"""
|
||||
|
||||
from collections.abc import Set
|
||||
|
||||
__all__ = ["Set"]
|
||||
|
||||
if True:
|
||||
__all__ += [r'''Set''']
|
||||
|
||||
if 1:
|
||||
__all__ += ["S" "e" "t"]
|
||||
|
||||
if not False:
|
||||
__all__ += ["Se" 't']
|
||||
6
crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI025_3.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI025_3.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
"""
|
||||
Tests for PYI025 where the import is marked as re-exported
|
||||
through usage of a "redundant" `import Set as Set` alias
|
||||
"""
|
||||
|
||||
from collections.abc import Set as Set # PYI025 triggered but fix is not marked as safe
|
||||
6
crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI025_3.pyi
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI025_3.pyi
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
"""
|
||||
Tests for PYI025 where the import is marked as re-exported
|
||||
through usage of a "redundant" `import Set as Set` alias
|
||||
"""
|
||||
|
||||
from collections.abc import Set as Set # PYI025 triggered but fix is not marked as safe
|
||||
@@ -51,3 +51,8 @@ def test_error_parentheses_trailing_comma(x):
|
||||
@pytest.mark.parametrize("x", [1, 2])
|
||||
def test_ok(x):
|
||||
...
|
||||
|
||||
|
||||
@pytest.mark.parametrize('data, spec', [(1.0, 1.0), (1.0, 1.0)])
|
||||
def test_numbers(data, spec):
|
||||
...
|
||||
|
||||
7
crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_all.py
vendored
Normal file
7
crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_all.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
"""This is a docstring."""
|
||||
|
||||
this_is_an_inline_string = "double quote string"
|
||||
|
||||
this_is_a_multiline_string = """
|
||||
double quote string
|
||||
"""
|
||||
@@ -2,3 +2,6 @@ this_should_be_linted = 'single quote string'
|
||||
this_should_be_linted = u'double quote string'
|
||||
this_should_be_linted = f'double quote string'
|
||||
this_should_be_linted = f'double {"quote"} string'
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/10546
|
||||
x: "Literal['foo', 'bar']"
|
||||
|
||||
@@ -406,3 +406,18 @@ def foo():
|
||||
with contextlib.suppress(Exception):
|
||||
y = 2
|
||||
return y
|
||||
|
||||
|
||||
# See: https://github.com/astral-sh/ruff/issues/10732
|
||||
def func(a: dict[str, int]) -> list[dict[str, int]]:
|
||||
services: list[dict[str, int]]
|
||||
if "services" in a:
|
||||
services = a["services"]
|
||||
return services
|
||||
|
||||
|
||||
# See: https://github.com/astral-sh/ruff/issues/10732
|
||||
def func(a: dict[str, int]) -> list[dict[str, int]]:
|
||||
if "services" in a:
|
||||
services = a["services"]
|
||||
return services
|
||||
|
||||
@@ -3,3 +3,5 @@ import ruff
|
||||
import leading_prefix
|
||||
import os
|
||||
from . import leading_prefix
|
||||
from .. import trailing_prefix
|
||||
from ruff import check
|
||||
|
||||
6
crates/ruff_linter/resources/test/fixtures/isort/unicode.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/isort/unicode.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
from astropy.constants import hbar as ℏ
|
||||
from numpy import pi as π
|
||||
import numpy as ℂℇℊℋℌℍℎℐℑℒℓℕℤΩℨKÅℬℭℯℰℱℹℴ
|
||||
import numpy as CƐgHHHhIILlNZΩZKÅBCeEFio
|
||||
|
||||
h = 2 * π * ℏ
|
||||
@@ -104,3 +104,5 @@ def func():
|
||||
np.unicode_("asf")
|
||||
|
||||
np.who()
|
||||
|
||||
np.row_stack(([1,2], [3,4]))
|
||||
|
||||
@@ -21,3 +21,10 @@ class D(TypedDict):
|
||||
mixedCase: bool
|
||||
_mixedCase: list
|
||||
mixed_Case: set
|
||||
|
||||
class E(D):
|
||||
lower: int
|
||||
CONSTANT: str
|
||||
mixedCase: bool
|
||||
_mixedCase: list
|
||||
mixed_Case: set
|
||||
|
||||
228
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.ipynb
vendored
Normal file
228
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.ipynb
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "palRUQyD-U6u"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"some_string = \"123123\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "UWdDLRyf-Zz0"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"some_computation = 1 + 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "YreT1sTr-c32"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"some_computation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "V48ppml7-h0f"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def fn():\n",
|
||||
" print(\"Hey!\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "cscw_8Xv-lYQ"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fn()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# E301\n",
|
||||
"class Class:\n",
|
||||
" \"\"\"Class for minimal repo.\"\"\"\n",
|
||||
"\n",
|
||||
" def method(cls) -> None:\n",
|
||||
" pass\n",
|
||||
" @classmethod\n",
|
||||
" def cls_method(cls) -> None:\n",
|
||||
" pass\n",
|
||||
"# end"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# E302\n",
|
||||
"def a():\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"def b():\n",
|
||||
" pass\n",
|
||||
"# end"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# E303\n",
|
||||
"def fn():\n",
|
||||
" _ = None\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" # arbitrary comment\n",
|
||||
"\n",
|
||||
" def inner(): # E306 not expected (pycodestyle detects E306)\n",
|
||||
" pass\n",
|
||||
"# end"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# E303"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def fn():\n",
|
||||
"\tpass\n",
|
||||
"# end"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# E304\n",
|
||||
"@decorator\n",
|
||||
"\n",
|
||||
"def function():\n",
|
||||
" pass\n",
|
||||
"# end"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# E305:7:1\n",
|
||||
"def fn():\n",
|
||||
" print()\n",
|
||||
"\n",
|
||||
" # comment\n",
|
||||
"\n",
|
||||
" # another comment\n",
|
||||
"fn()\n",
|
||||
"# end"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# E306:3:5\n",
|
||||
"def a():\n",
|
||||
" x = 1\n",
|
||||
" def b():\n",
|
||||
" pass\n",
|
||||
"# end"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Ok\n",
|
||||
"def function1():\n",
|
||||
"\tpass\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"\n",
|
||||
"def function2():\n",
|
||||
"\tpass\n",
|
||||
"# end"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"provenance": []
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.7"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
@@ -87,6 +87,37 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a51463ee-091c-44b4-9069-c03bf7e3bf83",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%time\n",
|
||||
"import pathlib"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0ddc937e-6c19-475f-b108-9405aa1af4f1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "285041d2-a76c-4ff3-8ff2-0131bbf66016",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%time\n",
|
||||
"%%time\n",
|
||||
"import pathlib"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -60,7 +60,7 @@ class Scope:
|
||||
class Scope:
|
||||
from typing import Callable
|
||||
|
||||
# E731
|
||||
# OK
|
||||
f: Callable[[int], int] = lambda x: 2 * x
|
||||
|
||||
|
||||
@@ -147,3 +147,12 @@ def scope():
|
||||
f = lambda: (
|
||||
i := 1,
|
||||
)
|
||||
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
|
||||
@dataclass
|
||||
class FilterDataclass:
|
||||
# OK
|
||||
filter: Callable[[str], bool] = lambda _: True
|
||||
|
||||
@@ -33,3 +33,16 @@ class MyClass:
|
||||
baz: MyClass
|
||||
eggs = baz # Still invalid even when `__future__.annotations` are enabled
|
||||
eggs = "baz" # always okay
|
||||
|
||||
# Forward references:
|
||||
MaybeDStr: TypeAlias = Optional[DStr] # Still invalid even when `__future__.annotations` are enabled
|
||||
MaybeDStr2: TypeAlias = Optional["DStr"] # always okay
|
||||
DStr: TypeAlias = Union[D, str] # Still invalid even when `__future__.annotations` are enabled
|
||||
DStr2: TypeAlias = Union["D", str] # always okay
|
||||
|
||||
class D: ...
|
||||
|
||||
# More circular references
|
||||
class Leaf: ...
|
||||
class Tree(list[Tree | Leaf]): ... # Still invalid even when `__future__.annotations` are enabled
|
||||
class Tree2(list["Tree | Leaf"]): ... # always okay
|
||||
|
||||
@@ -5,6 +5,7 @@ class Person: # [eq-without-hash]
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Person) and other.name == self.name
|
||||
|
||||
|
||||
# OK
|
||||
class Language:
|
||||
def __init__(self):
|
||||
@@ -16,8 +17,24 @@ class Language:
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
|
||||
|
||||
class MyClass:
|
||||
def __eq__(self, other):
|
||||
return True
|
||||
|
||||
__hash__ = None
|
||||
|
||||
|
||||
class SingleClass:
|
||||
def __eq__(self, other):
|
||||
return True
|
||||
|
||||
def __hash__(self):
|
||||
return 7
|
||||
|
||||
|
||||
class ChildClass(SingleClass):
|
||||
def __eq__(self, other):
|
||||
return True
|
||||
|
||||
__hash__ = SingleClass.__hash__
|
||||
|
||||
60
crates/ruff_linter/resources/test/fixtures/pylint/modified_iterating_set.py
vendored
Normal file
60
crates/ruff_linter/resources/test/fixtures/pylint/modified_iterating_set.py
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
# Errors
|
||||
|
||||
nums = {1, 2, 3}
|
||||
for num in nums:
|
||||
nums.add(num + 1)
|
||||
|
||||
animals = {"dog", "cat", "cow"}
|
||||
for animal in animals:
|
||||
animals.pop("cow")
|
||||
|
||||
fruits = {"apple", "orange", "grape"}
|
||||
for fruit in fruits:
|
||||
fruits.clear()
|
||||
|
||||
planets = {"mercury", "venus", "earth"}
|
||||
for planet in planets:
|
||||
planets.discard("mercury")
|
||||
|
||||
colors = {"red", "green", "blue"}
|
||||
for color in colors:
|
||||
colors.remove("red")
|
||||
|
||||
odds = {1, 3, 5}
|
||||
for num in odds:
|
||||
if num > 1:
|
||||
odds.add(num + 1)
|
||||
|
||||
# OK
|
||||
|
||||
nums = {1, 2, 3}
|
||||
for num in nums.copy():
|
||||
nums.add(nums + 3)
|
||||
|
||||
animals = {"dog", "cat", "cow"}
|
||||
for animal in animals:
|
||||
print(animals - {animal})
|
||||
|
||||
fruits = {"apple", "orange", "grape"}
|
||||
temp_fruits = set()
|
||||
for fruit in fruits:
|
||||
temp_fruits.add(fruit)
|
||||
temp_fruits.remove(fruit)
|
||||
temp_fruits.clear(fruit)
|
||||
|
||||
colors = {"red", "green", "blue"}
|
||||
|
||||
|
||||
def add_colors():
|
||||
colors = {"cyan", "magenta", "yellow"}
|
||||
for color in colors:
|
||||
|
||||
def add_color():
|
||||
global colors
|
||||
colors.add(color)
|
||||
|
||||
add_color()
|
||||
|
||||
|
||||
add_colors()
|
||||
print(colors)
|
||||
@@ -20,7 +20,7 @@ class Board:
|
||||
def place(self, position):
|
||||
pass
|
||||
|
||||
@singledispatch
|
||||
@singledispatch # [singledispatch-method]
|
||||
@staticmethod
|
||||
def do(position):
|
||||
pass
|
||||
|
||||
@@ -17,7 +17,7 @@ class Board:
|
||||
def move(self, position):
|
||||
pass
|
||||
|
||||
@singledispatchmethod # [singledispatchmethod-function]
|
||||
@singledispatchmethod # Ok
|
||||
@staticmethod
|
||||
def do(position):
|
||||
pass
|
||||
|
||||
61
crates/ruff_linter/resources/test/fixtures/refurb/FURB142.py
vendored
Normal file
61
crates/ruff_linter/resources/test/fixtures/refurb/FURB142.py
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
# Errors
|
||||
|
||||
s = set()
|
||||
|
||||
for x in [1, 2, 3]:
|
||||
s.add(x)
|
||||
|
||||
for x in {1, 2, 3}:
|
||||
s.add(x)
|
||||
|
||||
for x in (1, 2, 3):
|
||||
s.add(x)
|
||||
|
||||
for x in (1, 2, 3):
|
||||
s.discard(x)
|
||||
|
||||
for x in (1, 2, 3):
|
||||
s.add(x + 1)
|
||||
|
||||
for x, y in ((1, 2), (3, 4)):
|
||||
s.add((x, y))
|
||||
|
||||
num = 123
|
||||
|
||||
for x in (1, 2, 3):
|
||||
s.add(num)
|
||||
|
||||
for x in (1, 2, 3):
|
||||
s.add((num, x))
|
||||
|
||||
for x in (1, 2, 3):
|
||||
s.add(x + num)
|
||||
|
||||
# False negative
|
||||
|
||||
class C:
|
||||
s: set[int]
|
||||
|
||||
|
||||
c = C()
|
||||
for x in (1, 2, 3):
|
||||
c.s.add(x)
|
||||
|
||||
# Ok
|
||||
|
||||
s.update(x for x in (1, 2, 3))
|
||||
|
||||
for x in (1, 2, 3):
|
||||
s.add(x)
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
async def f(y):
|
||||
async for x in y:
|
||||
s.add(x)
|
||||
|
||||
|
||||
def g():
|
||||
for x in (set(),):
|
||||
x.add(x)
|
||||
17
crates/ruff_linter/resources/test/fixtures/refurb/FURB157.py
vendored
Normal file
17
crates/ruff_linter/resources/test/fixtures/refurb/FURB157.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import decimal
|
||||
from decimal import Decimal
|
||||
|
||||
# Errors
|
||||
Decimal("0")
|
||||
Decimal("-42")
|
||||
Decimal(float("Infinity"))
|
||||
Decimal(float("-Infinity"))
|
||||
Decimal(float("inf"))
|
||||
Decimal(float("-inf"))
|
||||
Decimal(float("nan"))
|
||||
decimal.Decimal("0")
|
||||
|
||||
# OK
|
||||
Decimal(0)
|
||||
Decimal("Infinity")
|
||||
decimal.Decimal(0)
|
||||
34
crates/ruff_linter/resources/test/fixtures/refurb/FURB164.py
vendored
Normal file
34
crates/ruff_linter/resources/test/fixtures/refurb/FURB164.py
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
from decimal import Decimal
|
||||
from fractions import Fraction
|
||||
import decimal
|
||||
import fractions
|
||||
|
||||
# Errors
|
||||
_ = Fraction.from_float(0.1)
|
||||
_ = Fraction.from_float(-0.5)
|
||||
_ = Fraction.from_float(5.0)
|
||||
_ = fractions.Fraction.from_float(4.2)
|
||||
_ = Fraction.from_decimal(Decimal("4.2"))
|
||||
_ = Fraction.from_decimal(Decimal("-4.2"))
|
||||
_ = Fraction.from_decimal(Decimal.from_float(4.2))
|
||||
_ = Decimal.from_float(0.1)
|
||||
_ = Decimal.from_float(-0.5)
|
||||
_ = Decimal.from_float(5.0)
|
||||
_ = decimal.Decimal.from_float(4.2)
|
||||
_ = Decimal.from_float(float("inf"))
|
||||
_ = Decimal.from_float(float("-inf"))
|
||||
_ = Decimal.from_float(float("Infinity"))
|
||||
_ = Decimal.from_float(float("-Infinity"))
|
||||
_ = Decimal.from_float(float("nan"))
|
||||
|
||||
# OK
|
||||
_ = Fraction(0.1)
|
||||
_ = Fraction(-0.5)
|
||||
_ = Fraction(5.0)
|
||||
_ = fractions.Fraction(4.2)
|
||||
_ = Fraction(Decimal("4.2"))
|
||||
_ = Fraction(Decimal("-4.2"))
|
||||
_ = Decimal(0.1)
|
||||
_ = Decimal(-0.5)
|
||||
_ = Decimal(5.0)
|
||||
_ = decimal.Decimal(4.2)
|
||||
29
crates/ruff_linter/resources/test/fixtures/refurb/FURB166.py
vendored
Normal file
29
crates/ruff_linter/resources/test/fixtures/refurb/FURB166.py
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# Errors
|
||||
|
||||
_ = int("0b1010"[2:], 2)
|
||||
_ = int("0o777"[2:], 8)
|
||||
_ = int("0xFFFF"[2:], 16)
|
||||
|
||||
b = "0b11"
|
||||
_ = int(b[2:], 2)
|
||||
|
||||
_ = int("0xFFFF"[2:], base=16)
|
||||
|
||||
_ = int(b"0xFFFF"[2:], 16)
|
||||
|
||||
|
||||
def get_str():
|
||||
return "0xFFF"
|
||||
|
||||
|
||||
_ = int(get_str()[2:], 16)
|
||||
|
||||
# OK
|
||||
|
||||
_ = int("0b1100", 0)
|
||||
_ = int("123", 3)
|
||||
_ = int("123", 10)
|
||||
_ = int("0b1010"[3:], 2)
|
||||
_ = int("0b1010"[:2], 2)
|
||||
_ = int("12345"[2:])
|
||||
_ = int("12345"[2:], xyz=1) # type: ignore
|
||||
@@ -100,3 +100,9 @@ def f():
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa
|
||||
|
||||
|
||||
def f():
|
||||
# Invalid - nonexistant error code with multibyte character
|
||||
d = 1 #
noqa: F841, E50
|
||||
e = 1 #
noqa: E50
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::str::raw_contents_range;
|
||||
use ruff_python_ast::{all::DunderAllName, str::raw_contents_range};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use ruff_python_semantic::{
|
||||
@@ -93,7 +93,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
}
|
||||
|
||||
// Compute visibility of all definitions.
|
||||
let exports: Option<Vec<&str>> = {
|
||||
let exports: Option<Vec<DunderAllName>> = {
|
||||
checker
|
||||
.semantic
|
||||
.global_scope()
|
||||
|
||||
@@ -223,14 +223,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::MixedCaseVariableInClassScope) {
|
||||
if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) =
|
||||
&checker.semantic.current_scope().kind
|
||||
if let ScopeKind::Class(class_def) = &checker.semantic.current_scope().kind
|
||||
{
|
||||
pep8_naming::rules::mixed_case_variable_in_class_scope(
|
||||
checker,
|
||||
expr,
|
||||
id,
|
||||
arguments.as_deref(),
|
||||
checker, expr, id, class_def,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -709,8 +705,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
args,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryComprehensionAnyAll) {
|
||||
flake8_comprehensions::rules::unnecessary_comprehension_any_all(
|
||||
if checker.enabled(Rule::UnnecessaryComprehensionInCall) {
|
||||
flake8_comprehensions::rules::unnecessary_comprehension_in_call(
|
||||
checker, expr, func, args, keywords,
|
||||
);
|
||||
}
|
||||
@@ -926,6 +922,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::RedundantLogBase) {
|
||||
refurb::rules::redundant_log_base(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::VerboseDecimalConstructor) {
|
||||
refurb::rules::verbose_decimal_constructor(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryFromFloat) {
|
||||
refurb::rules::unnecessary_from_float(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::QuadraticListSummation) {
|
||||
ruff::rules::quadratic_list_summation(checker, call);
|
||||
}
|
||||
@@ -971,6 +973,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryIterableAllocationForFirstElement) {
|
||||
ruff::rules::unnecessary_iterable_allocation_for_first_element(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::IntOnSlicedStr) {
|
||||
refurb::rules::int_on_sliced_str(checker, call);
|
||||
}
|
||||
}
|
||||
Expr::Dict(dict) => {
|
||||
if checker.any_enabled(&[
|
||||
|
||||
@@ -1293,6 +1293,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ManualDictComprehension) {
|
||||
perflint::rules::manual_dict_comprehension(checker, target, body);
|
||||
}
|
||||
if checker.enabled(Rule::ModifiedIteratingSet) {
|
||||
pylint::rules::modified_iterating_set(checker, for_stmt);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryListCast) {
|
||||
perflint::rules::unnecessary_list_cast(checker, iter, body);
|
||||
}
|
||||
@@ -1315,6 +1318,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::TryExceptInLoop) {
|
||||
perflint::rules::try_except_in_loop(checker, body);
|
||||
}
|
||||
if checker.enabled(Rule::ForLoopSetMutations) {
|
||||
refurb::rules::for_loop_set_mutations(checker, for_stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::Try(ast::StmtTry {
|
||||
|
||||
@@ -2,7 +2,7 @@ use ruff_python_ast::StringLike;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::{flake8_bandit, flake8_pyi, ruff};
|
||||
use crate::rules::{flake8_bandit, flake8_pyi, flake8_quotes, ruff};
|
||||
|
||||
/// Run lint rules over a [`StringLike`] syntax nodes.
|
||||
pub(crate) fn string_like(string_like: StringLike, checker: &mut Checker) {
|
||||
@@ -23,4 +23,14 @@ pub(crate) fn string_like(string_like: StringLike, checker: &mut Checker) {
|
||||
flake8_pyi::rules::string_or_bytes_too_long(checker, string_like);
|
||||
}
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::BadQuotesInlineString,
|
||||
Rule::BadQuotesMultilineString,
|
||||
Rule::BadQuotesDocstring,
|
||||
]) {
|
||||
flake8_quotes::rules::check_string_quotes(checker, string_like);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryEscapedQuote) {
|
||||
flake8_quotes::rules::unnecessary_escaped_quote(checker, string_like);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,9 @@ use std::path::Path;
|
||||
use itertools::Itertools;
|
||||
use log::debug;
|
||||
use ruff_python_ast::{
|
||||
self as ast, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext, Keyword,
|
||||
MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt, Suite, UnaryOp,
|
||||
self as ast, all::DunderAllName, Comprehension, ElifElseClause, ExceptHandler, Expr,
|
||||
ExprContext, Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt,
|
||||
Suite, UnaryOp,
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
@@ -364,6 +365,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING_BOUNDARY;
|
||||
self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY;
|
||||
if !(self.semantic.seen_import_boundary()
|
||||
|| stmt.is_ipy_escape_command_stmt()
|
||||
|| helpers::is_assignment_to_a_dunder(stmt)
|
||||
|| helpers::in_nested_block(self.semantic.current_statements())
|
||||
|| imports::is_matplotlib_activation(stmt, self.semantic())
|
||||
@@ -937,6 +939,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
&& !self.semantic.in_deferred_type_definition()
|
||||
&& self.semantic.in_type_definition()
|
||||
&& self.semantic.future_annotations()
|
||||
&& (self.semantic.in_annotation() || self.source_type.is_stub())
|
||||
{
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
|
||||
self.visit.string_type_definitions.push((
|
||||
@@ -2097,33 +2100,32 @@ impl<'a> Checker<'a> {
|
||||
fn visit_exports(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
|
||||
let exports: Vec<(&str, TextRange)> = self
|
||||
let exports: Vec<DunderAllName> = self
|
||||
.semantic
|
||||
.global_scope()
|
||||
.get_all("__all__")
|
||||
.map(|binding_id| &self.semantic.bindings[binding_id])
|
||||
.filter_map(|binding| match &binding.kind {
|
||||
BindingKind::Export(Export { names }) => {
|
||||
Some(names.iter().map(|name| (*name, binding.range())))
|
||||
}
|
||||
BindingKind::Export(Export { names }) => Some(names.iter().copied()),
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
for (name, range) in exports {
|
||||
for export in exports {
|
||||
let (name, range) = (export.name(), export.range());
|
||||
if let Some(binding_id) = self.semantic.global_scope().get(name) {
|
||||
self.semantic.flags |= SemanticModelFlags::DUNDER_ALL_DEFINITION;
|
||||
// Mark anything referenced in `__all__` as used.
|
||||
// TODO(charlie): `range` here should be the range of the name in `__all__`, not
|
||||
// the range of `__all__` itself.
|
||||
self.semantic
|
||||
.add_global_reference(binding_id, ExprContext::Load, range);
|
||||
self.semantic.flags -= SemanticModelFlags::DUNDER_ALL_DEFINITION;
|
||||
} else {
|
||||
if self.semantic.global_scope().uses_star_imports() {
|
||||
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {
|
||||
self.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedLocalWithImportStarUsage {
|
||||
name: (*name).to_string(),
|
||||
name: name.to_string(),
|
||||
},
|
||||
range,
|
||||
));
|
||||
@@ -2133,7 +2135,7 @@ impl<'a> Checker<'a> {
|
||||
if !self.path.ends_with("__init__.py") {
|
||||
self.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedExport {
|
||||
name: (*name).to_string(),
|
||||
name: name.to_string(),
|
||||
},
|
||||
range,
|
||||
));
|
||||
|
||||
@@ -41,7 +41,7 @@ pub(crate) fn check_tokens(
|
||||
Rule::BlankLinesAfterFunctionOrClass,
|
||||
Rule::BlankLinesBeforeNestedDefinition,
|
||||
]) {
|
||||
BlankLinesChecker::new(locator, stylist, settings, source_type)
|
||||
BlankLinesChecker::new(locator, stylist, settings, source_type, cell_offsets)
|
||||
.check_lines(tokens, &mut diagnostics);
|
||||
}
|
||||
|
||||
@@ -126,18 +126,6 @@ pub(crate) fn check_tokens(
|
||||
flake8_quotes::rules::avoidable_escaped_quote(&mut diagnostics, tokens, locator, settings);
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::UnnecessaryEscapedQuote) {
|
||||
flake8_quotes::rules::unnecessary_escaped_quote(&mut diagnostics, tokens, locator);
|
||||
}
|
||||
|
||||
if settings.rules.any_enabled(&[
|
||||
Rule::BadQuotesInlineString,
|
||||
Rule::BadQuotesMultilineString,
|
||||
Rule::BadQuotesDocstring,
|
||||
]) {
|
||||
flake8_quotes::rules::check_string_quotes(&mut diagnostics, tokens, locator, settings);
|
||||
}
|
||||
|
||||
if settings.rules.any_enabled(&[
|
||||
Rule::SingleLineImplicitStringConcatenation,
|
||||
Rule::MultiLineImplicitStringConcatenation,
|
||||
|
||||
@@ -265,6 +265,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "E2513") => (RuleGroup::Stable, rules::pylint::rules::InvalidCharacterEsc),
|
||||
(Pylint, "E2514") => (RuleGroup::Stable, rules::pylint::rules::InvalidCharacterNul),
|
||||
(Pylint, "E2515") => (RuleGroup::Stable, rules::pylint::rules::InvalidCharacterZeroWidthSpace),
|
||||
(Pylint, "E4703") => (RuleGroup::Preview, rules::pylint::rules::ModifiedIteratingSet),
|
||||
(Pylint, "R0124") => (RuleGroup::Stable, rules::pylint::rules::ComparisonWithItself),
|
||||
(Pylint, "R0133") => (RuleGroup::Stable, rules::pylint::rules::ComparisonOfConstant),
|
||||
(Pylint, "R0202") => (RuleGroup::Preview, rules::pylint::rules::NoClassmethodDecorator),
|
||||
@@ -397,7 +398,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Comprehensions, "16") => (RuleGroup::Stable, rules::flake8_comprehensions::rules::UnnecessaryComprehension),
|
||||
(Flake8Comprehensions, "17") => (RuleGroup::Stable, rules::flake8_comprehensions::rules::UnnecessaryMap),
|
||||
(Flake8Comprehensions, "18") => (RuleGroup::Stable, rules::flake8_comprehensions::rules::UnnecessaryLiteralWithinDictCall),
|
||||
(Flake8Comprehensions, "19") => (RuleGroup::Stable, rules::flake8_comprehensions::rules::UnnecessaryComprehensionAnyAll),
|
||||
(Flake8Comprehensions, "19") => (RuleGroup::Stable, rules::flake8_comprehensions::rules::UnnecessaryComprehensionInCall),
|
||||
|
||||
// flake8-debugger
|
||||
(Flake8Debugger, "0") => (RuleGroup::Stable, rules::flake8_debugger::rules::Debugger),
|
||||
@@ -1044,11 +1045,15 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Refurb, "132") => (RuleGroup::Nursery, rules::refurb::rules::CheckAndRemoveFromSet),
|
||||
(Refurb, "136") => (RuleGroup::Preview, rules::refurb::rules::IfExprMinMax),
|
||||
(Refurb, "140") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedStarmap),
|
||||
(Refurb, "142") => (RuleGroup::Preview, rules::refurb::rules::ForLoopSetMutations),
|
||||
(Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy),
|
||||
(Refurb, "148") => (RuleGroup::Preview, rules::refurb::rules::UnnecessaryEnumerate),
|
||||
(Refurb, "152") => (RuleGroup::Preview, rules::refurb::rules::MathConstant),
|
||||
(Refurb, "157") => (RuleGroup::Preview, rules::refurb::rules::VerboseDecimalConstructor),
|
||||
(Refurb, "161") => (RuleGroup::Preview, rules::refurb::rules::BitCount),
|
||||
(Refurb, "163") => (RuleGroup::Preview, rules::refurb::rules::RedundantLogBase),
|
||||
(Refurb, "164") => (RuleGroup::Preview, rules::refurb::rules::UnnecessaryFromFloat),
|
||||
(Refurb, "166") => (RuleGroup::Preview, rules::refurb::rules::IntOnSlicedStr),
|
||||
(Refurb, "167") => (RuleGroup::Preview, rules::refurb::rules::RegexFlagAlias),
|
||||
(Refurb, "168") => (RuleGroup::Preview, rules::refurb::rules::IsinstanceTypeNone),
|
||||
(Refurb, "169") => (RuleGroup::Preview, rules::refurb::rules::TypeNoneComparison),
|
||||
|
||||
19
crates/ruff_linter/src/colors.rs
Normal file
19
crates/ruff_linter/src/colors.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use anstream::stream::RawStream;
|
||||
use anstream::{AutoStream, ColorChoice};
|
||||
|
||||
pub fn none<S: RawStream>(stream: S) -> AutoStream<S> {
|
||||
AutoStream::new(stream, ColorChoice::Never)
|
||||
}
|
||||
|
||||
pub fn auto<S: RawStream>(stream: S) -> AutoStream<S> {
|
||||
let choice = choice(&stream);
|
||||
AutoStream::new(stream, choice)
|
||||
}
|
||||
|
||||
pub fn choice<S: RawStream>(stream: &S) -> ColorChoice {
|
||||
AutoStream::choice(stream)
|
||||
}
|
||||
|
||||
pub fn enabled<S: RawStream>(stream: &S) -> bool {
|
||||
choice(stream) != ColorChoice::Never
|
||||
}
|
||||
@@ -83,6 +83,7 @@ pub(crate) fn delete_comment(range: TextRange, locator: &Locator) -> Edit {
|
||||
}
|
||||
// Ex) `x = 1 # noqa`
|
||||
else if range.end() + trailing_space_len == line_range.end() {
|
||||
// Replace `x = 1 # noqa` with `x = 1`.
|
||||
Edit::deletion(range.start() - leading_space_len, line_range.end())
|
||||
}
|
||||
// Ex) `x = 1 # noqa # type: ignore`
|
||||
@@ -93,13 +94,15 @@ pub(crate) fn delete_comment(range: TextRange, locator: &Locator) -> Edit {
|
||||
))
|
||||
.starts_with('#')
|
||||
{
|
||||
// Replace `# noqa # type: ignore` with `# type: ignore`.
|
||||
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,
|
||||
// Replace `# noqa here` with `# here`.
|
||||
Edit::range_replacement(
|
||||
"# ".to_string(),
|
||||
TextRange::new(range.start(), range.end() + trailing_space_len),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
mod checkers;
|
||||
pub mod codes;
|
||||
pub mod colors;
|
||||
mod comments;
|
||||
mod cst;
|
||||
pub mod directives;
|
||||
|
||||
@@ -2,10 +2,11 @@ use std::borrow::Cow;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
|
||||
use anstream::eprintln;
|
||||
use anyhow::{anyhow, Result};
|
||||
use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use owo_colors::OwoColorize;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
|
||||
@@ -3,15 +3,16 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use fern;
|
||||
use log::Level;
|
||||
use once_cell::sync::Lazy;
|
||||
use owo_colors::OwoColorize;
|
||||
use ruff_python_parser::{ParseError, ParseErrorType};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_source_file::{LineIndex, OneIndexed, SourceCode, SourceLocation};
|
||||
|
||||
use crate::colors;
|
||||
use crate::fs;
|
||||
use crate::source_kind::SourceKind;
|
||||
use ruff_notebook::Notebook;
|
||||
@@ -22,7 +23,7 @@ pub static IDENTIFIERS: Lazy<Mutex<Vec<&'static str>>> = Lazy::new(Mutex::defaul
|
||||
#[macro_export]
|
||||
macro_rules! warn_user_once_by_id {
|
||||
($id:expr, $($arg:tt)*) => {
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
use log::warn;
|
||||
|
||||
if let Ok(mut states) = $crate::logging::IDENTIFIERS.lock() {
|
||||
@@ -42,7 +43,7 @@ pub static MESSAGES: Lazy<Mutex<FxHashSet<String>>> = Lazy::new(Mutex::default);
|
||||
#[macro_export]
|
||||
macro_rules! warn_user_once_by_message {
|
||||
($($arg:tt)*) => {
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
use log::warn;
|
||||
|
||||
if let Ok(mut states) = $crate::logging::MESSAGES.lock() {
|
||||
@@ -59,7 +60,7 @@ macro_rules! warn_user_once_by_message {
|
||||
#[macro_export]
|
||||
macro_rules! warn_user_once {
|
||||
($($arg:tt)*) => {
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
use log::warn;
|
||||
|
||||
static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
@@ -73,7 +74,7 @@ macro_rules! warn_user_once {
|
||||
#[macro_export]
|
||||
macro_rules! warn_user {
|
||||
($($arg:tt)*) => {{
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
use log::warn;
|
||||
|
||||
let message = format!("{}", format_args!($($arg)*));
|
||||
@@ -152,7 +153,10 @@ pub fn set_up_logging(level: LogLevel) -> Result<()> {
|
||||
})
|
||||
.level(level.level_filter())
|
||||
.level_for("globset", log::LevelFilter::Warn)
|
||||
.chain(std::io::stderr())
|
||||
.chain(fern::Output::writer(
|
||||
Box::new(colors::auto(std::io::stderr())),
|
||||
"\n",
|
||||
))
|
||||
.apply()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use colored::{Color, ColoredString, Colorize, Styles};
|
||||
use owo_colors::{OwoColorize, Style};
|
||||
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
@@ -81,7 +81,7 @@ impl Display for Diff<'_> {
|
||||
ChangeTag::Equal => " ",
|
||||
};
|
||||
|
||||
let line_style = LineStyle::from(change.tag());
|
||||
let line_style = diff_line_style(change.tag());
|
||||
|
||||
let old_index = change.old_index().map(OneIndexed::from_zero_indexed);
|
||||
let new_index = change.new_index().map(OneIndexed::from_zero_indexed);
|
||||
@@ -97,14 +97,14 @@ impl Display for Diff<'_> {
|
||||
index: new_index,
|
||||
width: digit_with
|
||||
},
|
||||
line_style.apply_to(sign).bold()
|
||||
sign.style(line_style).bold()
|
||||
)?;
|
||||
|
||||
for (emphasized, value) in change.iter_strings_lossy() {
|
||||
if emphasized {
|
||||
write!(f, "{}", line_style.apply_to(&value).underline().on_black())?;
|
||||
write!(f, "{}", value.style(line_style).underline().on_black())?;
|
||||
} else {
|
||||
write!(f, "{}", line_style.apply_to(&value))?;
|
||||
write!(f, "{}", value.style(line_style))?;
|
||||
}
|
||||
}
|
||||
if change.missing_newline() {
|
||||
@@ -118,52 +118,11 @@ impl Display for Diff<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
struct LineStyle {
|
||||
fgcolor: Option<Color>,
|
||||
style: Option<Styles>,
|
||||
}
|
||||
|
||||
impl LineStyle {
|
||||
fn apply_to(&self, input: &str) -> ColoredString {
|
||||
let mut colored = ColoredString::from(input);
|
||||
if let Some(color) = self.fgcolor {
|
||||
colored = colored.color(color);
|
||||
}
|
||||
|
||||
if let Some(style) = self.style {
|
||||
match style {
|
||||
Styles::Clear => colored.clear(),
|
||||
Styles::Bold => colored.bold(),
|
||||
Styles::Dimmed => colored.dimmed(),
|
||||
Styles::Underline => colored.underline(),
|
||||
Styles::Reversed => colored.reversed(),
|
||||
Styles::Italic => colored.italic(),
|
||||
Styles::Blink => colored.blink(),
|
||||
Styles::Hidden => colored.hidden(),
|
||||
Styles::Strikethrough => colored.strikethrough(),
|
||||
}
|
||||
} else {
|
||||
colored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChangeTag> for LineStyle {
|
||||
fn from(value: ChangeTag) -> Self {
|
||||
match value {
|
||||
ChangeTag::Equal => LineStyle {
|
||||
fgcolor: None,
|
||||
style: Some(Styles::Dimmed),
|
||||
},
|
||||
ChangeTag::Delete => LineStyle {
|
||||
fgcolor: Some(Color::Red),
|
||||
style: None,
|
||||
},
|
||||
ChangeTag::Insert => LineStyle {
|
||||
fgcolor: Some(Color::Green),
|
||||
style: None,
|
||||
},
|
||||
}
|
||||
fn diff_line_style(change_tag: ChangeTag) -> Style {
|
||||
match change_tag {
|
||||
ChangeTag::Equal => Style::new().dimmed(),
|
||||
ChangeTag::Delete => Style::new().red(),
|
||||
ChangeTag::Insert => Style::new().green(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter};
|
||||
use std::io::Write;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::OneIndexed;
|
||||
|
||||
@@ -156,6 +156,7 @@ mod tests {
|
||||
use ruff_source_file::{OneIndexed, SourceFileBuilder};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::colors;
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
|
||||
pub(super) fn create_messages() -> Vec<Message> {
|
||||
@@ -337,10 +338,10 @@ def foo():
|
||||
) -> String {
|
||||
let notebook_indexes = FxHashMap::default();
|
||||
let context = EmitterContext::new(¬ebook_indexes);
|
||||
let mut output: Vec<u8> = Vec::new();
|
||||
let mut output = colors::none(Vec::new());
|
||||
emitter.emit(&mut output, messages, &context).unwrap();
|
||||
|
||||
String::from_utf8(output).expect("Output to be valid UTF-8")
|
||||
String::from_utf8(output.into_inner()).expect("Output to be valid UTF-8")
|
||||
}
|
||||
|
||||
pub(super) fn capture_emitter_notebook_output(
|
||||
@@ -349,9 +350,9 @@ def foo():
|
||||
notebook_indexes: &FxHashMap<String, NotebookIndex>,
|
||||
) -> String {
|
||||
let context = EmitterContext::new(notebook_indexes);
|
||||
let mut output: Vec<u8> = Vec::new();
|
||||
let mut output = colors::none(Vec::new());
|
||||
emitter.emit(&mut output, messages, &context).unwrap();
|
||||
|
||||
String::from_utf8(output).expect("Output to be valid UTF-8")
|
||||
String::from_utf8(output.into_inner()).expect("Output to be valid UTF-8")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ use std::io::Write;
|
||||
use annotate_snippets::display_list::{DisplayList, FormatOptions};
|
||||
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
|
||||
use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{OneIndexed, SourceLocation};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::colors;
|
||||
use crate::fs::relativize_path;
|
||||
use crate::line_width::{IndentWidth, LineWidthBuilder};
|
||||
use crate::message::diff::Diff;
|
||||
@@ -284,10 +285,7 @@ impl Display for MessageCodeFrame<'_> {
|
||||
}],
|
||||
footer,
|
||||
opt: FormatOptions {
|
||||
#[cfg(test)]
|
||||
color: false,
|
||||
#[cfg(not(test))]
|
||||
color: colored::control::SHOULD_COLORIZE.should_colorize(),
|
||||
color: colors::enabled(&std::io::stdout()),
|
||||
..FormatOptions::default()
|
||||
},
|
||||
};
|
||||
|
||||
@@ -26,17 +26,14 @@ use std::path::{Path, PathBuf};
|
||||
/// Return `true` if the directory at the given `Path` appears to be a Python
|
||||
/// package.
|
||||
pub fn is_package(path: &Path, namespace_packages: &[PathBuf]) -> bool {
|
||||
path.join("__init__.py").is_file()
|
||||
|| namespace_packages
|
||||
.iter()
|
||||
.any(|namespace_package| namespace_package == path)
|
||||
namespace_packages
|
||||
.iter()
|
||||
.any(|namespace_package| path.starts_with(namespace_package))
|
||||
|| path.join("__init__.py").is_file()
|
||||
}
|
||||
|
||||
/// Return the package root for the given Python file.
|
||||
pub fn detect_package_root<'a>(
|
||||
path: &'a Path,
|
||||
namespace_packages: &'a [PathBuf],
|
||||
) -> Option<&'a Path> {
|
||||
/// Return the package root for the given path to a directory with Python file.
|
||||
pub fn detect_package_root<'a>(path: &'a Path, namespace_packages: &[PathBuf]) -> Option<&'a Path> {
|
||||
let mut current = None;
|
||||
for parent in path.ancestors() {
|
||||
if !is_package(parent, namespace_packages) {
|
||||
@@ -84,4 +81,39 @@ mod tests {
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn package_detection_with_namespace_packages() {
|
||||
assert_eq!(
|
||||
detect_package_root(&test_resource_path("project/python_modules/core/core"), &[],),
|
||||
Some(test_resource_path("project/python_modules/core/core").as_path())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
detect_package_root(
|
||||
&test_resource_path("project/python_modules/core/core"),
|
||||
&[test_resource_path("project/python_modules/core"),],
|
||||
),
|
||||
Some(test_resource_path("project/python_modules/core").as_path())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
detect_package_root(
|
||||
&test_resource_path("project/python_modules/core/core"),
|
||||
&[
|
||||
test_resource_path("project/python_modules/core"),
|
||||
test_resource_path("project/python_modules"),
|
||||
],
|
||||
),
|
||||
Some(test_resource_path("project/python_modules").as_path())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
detect_package_root(
|
||||
&test_resource_path("project/python_modules/core/core"),
|
||||
&[test_resource_path("project/python_modules"),],
|
||||
),
|
||||
Some(test_resource_path("project/python_modules").as_path())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use colored::Colorize;
|
||||
use log::warn;
|
||||
use owo_colors::OwoColorize;
|
||||
use pyproject_toml::PyProjectToml;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
|
||||
@@ -115,6 +115,9 @@ pub enum Linter {
|
||||
/// [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions)
|
||||
#[prefix = "ICN"]
|
||||
Flake8ImportConventions,
|
||||
/// [flake8-logging](https://pypi.org/project/flake8-logging/)
|
||||
#[prefix = "LOG"]
|
||||
Flake8Logging,
|
||||
/// [flake8-logging-format](https://pypi.org/project/flake8-logging-format/)
|
||||
#[prefix = "G"]
|
||||
Flake8LoggingFormat,
|
||||
@@ -202,9 +205,6 @@ pub enum Linter {
|
||||
/// [refurb](https://pypi.org/project/refurb/)
|
||||
#[prefix = "FURB"]
|
||||
Refurb,
|
||||
/// [flake8-logging](https://pypi.org/project/flake8-logging/)
|
||||
#[prefix = "LOG"]
|
||||
Flake8Logging,
|
||||
/// Ruff-specific rules
|
||||
#[prefix = "RUF"]
|
||||
Ruff,
|
||||
@@ -257,9 +257,6 @@ impl Rule {
|
||||
| Rule::TrailingWhitespace => LintSource::PhysicalLines,
|
||||
Rule::AmbiguousUnicodeCharacterComment
|
||||
| Rule::AvoidableEscapedQuote
|
||||
| Rule::BadQuotesDocstring
|
||||
| Rule::BadQuotesInlineString
|
||||
| Rule::BadQuotesMultilineString
|
||||
| Rule::BlanketNOQA
|
||||
| Rule::BlanketTypeIgnore
|
||||
| Rule::BlankLineAfterDecorator
|
||||
|
||||
@@ -4,8 +4,9 @@ use anyhow::{anyhow, Result};
|
||||
use itertools::Itertools;
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_semantic::{Binding, BindingKind, Scope, ScopeId, SemanticModel};
|
||||
use ruff_text_size::Ranged;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
pub(crate) struct Renamer;
|
||||
|
||||
@@ -104,6 +105,7 @@ impl Renamer {
|
||||
target: &str,
|
||||
scope: &Scope,
|
||||
semantic: &SemanticModel,
|
||||
stylist: &Stylist,
|
||||
) -> Result<(Edit, Vec<Edit>)> {
|
||||
let mut edits = vec![];
|
||||
|
||||
@@ -130,7 +132,9 @@ impl Renamer {
|
||||
});
|
||||
|
||||
let scope = scope_id.map_or(scope, |scope_id| &semantic.scopes[scope_id]);
|
||||
edits.extend(Renamer::rename_in_scope(name, target, scope, semantic));
|
||||
edits.extend(Renamer::rename_in_scope(
|
||||
name, target, scope, semantic, stylist,
|
||||
));
|
||||
|
||||
// Find any scopes in which the symbol is referenced as `nonlocal` or `global`. For example,
|
||||
// given:
|
||||
@@ -160,7 +164,9 @@ impl Renamer {
|
||||
.copied()
|
||||
{
|
||||
let scope = &semantic.scopes[scope_id];
|
||||
edits.extend(Renamer::rename_in_scope(name, target, scope, semantic));
|
||||
edits.extend(Renamer::rename_in_scope(
|
||||
name, target, scope, semantic, stylist,
|
||||
));
|
||||
}
|
||||
|
||||
// Deduplicate any edits.
|
||||
@@ -180,6 +186,7 @@ impl Renamer {
|
||||
target: &str,
|
||||
scope: &Scope,
|
||||
semantic: &SemanticModel,
|
||||
stylist: &Stylist,
|
||||
) -> Vec<Edit> {
|
||||
let mut edits = vec![];
|
||||
|
||||
@@ -202,7 +209,17 @@ impl Renamer {
|
||||
// Rename the references to the binding.
|
||||
edits.extend(binding.references().map(|reference_id| {
|
||||
let reference = semantic.reference(reference_id);
|
||||
Edit::range_replacement(target.to_string(), reference.range())
|
||||
let replacement = {
|
||||
if reference.in_dunder_all_definition() {
|
||||
debug_assert!(!reference.range().is_empty());
|
||||
let quote = stylist.quote();
|
||||
format!("{quote}{target}{quote}")
|
||||
} else {
|
||||
debug_assert_eq!(TextSize::of(name), reference.range().len());
|
||||
target.to_string()
|
||||
}
|
||||
};
|
||||
Edit::range_replacement(replacement, reference.range())
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ pub struct SuspiciousInsecureCipherModeUsage;
|
||||
impl Violation for SuspiciousInsecureCipherModeUsage {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use of insecure cipher mode, replace with a known secure cipher such as AES")
|
||||
format!("Use of insecure block cipher mode, replace with a known secure mode such as CBC or CTR")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
/// Returns `true` if a function call is allowed to use a boolean trap.
|
||||
pub(super) fn is_allowed_func_call(name: &str) -> bool {
|
||||
@@ -43,6 +48,24 @@ pub(super) fn is_allowed_func_call(name: &str) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if a call is allowed by the user to use a boolean trap.
|
||||
pub(super) fn is_user_allowed_func_call(
|
||||
call: &ast::ExprCall,
|
||||
semantic: &SemanticModel,
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
semantic
|
||||
.resolve_qualified_name(call.func.as_ref())
|
||||
.is_some_and(|qualified_name| {
|
||||
settings
|
||||
.flake8_boolean_trap
|
||||
.extend_allowed_calls
|
||||
.iter()
|
||||
.map(|target| QualifiedName::from_dotted_name(target))
|
||||
.any(|target| qualified_name == target)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if a function definition is allowed to use a boolean trap.
|
||||
pub(super) fn is_allowed_func_def(name: &str) -> bool {
|
||||
matches!(name, "__setitem__" | "__post_init__")
|
||||
@@ -51,7 +74,7 @@ pub(super) fn is_allowed_func_def(name: &str) -> bool {
|
||||
/// Returns `true` if an argument is allowed to use a boolean trap. To return
|
||||
/// `true`, the function name must be explicitly allowed, and the argument must
|
||||
/// be either the first or second argument in the call.
|
||||
pub(super) fn allow_boolean_trap(call: &ast::ExprCall) -> bool {
|
||||
pub(super) fn allow_boolean_trap(call: &ast::ExprCall, checker: &Checker) -> bool {
|
||||
let func_name = match call.func.as_ref() {
|
||||
Expr::Attribute(ast::ExprAttribute { attr, .. }) => attr.as_str(),
|
||||
Expr::Name(ast::ExprName { id, .. }) => id.as_str(),
|
||||
@@ -76,5 +99,10 @@ pub(super) fn allow_boolean_trap(call: &ast::ExprCall) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
// If the call is explicitly allowed by the user, then the boolean trap is allowed.
|
||||
if is_user_allowed_func_call(call, checker.semantic(), checker.settings) {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Rules from [flake8-boolean-trap](https://pypi.org/project/flake8-boolean-trap/).
|
||||
mod helpers;
|
||||
pub(crate) mod rules;
|
||||
pub mod settings;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -11,6 +12,7 @@ mod tests {
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::test::test_path;
|
||||
use crate::{assert_messages, settings};
|
||||
|
||||
@@ -44,4 +46,22 @@ mod tests {
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extend_allowed_callable() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_boolean_trap/FBT.py"),
|
||||
&LinterSettings {
|
||||
flake8_boolean_trap: super::settings::Settings {
|
||||
extend_allowed_calls: vec![
|
||||
"django.db.models.Value".to_string(),
|
||||
"pydantic.Field".to_string(),
|
||||
],
|
||||
},
|
||||
..LinterSettings::for_rule(Rule::BooleanPositionalValueInCall)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ use crate::rules::flake8_boolean_trap::helpers::allow_boolean_trap;
|
||||
/// ## What it does
|
||||
/// Checks for boolean positional arguments in function calls.
|
||||
///
|
||||
/// Some functions are whitelisted by default. To extend the list of allowed calls
|
||||
/// configure the [`lint.flake8-boolean-trap.extend-allowed-calls`] option.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Calling a function with boolean positional arguments is confusing as the
|
||||
/// meaning of the boolean value is not clear to the caller, and to future
|
||||
@@ -32,6 +35,9 @@ use crate::rules::flake8_boolean_trap::helpers::allow_boolean_trap;
|
||||
/// func(flag=True)
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.flake8-boolean-trap.extend-allowed-calls`
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
||||
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
||||
@@ -46,7 +52,7 @@ impl Violation for BooleanPositionalValueInCall {
|
||||
}
|
||||
|
||||
pub(crate) fn boolean_positional_value_in_call(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if allow_boolean_trap(call) {
|
||||
if allow_boolean_trap(call, checker) {
|
||||
return;
|
||||
}
|
||||
for arg in call
|
||||
|
||||
25
crates/ruff_linter/src/rules/flake8_boolean_trap/settings.rs
Normal file
25
crates/ruff_linter/src/rules/flake8_boolean_trap/settings.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
//! Settings for the `flake8-boolean-trap` plugin.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use ruff_macros::CacheKey;
|
||||
|
||||
use crate::display_settings;
|
||||
|
||||
#[derive(Debug, CacheKey, Default)]
|
||||
pub struct Settings {
|
||||
pub extend_allowed_calls: Vec<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Settings {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_boolean_trap",
|
||||
fields = [
|
||||
self.extend_allowed_calls | array,
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -81,12 +81,10 @@ FBT.py:19:5: FBT001 Boolean-typed positional argument in function definition
|
||||
21 | kwonly_nonvalued_nohint,
|
||||
|
|
||||
|
||||
FBT.py:91:19: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:90:19: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
90 | # FBT001: Boolean positional arg in function definition
|
||||
91 | def foo(self, value: bool) -> None:
|
||||
89 | # FBT001: Boolean positional arg in function definition
|
||||
90 | def foo(self, value: bool) -> None:
|
||||
| ^^^^^ FBT001
|
||||
92 | pass
|
||||
91 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,37 +1,61 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_boolean_trap/mod.rs
|
||||
---
|
||||
FBT.py:42:11: FBT003 Boolean positional value in function call
|
||||
FBT.py:41:11: FBT003 Boolean positional value in function call
|
||||
|
|
||||
42 | used("a", True)
|
||||
41 | used("a", True)
|
||||
| ^^^^ FBT003
|
||||
43 | used(do=True)
|
||||
42 | used(do=True)
|
||||
|
|
||||
|
||||
FBT.py:57:11: FBT003 Boolean positional value in function call
|
||||
FBT.py:56:11: FBT003 Boolean positional value in function call
|
||||
|
|
||||
55 | {}.pop(True, False)
|
||||
56 | dict.fromkeys(("world",), True)
|
||||
57 | {}.deploy(True, False)
|
||||
54 | {}.pop(True, False)
|
||||
55 | dict.fromkeys(("world",), True)
|
||||
56 | {}.deploy(True, False)
|
||||
| ^^^^ FBT003
|
||||
58 | getattr(someobj, attrname, False)
|
||||
59 | mylist.index(True)
|
||||
57 | getattr(someobj, attrname, False)
|
||||
58 | mylist.index(True)
|
||||
|
|
||||
|
||||
FBT.py:57:17: FBT003 Boolean positional value in function call
|
||||
FBT.py:56:17: FBT003 Boolean positional value in function call
|
||||
|
|
||||
55 | {}.pop(True, False)
|
||||
56 | dict.fromkeys(("world",), True)
|
||||
57 | {}.deploy(True, False)
|
||||
54 | {}.pop(True, False)
|
||||
55 | dict.fromkeys(("world",), True)
|
||||
56 | {}.deploy(True, False)
|
||||
| ^^^^^ FBT003
|
||||
58 | getattr(someobj, attrname, False)
|
||||
59 | mylist.index(True)
|
||||
57 | getattr(someobj, attrname, False)
|
||||
58 | mylist.index(True)
|
||||
|
|
||||
|
||||
FBT.py:121:10: FBT003 Boolean positional value in function call
|
||||
FBT.py:120:10: FBT003 Boolean positional value in function call
|
||||
|
|
||||
121 | settings(True)
|
||||
120 | settings(True)
|
||||
| ^^^^ FBT003
|
||||
|
|
||||
|
||||
FBT.py:144:20: FBT003 Boolean positional value in function call
|
||||
|
|
||||
142 | is_foo_or_bar=Case(
|
||||
143 | When(Q(is_foo=True) | Q(is_bar=True)),
|
||||
144 | then=Value(True),
|
||||
| ^^^^ FBT003
|
||||
145 | ),
|
||||
146 | default=Value(False),
|
||||
|
|
||||
|
||||
FBT.py:146:19: FBT003 Boolean positional value in function call
|
||||
|
|
||||
144 | then=Value(True),
|
||||
145 | ),
|
||||
146 | default=Value(False),
|
||||
| ^^^^^ FBT003
|
||||
147 | )
|
||||
|
|
||||
|
||||
FBT.py:156:23: FBT003 Boolean positional value in function call
|
||||
|
|
||||
155 | class Settings(BaseSettings):
|
||||
156 | foo: bool = Field(True, exclude=True)
|
||||
| ^^^^ FBT003
|
||||
|
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_boolean_trap/mod.rs
|
||||
---
|
||||
FBT.py:41:11: FBT003 Boolean positional value in function call
|
||||
|
|
||||
41 | used("a", True)
|
||||
| ^^^^ FBT003
|
||||
42 | used(do=True)
|
||||
|
|
||||
|
||||
FBT.py:56:11: FBT003 Boolean positional value in function call
|
||||
|
|
||||
54 | {}.pop(True, False)
|
||||
55 | dict.fromkeys(("world",), True)
|
||||
56 | {}.deploy(True, False)
|
||||
| ^^^^ FBT003
|
||||
57 | getattr(someobj, attrname, False)
|
||||
58 | mylist.index(True)
|
||||
|
|
||||
|
||||
FBT.py:56:17: FBT003 Boolean positional value in function call
|
||||
|
|
||||
54 | {}.pop(True, False)
|
||||
55 | dict.fromkeys(("world",), True)
|
||||
56 | {}.deploy(True, False)
|
||||
| ^^^^^ FBT003
|
||||
57 | getattr(someobj, attrname, False)
|
||||
58 | mylist.index(True)
|
||||
|
|
||||
|
||||
FBT.py:120:10: FBT003 Boolean positional value in function call
|
||||
|
|
||||
120 | settings(True)
|
||||
| ^^^^ FBT003
|
||||
|
|
||||
@@ -81,26 +81,24 @@ FBT.py:19:5: FBT001 Boolean-typed positional argument in function definition
|
||||
21 | kwonly_nonvalued_nohint,
|
||||
|
|
||||
|
||||
FBT.py:91:19: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:90:19: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
90 | # FBT001: Boolean positional arg in function definition
|
||||
91 | def foo(self, value: bool) -> None:
|
||||
89 | # FBT001: Boolean positional arg in function definition
|
||||
90 | def foo(self, value: bool) -> None:
|
||||
| ^^^^^ FBT001
|
||||
92 | pass
|
||||
91 | pass
|
||||
|
|
||||
|
||||
FBT.py:101:10: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:100:10: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
101 | def func(x: Union[list, Optional[int | str | float | bool]]):
|
||||
100 | def func(x: Union[list, Optional[int | str | float | bool]]):
|
||||
| ^ FBT001
|
||||
102 | pass
|
||||
101 | pass
|
||||
|
|
||||
|
||||
FBT.py:105:10: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:104:10: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
105 | def func(x: bool | str):
|
||||
104 | def func(x: bool | str):
|
||||
| ^ FBT001
|
||||
106 | pass
|
||||
105 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -100,6 +100,16 @@ impl<'a> GroupNameFinder<'a> {
|
||||
self.usage_count += value;
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the usage count for the group name by the given value.
|
||||
/// This function is called when there is a `continue`, `break`, or `return` statement.
|
||||
fn reset_usage_count(&mut self) {
|
||||
if let Some(last) = self.counter_stack.last_mut() {
|
||||
*last.last_mut().unwrap() = 0;
|
||||
} else {
|
||||
self.usage_count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for GroupNameFinder<'a> {
|
||||
@@ -197,6 +207,15 @@ impl<'a> Visitor<'a> for GroupNameFinder<'a> {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
}
|
||||
Stmt::Continue(_) | Stmt::Break(_) => {
|
||||
self.reset_usage_count();
|
||||
}
|
||||
Stmt::Return(ast::StmtReturn { value, range: _ }) => {
|
||||
if let Some(expr) = value {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.reset_usage_count();
|
||||
}
|
||||
_ => visitor::walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,26 @@ use super::super::helpers::at_last_top_level_expression_in_cell;
|
||||
/// ```python
|
||||
/// foo = 1 + 1
|
||||
/// ```
|
||||
///
|
||||
/// ## Known problems
|
||||
/// This rule ignores expression types that are commonly used for their side
|
||||
/// effects, such as function calls.
|
||||
///
|
||||
/// However, if a seemingly useless expression (like an attribute access) is
|
||||
/// needed to trigger a side effect, consider assigning it to an anonymous
|
||||
/// variable, to indicate that the return value is intentionally ignored.
|
||||
///
|
||||
/// For example, given:
|
||||
/// ```python
|
||||
/// with errors.ExceptionRaisedContext():
|
||||
/// obj.attribute
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// with errors.ExceptionRaisedContext():
|
||||
/// _ = obj.attribute
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct UselessExpression {
|
||||
kind: Kind,
|
||||
|
||||
@@ -195,4 +195,31 @@ B031.py:144:33: B031 Using the generator returned from `itertools.groupby()` mor
|
||||
146 | for group in groupby(items, key=lambda p: p[1]):
|
||||
|
|
||||
|
||||
B031.py:200:37: B031 Using the generator returned from `itertools.groupby()` more than once will do nothing on the second usage
|
||||
|
|
||||
198 | if _section == "greens":
|
||||
199 | collect_shop_items(shopper, section_items)
|
||||
200 | collect_shop_items(shopper, section_items)
|
||||
| ^^^^^^^^^^^^^ B031
|
||||
201 | return
|
||||
|
|
||||
|
||||
B031.py:210:37: B031 Using the generator returned from `itertools.groupby()` more than once will do nothing on the second usage
|
||||
|
|
||||
208 | elif _section == "frozen items":
|
||||
209 | collect_shop_items(shopper, section_items)
|
||||
210 | collect_shop_items(shopper, section_items)
|
||||
| ^^^^^^^^^^^^^ B031
|
||||
211 |
|
||||
212 | # Should trigger, since only one branch has a return statement.
|
||||
|
|
||||
|
||||
B031.py:219:33: B031 Using the generator returned from `itertools.groupby()` more than once will do nothing on the second usage
|
||||
|
|
||||
217 | elif _section == "frozen items":
|
||||
218 | collect_shop_items(shopper, section_items)
|
||||
219 | collect_shop_items(shopper, section_items) # B031
|
||||
| ^^^^^^^^^^^^^ B031
|
||||
220 |
|
||||
221 | # Let's redefine the `groupby` function to make sure we pick up the correct one.
|
||||
|
|
||||
|
||||
@@ -793,7 +793,7 @@ pub(crate) fn fix_unnecessary_map(
|
||||
}
|
||||
|
||||
/// (C419) Convert `[i for i in a]` into `i for i in a`
|
||||
pub(crate) fn fix_unnecessary_comprehension_any_all(
|
||||
pub(crate) fn fix_unnecessary_comprehension_in_call(
|
||||
expr: &Expr,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
|
||||
@@ -12,13 +12,15 @@ mod tests {
|
||||
|
||||
use crate::assert_messages;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::test::test_path;
|
||||
|
||||
#[test_case(Rule::UnnecessaryCallAroundSorted, Path::new("C413.py"))]
|
||||
#[test_case(Rule::UnnecessaryCollectionCall, Path::new("C408.py"))]
|
||||
#[test_case(Rule::UnnecessaryComprehension, Path::new("C416.py"))]
|
||||
#[test_case(Rule::UnnecessaryComprehensionAnyAll, Path::new("C419.py"))]
|
||||
#[test_case(Rule::UnnecessaryComprehensionInCall, Path::new("C419.py"))]
|
||||
#[test_case(Rule::UnnecessaryComprehensionInCall, Path::new("C419_2.py"))]
|
||||
#[test_case(Rule::UnnecessaryDoubleCastOrProcess, Path::new("C414.py"))]
|
||||
#[test_case(Rule::UnnecessaryGeneratorDict, Path::new("C402.py"))]
|
||||
#[test_case(Rule::UnnecessaryGeneratorList, Path::new("C400.py"))]
|
||||
@@ -43,6 +45,24 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::UnnecessaryComprehensionInCall, Path::new("C419_1.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_comprehensions").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::UnnecessaryCollectionCall, Path::new("C408.py"))]
|
||||
fn allow_dict_calls_with_keyword_arguments(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pub(crate) use unnecessary_call_around_sorted::*;
|
||||
pub(crate) use unnecessary_collection_call::*;
|
||||
pub(crate) use unnecessary_comprehension::*;
|
||||
pub(crate) use unnecessary_comprehension_any_all::*;
|
||||
pub(crate) use unnecessary_comprehension_in_call::*;
|
||||
pub(crate) use unnecessary_double_cast_or_process::*;
|
||||
pub(crate) use unnecessary_generator_dict::*;
|
||||
pub(crate) use unnecessary_generator_list::*;
|
||||
@@ -21,7 +21,7 @@ mod helpers;
|
||||
mod unnecessary_call_around_sorted;
|
||||
mod unnecessary_collection_call;
|
||||
mod unnecessary_comprehension;
|
||||
mod unnecessary_comprehension_any_all;
|
||||
mod unnecessary_comprehension_in_call;
|
||||
mod unnecessary_double_cast_or_process;
|
||||
mod unnecessary_generator_dict;
|
||||
mod unnecessary_generator_list;
|
||||
|
||||
@@ -11,15 +11,18 @@ use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_comprehensions::fixes;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary list comprehensions passed to `any` and `all`.
|
||||
/// Checks for unnecessary list comprehensions passed to builtin functions that take an iterable.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `any` and `all` take any iterators, including generators. Converting a generator to a list
|
||||
/// by way of a list comprehension is unnecessary and reduces performance due to the
|
||||
/// overhead of creating the list.
|
||||
/// Many builtin functions (this rule currently covers `any`, `all`, `min`, `max`, and `sum`) take
|
||||
/// any iterable, including a generator. Constructing a temporary list via list comprehension is
|
||||
/// unnecessary and wastes memory for large iterables.
|
||||
///
|
||||
/// For example, compare the performance of `all` with a list comprehension against that
|
||||
/// of a generator (~40x faster here):
|
||||
/// `any` and `all` can also short-circuit iteration, saving a lot of time. The unnecessary
|
||||
/// comprehension forces a full iteration of the input iterable, giving up the benefits of
|
||||
/// short-circuiting. For example, compare the performance of `all` with a list comprehension
|
||||
/// against that of a generator in a case where an early short-circuit is possible (almost 40x
|
||||
/// faster):
|
||||
///
|
||||
/// ```console
|
||||
/// In [1]: %timeit all([i for i in range(1000)])
|
||||
@@ -29,26 +32,41 @@ use crate::rules::flake8_comprehensions::fixes;
|
||||
/// 212 ns ± 0.892 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
|
||||
/// ```
|
||||
///
|
||||
/// This performance improvement is due to short-circuiting. If the entire iterable has to be
|
||||
/// traversed, the comprehension version may even be a bit faster: list allocation overhead is not
|
||||
/// necessarily greater than generator overhead.
|
||||
///
|
||||
/// Applying this rule simplifies the code and will usually save memory, but in the absence of
|
||||
/// short-circuiting it may not improve performance. (It may even slightly regress performance,
|
||||
/// though the difference will usually be small.)
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// any([x.id for x in bar])
|
||||
/// all([x.id for x in bar])
|
||||
/// sum([x.val for x in bar])
|
||||
/// min([x.val for x in bar])
|
||||
/// max([x.val for x in bar])
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// any(x.id for x in bar)
|
||||
/// all(x.id for x in bar)
|
||||
/// sum(x.val for x in bar)
|
||||
/// min(x.val for x in bar)
|
||||
/// max(x.val for x in bar)
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
|
||||
/// when rewriting the comprehension. In most cases, though, comments will be
|
||||
/// preserved.
|
||||
/// This rule's fix is marked as unsafe, as it can change the behavior of the code if the iteration
|
||||
/// has side effects (due to laziness and short-circuiting). The fix may also drop comments when
|
||||
/// rewriting some comprehensions.
|
||||
///
|
||||
#[violation]
|
||||
pub struct UnnecessaryComprehensionAnyAll;
|
||||
pub struct UnnecessaryComprehensionInCall;
|
||||
|
||||
impl Violation for UnnecessaryComprehensionAnyAll {
|
||||
impl Violation for UnnecessaryComprehensionInCall {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
@@ -62,7 +80,7 @@ impl Violation for UnnecessaryComprehensionAnyAll {
|
||||
}
|
||||
|
||||
/// C419
|
||||
pub(crate) fn unnecessary_comprehension_any_all(
|
||||
pub(crate) fn unnecessary_comprehension_in_call(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
@@ -72,10 +90,13 @@ pub(crate) fn unnecessary_comprehension_any_all(
|
||||
if !keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Expr::Name(ast::ExprName { id, .. }) = func else {
|
||||
return;
|
||||
};
|
||||
if !matches!(id.as_str(), "all" | "any") {
|
||||
if !(matches!(id.as_str(), "any" | "all")
|
||||
|| (checker.settings.preview.is_enabled() && matches!(id.as_str(), "sum" | "min" | "max")))
|
||||
{
|
||||
return;
|
||||
}
|
||||
let [arg] = args else {
|
||||
@@ -93,9 +114,9 @@ pub(crate) fn unnecessary_comprehension_any_all(
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryComprehensionAnyAll, arg.range());
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryComprehensionInCall, arg.range());
|
||||
diagnostic.try_set_fix(|| {
|
||||
fixes::fix_unnecessary_comprehension_any_all(expr, checker.locator(), checker.stylist())
|
||||
fixes::fix_unnecessary_comprehension_in_call(expr, checker.locator(), checker.stylist())
|
||||
});
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::ExprGenerator;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -10,37 +12,53 @@ use super::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary generators that can be rewritten as `set`
|
||||
/// comprehensions.
|
||||
/// comprehensions (or with `set` directly).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// It is unnecessary to use `set` around a generator expression, since
|
||||
/// there are equivalent comprehensions for these types. Using a
|
||||
/// comprehension is clearer and more idiomatic.
|
||||
///
|
||||
/// Further, if the comprehension can be removed entirely, as in the case of
|
||||
/// `set(x for x in foo)`, it's better to use `set(foo)` directly, since it's
|
||||
/// even more direct.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// set(f(x) for x in foo)
|
||||
/// set(x for x in foo)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// {f(x) for x in foo}
|
||||
/// set(foo)
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
|
||||
/// when rewriting the call. In most cases, though, comments will be preserved.
|
||||
#[violation]
|
||||
pub struct UnnecessaryGeneratorSet;
|
||||
pub struct UnnecessaryGeneratorSet {
|
||||
short_circuit: bool,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for UnnecessaryGeneratorSet {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Unnecessary generator (rewrite as a `set` comprehension)")
|
||||
if self.short_circuit {
|
||||
format!("Unnecessary generator (rewrite using `set()`")
|
||||
} else {
|
||||
format!("Unnecessary generator (rewrite as a `set` comprehension)")
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Rewrite as a `set` comprehension".to_string()
|
||||
if self.short_circuit {
|
||||
"Rewrite using `set()`".to_string()
|
||||
} else {
|
||||
"Rewrite as a `set` comprehension".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,28 +75,59 @@ pub(crate) fn unnecessary_generator_set(checker: &mut Checker, call: &ast::ExprC
|
||||
if !checker.semantic().is_builtin("set") {
|
||||
return;
|
||||
}
|
||||
if argument.is_generator_expr() {
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorSet, call.range());
|
||||
|
||||
// Convert `set(x for x in y)` to `{x for x in y}`.
|
||||
diagnostic.set_fix({
|
||||
// Replace `set(` with `}`.
|
||||
let call_start = Edit::replacement(
|
||||
pad_start("{", call.range(), checker.locator(), checker.semantic()),
|
||||
call.start(),
|
||||
call.arguments.start() + TextSize::from(1),
|
||||
);
|
||||
let Some(ExprGenerator {
|
||||
elt, generators, ..
|
||||
}) = argument.as_generator_expr()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Replace `)` with `}`.
|
||||
let call_end = Edit::replacement(
|
||||
pad_end("}", call.range(), checker.locator(), checker.semantic()),
|
||||
call.arguments.end() - TextSize::from(1),
|
||||
call.end(),
|
||||
);
|
||||
|
||||
Fix::unsafe_edits(call_start, [call_end])
|
||||
});
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
// Short-circuit: given `set(x for x in y)`, generate `set(y)` (in lieu of `{x for x in y}`).
|
||||
if let [generator] = generators.as_slice() {
|
||||
if generator.ifs.is_empty() && !generator.is_async {
|
||||
if ComparableExpr::from(elt) == ComparableExpr::from(&generator.target) {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnnecessaryGeneratorSet {
|
||||
short_circuit: true,
|
||||
},
|
||||
call.range(),
|
||||
);
|
||||
let iterator = format!("set({})", checker.locator().slice(generator.iter.range()));
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
iterator,
|
||||
call.range(),
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert `set(f(x) for x in y)` to `{f(x) for x in y}`.
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnnecessaryGeneratorSet {
|
||||
short_circuit: false,
|
||||
},
|
||||
call.range(),
|
||||
);
|
||||
diagnostic.set_fix({
|
||||
// Replace `set(` with `}`.
|
||||
let call_start = Edit::replacement(
|
||||
pad_start("{", call.range(), checker.locator(), checker.semantic()),
|
||||
call.start(),
|
||||
call.arguments.start() + TextSize::from(1),
|
||||
);
|
||||
|
||||
// Replace `)` with `}`.
|
||||
let call_end = Edit::replacement(
|
||||
pad_end("}", call.range(), checker.locator(), checker.semantic()),
|
||||
call.arguments.end() - TextSize::from(1),
|
||||
call.end(),
|
||||
);
|
||||
|
||||
Fix::unsafe_edits(call_start, [call_end])
|
||||
});
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -1,255 +1,249 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_comprehensions/mod.rs
|
||||
---
|
||||
C401.py:1:5: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
C401.py:2:13: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
|
|
||||
1 | x = set(x for x in range(3))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
2 | x = set(x for x in range(3))
|
||||
3 | y = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
1 | # Cannot conbime with C416. Should use set comprehension here.
|
||||
2 | even_nums = set(2 * x for x in range(3))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
3 | odd_nums = set(
|
||||
4 | 2 * x + 1 for x in range(3)
|
||||
|
|
||||
= help: Rewrite as a `set` comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |-x = set(x for x in range(3))
|
||||
1 |+x = {x for x in range(3)}
|
||||
2 2 | x = set(x for x in range(3))
|
||||
3 3 | y = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
4 4 | _ = "{}".format(set(a if a < 6 else 0 for a in range(3)))
|
||||
1 1 | # Cannot conbime with C416. Should use set comprehension here.
|
||||
2 |-even_nums = set(2 * x for x in range(3))
|
||||
2 |+even_nums = {2 * x for x in range(3)}
|
||||
3 3 | odd_nums = set(
|
||||
4 4 | 2 * x + 1 for x in range(3)
|
||||
5 5 | )
|
||||
|
||||
C401.py:2:5: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
C401.py:3:12: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
|
|
||||
1 | x = set(x for x in range(3))
|
||||
2 | x = set(x for x in range(3))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
3 | y = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
4 | _ = "{}".format(set(a if a < 6 else 0 for a in range(3)))
|
||||
1 | # Cannot conbime with C416. Should use set comprehension here.
|
||||
2 | even_nums = set(2 * x for x in range(3))
|
||||
3 | odd_nums = set(
|
||||
| ____________^
|
||||
4 | | 2 * x + 1 for x in range(3)
|
||||
5 | | )
|
||||
| |_^ C401
|
||||
6 | small_nums = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
|
|
||||
= help: Rewrite as a `set` comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | x = set(x for x in range(3))
|
||||
2 |-x = set(x for x in range(3))
|
||||
2 |+x = {x for x in range(3)}
|
||||
3 3 | y = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
4 4 | _ = "{}".format(set(a if a < 6 else 0 for a in range(3)))
|
||||
5 5 | print(f"Hello {set(a for a in range(3))} World")
|
||||
|
||||
C401.py:3:8: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
|
|
||||
1 | x = set(x for x in range(3))
|
||||
2 | x = set(x for x in range(3))
|
||||
3 | y = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
4 | _ = "{}".format(set(a if a < 6 else 0 for a in range(3)))
|
||||
5 | print(f"Hello {set(a for a in range(3))} World")
|
||||
|
|
||||
= help: Rewrite as a `set` comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | x = set(x for x in range(3))
|
||||
2 2 | x = set(x for x in range(3))
|
||||
3 |-y = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
3 |+y = f"{ {a if a < 6 else 0 for a in range(3)} }"
|
||||
4 4 | _ = "{}".format(set(a if a < 6 else 0 for a in range(3)))
|
||||
5 5 | print(f"Hello {set(a for a in range(3))} World")
|
||||
6 6 |
|
||||
|
||||
C401.py:4:17: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
|
|
||||
2 | x = set(x for x in range(3))
|
||||
3 | y = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
4 | _ = "{}".format(set(a if a < 6 else 0 for a in range(3)))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
5 | print(f"Hello {set(a for a in range(3))} World")
|
||||
|
|
||||
= help: Rewrite as a `set` comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | x = set(x for x in range(3))
|
||||
2 2 | x = set(x for x in range(3))
|
||||
3 3 | y = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
4 |-_ = "{}".format(set(a if a < 6 else 0 for a in range(3)))
|
||||
4 |+_ = "{}".format({a if a < 6 else 0 for a in range(3)})
|
||||
5 5 | print(f"Hello {set(a for a in range(3))} World")
|
||||
6 6 |
|
||||
7 7 |
|
||||
|
||||
C401.py:5:16: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
|
|
||||
3 | y = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
4 | _ = "{}".format(set(a if a < 6 else 0 for a in range(3)))
|
||||
5 | print(f"Hello {set(a for a in range(3))} World")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
|
|
||||
= help: Rewrite as a `set` comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
2 2 | x = set(x for x in range(3))
|
||||
3 3 | y = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
4 4 | _ = "{}".format(set(a if a < 6 else 0 for a in range(3)))
|
||||
5 |-print(f"Hello {set(a for a in range(3))} World")
|
||||
5 |+print(f"Hello { {a for a in range(3)} } World")
|
||||
6 6 |
|
||||
1 1 | # Cannot conbime with C416. Should use set comprehension here.
|
||||
2 2 | even_nums = set(2 * x for x in range(3))
|
||||
3 |-odd_nums = set(
|
||||
3 |+odd_nums = {
|
||||
4 4 | 2 * x + 1 for x in range(3)
|
||||
5 |-)
|
||||
5 |+}
|
||||
6 6 | small_nums = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
7 7 |
|
||||
8 8 | def f(x):
|
||||
|
||||
C401.py:12:16: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
C401.py:6:17: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
|
|
||||
4 | 2 * x + 1 for x in range(3)
|
||||
5 | )
|
||||
6 | small_nums = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
7 |
|
||||
8 | def f(x):
|
||||
|
|
||||
= help: Rewrite as a `set` comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | odd_nums = set(
|
||||
4 4 | 2 * x + 1 for x in range(3)
|
||||
5 5 | )
|
||||
6 |-small_nums = f"{set(a if a < 6 else 0 for a in range(3))}"
|
||||
6 |+small_nums = f"{ {a if a < 6 else 0 for a in range(3)} }"
|
||||
7 7 |
|
||||
8 8 | def f(x):
|
||||
9 9 | return x
|
||||
|
||||
C401.py:11:16: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
|
|
||||
12 | print(f'Hello {set(a for a in "abc")} World')
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
13 | print(f"Hello {set(a for a in 'abc')} World")
|
||||
14 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
9 | return x
|
||||
10 |
|
||||
11 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
12 | print(f"Hello { set(f(a) for a in 'abc') } World")
|
||||
|
|
||||
= help: Rewrite as a `set` comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
8 8 | def f(x):
|
||||
9 9 | return x
|
||||
10 10 |
|
||||
11 |-print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
11 |+print(f"Hello { {f(a) for a in 'abc'} } World")
|
||||
12 12 | print(f"Hello { set(f(a) for a in 'abc') } World")
|
||||
13 13 |
|
||||
14 14 |
|
||||
|
||||
C401.py:12:17: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
|
|
||||
11 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
12 | print(f"Hello { set(f(a) for a in 'abc') } World")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
|
|
||||
= help: Rewrite as a `set` comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
9 9 | return x
|
||||
10 10 |
|
||||
11 11 |
|
||||
12 |-print(f'Hello {set(a for a in "abc")} World')
|
||||
12 |+print(f'Hello { {a for a in "abc"} } World')
|
||||
13 13 | print(f"Hello {set(a for a in 'abc')} World")
|
||||
14 14 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
15 15 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
11 11 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
12 |-print(f"Hello { set(f(a) for a in 'abc') } World")
|
||||
12 |+print(f"Hello { {f(a) for a in 'abc'} } World")
|
||||
13 13 |
|
||||
14 14 |
|
||||
15 15 | # Short-circuit case, combine with C416 and should produce x = set(range(3))
|
||||
|
||||
C401.py:13:16: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
C401.py:16:5: C401 [*] Unnecessary generator (rewrite using `set()`
|
||||
|
|
||||
12 | print(f'Hello {set(a for a in "abc")} World')
|
||||
13 | print(f"Hello {set(a for a in 'abc')} World")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
14 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
15 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
15 | # Short-circuit case, combine with C416 and should produce x = set(range(3))
|
||||
16 | x = set(x for x in range(3))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
17 | x = set(
|
||||
18 | x for x in range(3)
|
||||
|
|
||||
= help: Rewrite as a `set` comprehension
|
||||
= help: Rewrite using `set()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
10 10 |
|
||||
11 11 |
|
||||
12 12 | print(f'Hello {set(a for a in "abc")} World')
|
||||
13 |-print(f"Hello {set(a for a in 'abc')} World")
|
||||
13 |+print(f"Hello { {a for a in 'abc'} } World")
|
||||
14 14 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
15 15 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
16 16 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
13 13 |
|
||||
14 14 |
|
||||
15 15 | # Short-circuit case, combine with C416 and should produce x = set(range(3))
|
||||
16 |-x = set(x for x in range(3))
|
||||
16 |+x = set(range(3))
|
||||
17 17 | x = set(
|
||||
18 18 | x for x in range(3)
|
||||
19 19 | )
|
||||
|
||||
C401.py:14:16: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
C401.py:17:5: C401 [*] Unnecessary generator (rewrite using `set()`
|
||||
|
|
||||
12 | print(f'Hello {set(a for a in "abc")} World')
|
||||
13 | print(f"Hello {set(a for a in 'abc')} World")
|
||||
14 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
15 | # Short-circuit case, combine with C416 and should produce x = set(range(3))
|
||||
16 | x = set(x for x in range(3))
|
||||
17 | x = set(
|
||||
| _____^
|
||||
18 | | x for x in range(3)
|
||||
19 | | )
|
||||
| |_^ C401
|
||||
20 | print(f"Hello {set(a for a in range(3))} World")
|
||||
21 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
|
|
||||
= help: Rewrite using `set()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
14 14 |
|
||||
15 15 | # Short-circuit case, combine with C416 and should produce x = set(range(3))
|
||||
16 16 | x = set(x for x in range(3))
|
||||
17 |-x = set(
|
||||
18 |- x for x in range(3)
|
||||
19 |-)
|
||||
17 |+x = set(range(3))
|
||||
20 18 | print(f"Hello {set(a for a in range(3))} World")
|
||||
21 19 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
22 20 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
|
||||
C401.py:20:16: C401 [*] Unnecessary generator (rewrite using `set()`
|
||||
|
|
||||
18 | x for x in range(3)
|
||||
19 | )
|
||||
20 | print(f"Hello {set(a for a in range(3))} World")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
15 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
16 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
21 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
22 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
|
|
||||
= help: Rewrite as a `set` comprehension
|
||||
= help: Rewrite using `set()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
11 11 |
|
||||
12 12 | print(f'Hello {set(a for a in "abc")} World')
|
||||
13 13 | print(f"Hello {set(a for a in 'abc')} World")
|
||||
14 |-print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
14 |+print(f"Hello { {f(a) for a in 'abc'} } World")
|
||||
15 15 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
16 16 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
17 17 |
|
||||
17 17 | x = set(
|
||||
18 18 | x for x in range(3)
|
||||
19 19 | )
|
||||
20 |-print(f"Hello {set(a for a in range(3))} World")
|
||||
20 |+print(f"Hello {set(range(3))} World")
|
||||
21 21 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
22 22 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
23 23 |
|
||||
|
||||
C401.py:15:10: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
C401.py:21:10: C401 [*] Unnecessary generator (rewrite using `set()`
|
||||
|
|
||||
13 | print(f"Hello {set(a for a in 'abc')} World")
|
||||
14 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
15 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
19 | )
|
||||
20 | print(f"Hello {set(a for a in range(3))} World")
|
||||
21 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
16 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
22 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
|
|
||||
= help: Rewrite as a `set` comprehension
|
||||
= help: Rewrite using `set()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
12 12 | print(f'Hello {set(a for a in "abc")} World')
|
||||
13 13 | print(f"Hello {set(a for a in 'abc')} World")
|
||||
14 14 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
15 |-print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
15 |+print(f"{ {a for a in 'abc'} - set(a for a in 'ab')}")
|
||||
16 16 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
17 17 |
|
||||
18 18 | # The fix generated for this diagnostic is incorrect, as we add additional space
|
||||
18 18 | x for x in range(3)
|
||||
19 19 | )
|
||||
20 20 | print(f"Hello {set(a for a in range(3))} World")
|
||||
21 |-print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
21 |+print(f"{set('abc') - set(a for a in 'ab')}")
|
||||
22 22 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
23 23 |
|
||||
24 24 |
|
||||
|
||||
C401.py:15:34: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
C401.py:21:34: C401 [*] Unnecessary generator (rewrite using `set()`
|
||||
|
|
||||
13 | print(f"Hello {set(a for a in 'abc')} World")
|
||||
14 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
15 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
19 | )
|
||||
20 | print(f"Hello {set(a for a in range(3))} World")
|
||||
21 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^ C401
|
||||
16 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
22 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
|
|
||||
= help: Rewrite as a `set` comprehension
|
||||
= help: Rewrite using `set()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
12 12 | print(f'Hello {set(a for a in "abc")} World')
|
||||
13 13 | print(f"Hello {set(a for a in 'abc')} World")
|
||||
14 14 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
15 |-print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
15 |+print(f"{set(a for a in 'abc') - {a for a in 'ab'} }")
|
||||
16 16 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
17 17 |
|
||||
18 18 | # The fix generated for this diagnostic is incorrect, as we add additional space
|
||||
18 18 | x for x in range(3)
|
||||
19 19 | )
|
||||
20 20 | print(f"Hello {set(a for a in range(3))} World")
|
||||
21 |-print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
21 |+print(f"{set(a for a in 'abc') - set('ab')}")
|
||||
22 22 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
23 23 |
|
||||
24 24 |
|
||||
|
||||
C401.py:16:11: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
C401.py:22:11: C401 [*] Unnecessary generator (rewrite using `set()`
|
||||
|
|
||||
14 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
15 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
16 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
20 | print(f"Hello {set(a for a in range(3))} World")
|
||||
21 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
22 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
17 |
|
||||
18 | # The fix generated for this diagnostic is incorrect, as we add additional space
|
||||
|
|
||||
= help: Rewrite as a `set` comprehension
|
||||
= help: Rewrite using `set()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
13 13 | print(f"Hello {set(a for a in 'abc')} World")
|
||||
14 14 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
15 15 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
16 |-print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
16 |+print(f"{ {a for a in 'abc'} - set(a for a in 'ab') }")
|
||||
17 17 |
|
||||
18 18 | # The fix generated for this diagnostic is incorrect, as we add additional space
|
||||
19 19 | # around the set comprehension.
|
||||
19 19 | )
|
||||
20 20 | print(f"Hello {set(a for a in range(3))} World")
|
||||
21 21 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
22 |-print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
22 |+print(f"{ set('abc') - set(a for a in 'ab') }")
|
||||
23 23 |
|
||||
24 24 |
|
||||
25 25 | # Not built-in set.
|
||||
|
||||
C401.py:16:35: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
C401.py:22:35: C401 [*] Unnecessary generator (rewrite using `set()`
|
||||
|
|
||||
14 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
15 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
16 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
20 | print(f"Hello {set(a for a in range(3))} World")
|
||||
21 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
22 | print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
| ^^^^^^^^^^^^^^^^^^^^ C401
|
||||
17 |
|
||||
18 | # The fix generated for this diagnostic is incorrect, as we add additional space
|
||||
|
|
||||
= help: Rewrite as a `set` comprehension
|
||||
= help: Rewrite using `set()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
13 13 | print(f"Hello {set(a for a in 'abc')} World")
|
||||
14 14 | print(f"Hello {set(f(a) for a in 'abc')} World")
|
||||
15 15 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
16 |-print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
16 |+print(f"{ set(a for a in 'abc') - {a for a in 'ab'} }")
|
||||
17 17 |
|
||||
18 18 | # The fix generated for this diagnostic is incorrect, as we add additional space
|
||||
19 19 | # around the set comprehension.
|
||||
|
||||
C401.py:20:12: C401 [*] Unnecessary generator (rewrite as a `set` comprehension)
|
||||
|
|
||||
18 | # The fix generated for this diagnostic is incorrect, as we add additional space
|
||||
19 | # around the set comprehension.
|
||||
20 | print(f"{ {set(a for a in 'abc')} }")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ C401
|
||||
|
|
||||
= help: Rewrite as a `set` comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
17 17 |
|
||||
18 18 | # The fix generated for this diagnostic is incorrect, as we add additional space
|
||||
19 19 | # around the set comprehension.
|
||||
20 |-print(f"{ {set(a for a in 'abc')} }")
|
||||
20 |+print(f"{ { {a for a in 'abc'} } }")
|
||||
|
||||
|
||||
19 19 | )
|
||||
20 20 | print(f"Hello {set(a for a in range(3))} World")
|
||||
21 21 | print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
|
||||
22 |-print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
|
||||
22 |+print(f"{ set(a for a in 'abc') - set('ab') }")
|
||||
23 23 |
|
||||
24 24 |
|
||||
25 25 | # Not built-in set.
|
||||
|
||||
@@ -98,67 +98,65 @@ C419.py:9:5: C419 [*] Unnecessary list comprehension
|
||||
11 11 | # OK
|
||||
12 12 | all(x.id for x in bar)
|
||||
|
||||
C419.py:24:5: C419 [*] Unnecessary list comprehension
|
||||
C419.py:28:5: C419 [*] Unnecessary list comprehension
|
||||
|
|
||||
22 | # Special comment handling
|
||||
23 | any(
|
||||
24 | [ # lbracket comment
|
||||
26 | # Special comment handling
|
||||
27 | any(
|
||||
28 | [ # lbracket comment
|
||||
| _____^
|
||||
25 | | # second line comment
|
||||
26 | | i.bit_count()
|
||||
27 | | # random middle comment
|
||||
28 | | for i in range(5) # rbracket comment
|
||||
29 | | ] # rpar comment
|
||||
29 | | # second line comment
|
||||
30 | | i.bit_count()
|
||||
31 | | # random middle comment
|
||||
32 | | for i in range(5) # rbracket comment
|
||||
33 | | ] # rpar comment
|
||||
| |_____^ C419
|
||||
30 | # trailing comment
|
||||
31 | )
|
||||
34 | # trailing comment
|
||||
35 | )
|
||||
|
|
||||
= help: Remove unnecessary list comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
21 21 |
|
||||
22 22 | # Special comment handling
|
||||
23 23 | any(
|
||||
24 |- [ # lbracket comment
|
||||
25 |- # second line comment
|
||||
26 |- i.bit_count()
|
||||
24 |+ # lbracket comment
|
||||
25 |+ # second line comment
|
||||
26 |+ i.bit_count()
|
||||
27 27 | # random middle comment
|
||||
28 |- for i in range(5) # rbracket comment
|
||||
29 |- ] # rpar comment
|
||||
28 |+ for i in range(5) # rbracket comment # rpar comment
|
||||
30 29 | # trailing comment
|
||||
31 30 | )
|
||||
32 31 |
|
||||
25 25 |
|
||||
26 26 | # Special comment handling
|
||||
27 27 | any(
|
||||
28 |- [ # lbracket comment
|
||||
29 |- # second line comment
|
||||
30 |- i.bit_count()
|
||||
28 |+ # lbracket comment
|
||||
29 |+ # second line comment
|
||||
30 |+ i.bit_count()
|
||||
31 31 | # random middle comment
|
||||
32 |- for i in range(5) # rbracket comment
|
||||
33 |- ] # rpar comment
|
||||
32 |+ for i in range(5) # rbracket comment # rpar comment
|
||||
34 33 | # trailing comment
|
||||
35 34 | )
|
||||
36 35 |
|
||||
|
||||
C419.py:35:5: C419 [*] Unnecessary list comprehension
|
||||
C419.py:39:5: C419 [*] Unnecessary list comprehension
|
||||
|
|
||||
33 | # Weird case where the function call, opening bracket, and comment are all
|
||||
34 | # on the same line.
|
||||
35 | any([ # lbracket comment
|
||||
37 | # Weird case where the function call, opening bracket, and comment are all
|
||||
38 | # on the same line.
|
||||
39 | any([ # lbracket comment
|
||||
| _____^
|
||||
36 | | # second line comment
|
||||
37 | | i.bit_count() for i in range(5) # rbracket comment
|
||||
38 | | ] # rpar comment
|
||||
40 | | # second line comment
|
||||
41 | | i.bit_count() for i in range(5) # rbracket comment
|
||||
42 | | ] # rpar comment
|
||||
| |_____^ C419
|
||||
39 | )
|
||||
43 | )
|
||||
|
|
||||
= help: Remove unnecessary list comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
32 32 |
|
||||
33 33 | # Weird case where the function call, opening bracket, and comment are all
|
||||
34 34 | # on the same line.
|
||||
35 |-any([ # lbracket comment
|
||||
36 |- # second line comment
|
||||
37 |- i.bit_count() for i in range(5) # rbracket comment
|
||||
38 |- ] # rpar comment
|
||||
35 |+any(
|
||||
36 |+# lbracket comment
|
||||
37 |+# second line comment
|
||||
38 |+i.bit_count() for i in range(5) # rbracket comment # rpar comment
|
||||
39 39 | )
|
||||
|
||||
|
||||
36 36 |
|
||||
37 37 | # Weird case where the function call, opening bracket, and comment are all
|
||||
38 38 | # on the same line.
|
||||
39 |-any([ # lbracket comment
|
||||
40 |- # second line comment
|
||||
41 |- i.bit_count() for i in range(5) # rbracket comment
|
||||
42 |- ] # rpar comment
|
||||
39 |+any(
|
||||
40 |+# lbracket comment
|
||||
41 |+# second line comment
|
||||
42 |+i.bit_count() for i in range(5) # rbracket comment # rpar comment
|
||||
43 43 | )
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_comprehensions/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_comprehensions/mod.rs
|
||||
---
|
||||
C419_1.py:1:5: C419 [*] Unnecessary list comprehension
|
||||
|
|
||||
1 | sum([x.val for x in bar])
|
||||
| ^^^^^^^^^^^^^^^^^^^^ C419
|
||||
2 | min([x.val for x in bar])
|
||||
3 | max([x.val for x in bar])
|
||||
|
|
||||
= help: Remove unnecessary list comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |-sum([x.val for x in bar])
|
||||
1 |+sum(x.val for x in bar)
|
||||
2 2 | min([x.val for x in bar])
|
||||
3 3 | max([x.val for x in bar])
|
||||
4 4 |
|
||||
|
||||
C419_1.py:2:5: C419 [*] Unnecessary list comprehension
|
||||
|
|
||||
1 | sum([x.val for x in bar])
|
||||
2 | min([x.val for x in bar])
|
||||
| ^^^^^^^^^^^^^^^^^^^^ C419
|
||||
3 | max([x.val for x in bar])
|
||||
|
|
||||
= help: Remove unnecessary list comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | sum([x.val for x in bar])
|
||||
2 |-min([x.val for x in bar])
|
||||
2 |+min(x.val for x in bar)
|
||||
3 3 | max([x.val for x in bar])
|
||||
4 4 |
|
||||
5 5 | # Ok
|
||||
|
||||
C419_1.py:3:5: C419 [*] Unnecessary list comprehension
|
||||
|
|
||||
1 | sum([x.val for x in bar])
|
||||
2 | min([x.val for x in bar])
|
||||
3 | max([x.val for x in bar])
|
||||
| ^^^^^^^^^^^^^^^^^^^^ C419
|
||||
4 |
|
||||
5 | # Ok
|
||||
|
|
||||
= help: Remove unnecessary list comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | sum([x.val for x in bar])
|
||||
2 2 | min([x.val for x in bar])
|
||||
3 |-max([x.val for x in bar])
|
||||
3 |+max(x.val for x in bar)
|
||||
4 4 |
|
||||
5 5 | # Ok
|
||||
6 6 | sum(x.val for x in bar)
|
||||
@@ -71,6 +71,20 @@ import os
|
||||
r"
|
||||
# Copyright (C) 2021-2023
|
||||
|
||||
import os
|
||||
"
|
||||
.trim(),
|
||||
&settings::LinterSettings::for_rules(vec![Rule::MissingCopyrightNotice]),
|
||||
);
|
||||
assert_messages!(diagnostics);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notice_with_comma() {
|
||||
let diagnostics = test_snippet(
|
||||
r"
|
||||
# Copyright (C) 2021, 2022
|
||||
|
||||
import os
|
||||
"
|
||||
.trim(),
|
||||
@@ -85,6 +99,126 @@ import os
|
||||
r"
|
||||
# Copyright (C) 2023 Ruff
|
||||
|
||||
import os
|
||||
"
|
||||
.trim(),
|
||||
&settings::LinterSettings {
|
||||
flake8_copyright: super::settings::Settings {
|
||||
author: Some("Ruff".to_string()),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
..settings::LinterSettings::for_rules(vec![Rule::MissingCopyrightNotice])
|
||||
},
|
||||
);
|
||||
assert_messages!(diagnostics);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_author_with_dash() {
|
||||
let diagnostics = test_snippet(
|
||||
r"
|
||||
# Copyright (C) 2022-2023 Ruff
|
||||
|
||||
import os
|
||||
"
|
||||
.trim(),
|
||||
&settings::LinterSettings {
|
||||
flake8_copyright: super::settings::Settings {
|
||||
author: Some("Ruff".to_string()),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
..settings::LinterSettings::for_rules(vec![Rule::MissingCopyrightNotice])
|
||||
},
|
||||
);
|
||||
assert_messages!(diagnostics);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_author_with_dash_invalid_space() {
|
||||
let diagnostics = test_snippet(
|
||||
r"
|
||||
# Copyright (C) 2022- 2023 Ruff
|
||||
|
||||
import os
|
||||
"
|
||||
.trim(),
|
||||
&settings::LinterSettings {
|
||||
flake8_copyright: super::settings::Settings {
|
||||
author: Some("Ruff".to_string()),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
..settings::LinterSettings::for_rules(vec![Rule::MissingCopyrightNotice])
|
||||
},
|
||||
);
|
||||
assert_messages!(diagnostics);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_author_with_dash_invalid_spaces() {
|
||||
let diagnostics = test_snippet(
|
||||
r"
|
||||
# Copyright (C) 2022 - 2023 Ruff
|
||||
|
||||
import os
|
||||
"
|
||||
.trim(),
|
||||
&settings::LinterSettings {
|
||||
flake8_copyright: super::settings::Settings {
|
||||
author: Some("Ruff".to_string()),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
..settings::LinterSettings::for_rules(vec![Rule::MissingCopyrightNotice])
|
||||
},
|
||||
);
|
||||
assert_messages!(diagnostics);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_author_with_comma_invalid_no_space() {
|
||||
let diagnostics = test_snippet(
|
||||
r"
|
||||
# Copyright (C) 2022,2023 Ruff
|
||||
|
||||
import os
|
||||
"
|
||||
.trim(),
|
||||
&settings::LinterSettings {
|
||||
flake8_copyright: super::settings::Settings {
|
||||
author: Some("Ruff".to_string()),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
..settings::LinterSettings::for_rules(vec![Rule::MissingCopyrightNotice])
|
||||
},
|
||||
);
|
||||
assert_messages!(diagnostics);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_author_with_comma_invalid_spaces() {
|
||||
let diagnostics = test_snippet(
|
||||
r"
|
||||
# Copyright (C) 2022 , 2023 Ruff
|
||||
|
||||
import os
|
||||
"
|
||||
.trim(),
|
||||
&settings::LinterSettings {
|
||||
flake8_copyright: super::settings::Settings {
|
||||
author: Some("Ruff".to_string()),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
..settings::LinterSettings::for_rules(vec![Rule::MissingCopyrightNotice])
|
||||
},
|
||||
);
|
||||
assert_messages!(diagnostics);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_author_with_comma_valid_space() {
|
||||
let diagnostics = test_snippet(
|
||||
r"
|
||||
# Copyright (C) 2022, 2023 Ruff
|
||||
|
||||
import os
|
||||
"
|
||||
.trim(),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user