Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2315db7d13 | ||
|
|
f1a183c171 | ||
|
|
509c6d5ec7 | ||
|
|
6695988b59 | ||
|
|
e3867b172d | ||
|
|
4b8e30f350 | ||
|
|
8fd0d8e9d8 | ||
|
|
70895a8f1e | ||
|
|
f2c9f94f73 | ||
|
|
605c6069e2 | ||
|
|
92c2981b6d | ||
|
|
4ad8db3d61 | ||
|
|
0e8c237167 | ||
|
|
960c5e2006 | ||
|
|
9ba17fbf92 | ||
|
|
bfdf972a5d | ||
|
|
0c215365ae | ||
|
|
815284f890 | ||
|
|
6880338a9a | ||
|
|
68b749c67d | ||
|
|
c0fc55b812 | ||
|
|
ba9cf70917 | ||
|
|
f73dfbbfd3 | ||
|
|
62c273cd22 | ||
|
|
938ad9a39e | ||
|
|
14248cb8cb | ||
|
|
3a280039e1 | ||
|
|
4e9e58bdc0 | ||
|
|
926b5494ad | ||
|
|
6717b48ca5 | ||
|
|
f7bb5bc858 | ||
|
|
3e23fd1487 | ||
|
|
01c74e0629 | ||
|
|
1e3cf87f67 | ||
|
|
248447e139 | ||
|
|
95f139583a | ||
|
|
74903f23d6 | ||
|
|
3ee20a70d3 | ||
|
|
4c2fbb7ac0 | ||
|
|
7a66f98590 | ||
|
|
a2bf3916f3 | ||
|
|
c9aa7b9308 | ||
|
|
d880ca6cc6 | ||
|
|
080f99b908 | ||
|
|
a7dc491ff1 | ||
|
|
cdc8f8c91a | ||
|
|
138c46e793 | ||
|
|
a86c57a832 | ||
|
|
818582fe8a | ||
|
|
90574c1088 | ||
|
|
3061a35e7c | ||
|
|
87681697ae | ||
|
|
e9ec2a7b36 | ||
|
|
b0bb75dc1c | ||
|
|
ebca5c2df8 | ||
|
|
16b10c42f0 | ||
|
|
4a6e5d1549 | ||
|
|
b078050732 | ||
|
|
5f796b39b4 | ||
|
|
9d34da23bd | ||
|
|
bde12c3bb3 | ||
|
|
f735660801 | ||
|
|
34cd22dfc1 | ||
|
|
9fafe16a55 | ||
|
|
e9a4cb1c1d | ||
|
|
9db825c731 | ||
|
|
2c7464604a | ||
|
|
cd2099f772 | ||
|
|
091d36cd30 | ||
|
|
02f156c6cb | ||
|
|
9f7350961e | ||
|
|
118a93260a | ||
|
|
1c16255884 | ||
|
|
16c4552946 | ||
|
|
0ba3989b3d | ||
|
|
3435e15cba | ||
|
|
781bbbc286 | ||
|
|
acf0b82f19 |
9
.github/workflows/ci.yaml
vendored
9
.github/workflows/ci.yaml
vendored
@@ -40,8 +40,7 @@ jobs:
|
||||
- run: git diff --quiet README.md || echo "::error file=README.md::This file is outdated. Run 'cargo +nightly dev generate-all'."
|
||||
- run: git diff --quiet src/checks_gen.rs || echo "::error file=src/checks_gen.rs::This file is outdated. Run 'cargo +nightly dev generate-all'."
|
||||
- run: git diff --quiet ruff.schema.json || echo "::error file=ruff.schema.json::This file is outdated. Run 'cargo +nightly dev generate-all'."
|
||||
- run: git diff --quiet playground/src/ruff_options.ts || echo "::error file=playground/src/ruff_options.ts::This file is outdated. Run 'cargo +nightly dev generate-all'."
|
||||
- run: git diff --exit-code -- README.md src/checks_gen.rs ruff.schema.json playground/src/ruff_options.ts
|
||||
- run: git diff --exit-code -- README.md src/checks_gen.rs ruff.schema.json
|
||||
|
||||
cargo-fmt:
|
||||
name: "cargo fmt"
|
||||
@@ -117,8 +116,12 @@ jobs:
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo install cargo-insta
|
||||
- run: pip install black[d]==22.12.0
|
||||
- run: cargo test --all
|
||||
- name: Run tests
|
||||
run: |
|
||||
cargo insta test --all --delete-unreferenced-snapshots
|
||||
git diff --exit-code
|
||||
- run: cargo test --package ruff --test black_compatibility_test -- --ignored
|
||||
|
||||
# TODO(charlie): Re-enable the `wasm-pack` tests.
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -181,3 +181,4 @@ cython_debug/
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
.idea/
|
||||
.vimspector.json
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.199
|
||||
rev: v0.0.206
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
@@ -66,11 +66,13 @@ understand how other, similar rules are implemented.
|
||||
To add a test fixture, create a file under `resources/test/fixtures`, named to match the `CheckCode`
|
||||
you defined earlier (e.g., `E402.py`). This file should contain a variety of violations and
|
||||
non-violations designed to evaluate and demonstrate the behavior of your lint rule. Run Ruff locally
|
||||
with (e.g.) `cargo run resources/test/fixtures/E402.py --no-cache`. Once you're satisfied with the
|
||||
with (e.g.) `cargo run resources/test/fixtures/E402.py --no-cache --select E402`. Once you're satisfied with the
|
||||
output, codify the behavior as a snapshot test by adding a new `testcase` macro to the `mod tests`
|
||||
section of `src/linter.rs`, like so:
|
||||
|
||||
```rust
|
||||
use test_case::test_case;
|
||||
|
||||
#[test_case(CheckCode::A001, Path::new("A001.py"); "A001")]
|
||||
...
|
||||
```
|
||||
|
||||
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -750,7 +750,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.199-dev.0"
|
||||
version = "0.0.206-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.32",
|
||||
@@ -1878,7 +1878,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.199"
|
||||
version = "0.0.206"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1946,7 +1946,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.199"
|
||||
version = "0.0.206"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.32",
|
||||
@@ -1967,7 +1967,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.199"
|
||||
version = "0.0.206"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2010,7 +2010,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=68d26955b3e24198a150315e7959719b03709dee#68d26955b3e24198a150315e7959719b03709dee"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=71becd4059fdce4bce7010f1208ed3b1c883abba#71becd4059fdce4bce7010f1208ed3b1c883abba"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-common",
|
||||
@@ -2020,7 +2020,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-common"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=68d26955b3e24198a150315e7959719b03709dee#68d26955b3e24198a150315e7959719b03709dee"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=71becd4059fdce4bce7010f1208ed3b1c883abba#71becd4059fdce4bce7010f1208ed3b1c883abba"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -2043,7 +2043,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=68d26955b3e24198a150315e7959719b03709dee#68d26955b3e24198a150315e7959719b03709dee"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=71becd4059fdce4bce7010f1208ed3b1c883abba#71becd4059fdce4bce7010f1208ed3b1c883abba"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -2060,7 +2060,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=68d26955b3e24198a150315e7959719b03709dee#68d26955b3e24198a150315e7959719b03709dee"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=71becd4059fdce4bce7010f1208ed3b1c883abba#71becd4059fdce4bce7010f1208ed3b1c883abba"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.199"
|
||||
version = "0.0.206"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
@@ -51,11 +51,11 @@ path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix
|
||||
quick-junit = { version = "0.3.2" }
|
||||
regex = { version = "1.6.0" }
|
||||
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
|
||||
ruff_macros = { version = "0.0.199", path = "ruff_macros" }
|
||||
ruff_macros = { version = "0.0.206", path = "ruff_macros" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "71becd4059fdce4bce7010f1208ed3b1c883abba" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "71becd4059fdce4bce7010f1208ed3b1c883abba" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "71becd4059fdce4bce7010f1208ed3b1c883abba" }
|
||||
schemars = { version = "0.8.11" }
|
||||
semver = { version = "1.0.16" }
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
|
||||
25
LICENSE
25
LICENSE
@@ -388,6 +388,31 @@ are:
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-implicit-str-concat, licensed as follows:
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 Dylan Turner
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-import-conventions, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
120
README.md
120
README.md
@@ -8,7 +8,11 @@
|
||||
An extremely fast Python linter, written in Rust.
|
||||
|
||||
<p align="center">
|
||||
<img alt="Bar chart with benchmark results" src="https://user-images.githubusercontent.com/1309177/187504482-6d9df992-a81d-4e86-9f6a-d958741c8182.svg">
|
||||
<picture align="center">
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/1309177/210156880-a97c2a0d-2c03-4393-8695-36547935a94e.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1309177/210156881-a88fd142-5008-4695-9407-d028cec3eff7.svg">
|
||||
<img alt="Shows a bar chart with benchmark results." src="https://user-images.githubusercontent.com/1309177/210156881-a88fd142-5008-4695-9407-d028cec3eff7.svg">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -44,6 +48,7 @@ Ruff is extremely actively developed and used in major open-source projects like
|
||||
- [Bokeh](https://github.com/bokeh/bokeh)
|
||||
- [Zulip](https://github.com/zulip/zulip)
|
||||
- [Pydantic](https://github.com/pydantic/pydantic)
|
||||
- [Sphinx](https://github.com/sphinx-doc/sphinx)
|
||||
- [Hatch](https://github.com/pypa/hatch)
|
||||
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
|
||||
- [Synapse](https://github.com/matrix-org/synapse)
|
||||
@@ -94,6 +99,7 @@ of [Conda](https://docs.conda.io/en/latest/):
|
||||
1. [flake8-comprehensions (C4)](#flake8-comprehensions-c4)
|
||||
1. [flake8-debugger (T10)](#flake8-debugger-t10)
|
||||
1. [flake8-errmsg (EM)](#flake8-errmsg-em)
|
||||
1. [flake8-implicit-str-concat (ISC)](#flake8-implicit-str-concat-isc)
|
||||
1. [flake8-import-conventions (ICN)](#flake8-import-conventions-icn)
|
||||
1. [flake8-print (T20)](#flake8-print-t20)
|
||||
1. [flake8-quotes (Q)](#flake8-quotes-q)
|
||||
@@ -167,7 +173,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.199'
|
||||
rev: 'v0.0.206'
|
||||
hooks:
|
||||
- id: ruff
|
||||
# Respect `exclude` and `extend-exclude` settings.
|
||||
@@ -298,6 +304,7 @@ ruff path/to/code/ --select F401 --select F403
|
||||
|
||||
See `ruff --help` for more:
|
||||
|
||||
<!-- Begin auto-generated cli help. -->
|
||||
```shell
|
||||
Ruff: An extremely fast Python linter.
|
||||
|
||||
@@ -328,11 +335,11 @@ Options:
|
||||
-n, --no-cache
|
||||
Disable cache reads
|
||||
--select <SELECT>
|
||||
List of error codes to enable
|
||||
Comma-separated list of error codes to enable (or ALL, to enable all checks)
|
||||
--extend-select <EXTEND_SELECT>
|
||||
Like --select, but adds additional error codes on top of the selected ones
|
||||
--ignore <IGNORE>
|
||||
List of error codes to ignore
|
||||
Comma-separated list of error codes to disable
|
||||
--extend-ignore <EXTEND_IGNORE>
|
||||
Like --ignore, but adds additional error codes on top of the ignored ones
|
||||
--exclude <EXCLUDE>
|
||||
@@ -347,18 +354,18 @@ Options:
|
||||
List of mappings from file pattern to code to exclude
|
||||
--format <FORMAT>
|
||||
Output serialization format for error messages [possible values: text, json, junit, grouped, github, gitlab]
|
||||
--stdin-filename <STDIN_FILENAME>
|
||||
The name of the file when passing it through stdin
|
||||
--cache-dir <CACHE_DIR>
|
||||
Path to the cache directory
|
||||
--show-source
|
||||
Show violations with source code
|
||||
--respect-gitignore
|
||||
Respect file exclusions via `.gitignore` and other standard ignore files
|
||||
--force-exclude
|
||||
Enforce exclusions, even for paths passed to Ruff directly on the command-line
|
||||
--show-files
|
||||
See the files Ruff will be run against with the current settings
|
||||
--show-settings
|
||||
See the settings Ruff will use to check a given Python file
|
||||
--add-noqa
|
||||
Enable automatic additions of noqa directives to failing lines
|
||||
--update-check
|
||||
Enable or disable automatic update checks
|
||||
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
|
||||
Regular expression matching the name of dummy variables
|
||||
--target-version <TARGET_VERSION>
|
||||
@@ -366,18 +373,23 @@ Options:
|
||||
--line-length <LINE_LENGTH>
|
||||
Set the line-length for length-associated checks and automatic formatting
|
||||
--max-complexity <MAX_COMPLEXITY>
|
||||
Max McCabe complexity allowed for a function
|
||||
--stdin-filename <STDIN_FILENAME>
|
||||
The name of the file when passing it through stdin
|
||||
Maximum McCabe complexity allowed for a given function
|
||||
--add-noqa
|
||||
Enable automatic additions of `noqa` directives to failing lines
|
||||
--clean
|
||||
Clear any caches in the current directory or any subdirectories
|
||||
--explain <EXPLAIN>
|
||||
Explain a rule
|
||||
--cache-dir <CACHE_DIR>
|
||||
Path to the cache directory
|
||||
--show-files
|
||||
See the files Ruff will be run against with the current settings
|
||||
--show-settings
|
||||
See the settings Ruff will use to check a given Python file
|
||||
-h, --help
|
||||
Print help information
|
||||
-V, --version
|
||||
Print version information
|
||||
```
|
||||
<!-- End auto-generated cli help. -->
|
||||
|
||||
### `pyproject.toml` discovery
|
||||
|
||||
@@ -537,7 +549,7 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
|
||||
| F621 | ExpressionsInStarAssignment | Too many expressions in star-unpacking assignment | |
|
||||
| F622 | TwoStarredExpressions | Two starred expressions in assignment | |
|
||||
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` | |
|
||||
| F632 | IsLiteral | Use `==` and `!=` to compare constant literals | 🛠 |
|
||||
| F632 | IsLiteral | Use `==` to compare constant literals | 🛠 |
|
||||
| F633 | InvalidPrintSyntax | Use of `>>` is invalid with `print` function | |
|
||||
| F634 | IfTuple | If test is a tuple, which is always `True` | |
|
||||
| F701 | BreakOutsideLoop | `break` outside loop | |
|
||||
@@ -550,7 +562,6 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
|
||||
| F821 | UndefinedName | Undefined name `...` | |
|
||||
| F822 | UndefinedExport | Undefined name `...` in `__all__` | |
|
||||
| F823 | UndefinedLocal | Local variable `...` referenced before assignment | |
|
||||
| F831 | DuplicateArgumentName | Duplicate argument name in function definition | |
|
||||
| F841 | UnusedVariable | Local variable `...` is assigned to but never used | |
|
||||
| F842 | UnusedAnnotation | Local variable `...` is annotated but never used | |
|
||||
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | 🛠 |
|
||||
@@ -570,7 +581,7 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI
|
||||
| E714 | NotIsTest | Test for object identity should be `is not` | 🛠 |
|
||||
| E721 | TypeComparison | Do not compare types, use `isinstance()` | |
|
||||
| E722 | DoNotUseBareExcept | Do not use bare `except` | |
|
||||
| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def | 🛠 |
|
||||
| E731 | DoNotAssignLambda | Do not assign a `lambda` expression, use a `def` | 🛠 |
|
||||
| E741 | AmbiguousVariableName | Ambiguous variable name: `...` | |
|
||||
| E742 | AmbiguousClassName | Ambiguous class name: `...` | |
|
||||
| E743 | AmbiguousFunctionName | Ambiguous function name: `...` | |
|
||||
@@ -655,7 +666,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
|
||||
| ---- | ---- | ------- | --- |
|
||||
| UP001 | UselessMetaclassType | `__metaclass__ = type` is implied | 🛠 |
|
||||
| UP003 | TypeOfPrimitive | Use `str` instead of `type(...)` | 🛠 |
|
||||
| UP004 | UselessObjectInheritance | Class `...` inherits from object | 🛠 |
|
||||
| UP004 | UselessObjectInheritance | Class `...` inherits from `object` | 🛠 |
|
||||
| UP005 | DeprecatedUnittestAlias | `assertEquals` is deprecated, use `assertEqual` | 🛠 |
|
||||
| UP006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | 🛠 |
|
||||
| UP007 | UsePEP604Annotation | Use `X \| Y` for type annotations | 🛠 |
|
||||
@@ -669,12 +680,15 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
|
||||
| UP015 | RedundantOpenModes | Unnecessary open mode parameters | 🛠 |
|
||||
| UP016 | RemoveSixCompat | Unnecessary `six` compatibility usage | 🛠 |
|
||||
| UP017 | DatetimeTimezoneUTC | Use `datetime.UTC` alias | 🛠 |
|
||||
| UP018 | NativeLiterals | Unnecessary call to `str` and `bytes` | 🛠 |
|
||||
| UP018 | NativeLiterals | Unnecessary call to `str` | 🛠 |
|
||||
| UP019 | TypingTextStrAlias | `typing.Text` is deprecated, use `str` | 🛠 |
|
||||
| UP020 | OpenAlias | Use builtin `open` | 🛠 |
|
||||
| UP021 | ReplaceUniversalNewlines | `universal_newlines` is deprecated, use `text` | 🛠 |
|
||||
| UP022 | ReplaceStdoutStderr | Sending stdout and stderr to pipe is deprecated, use `capture_output` | 🛠 |
|
||||
| UP023 | RewriteCElementTree | `cElementTree` is deprecated, use `ElementTree` | 🛠 |
|
||||
| UP024 | OSErrorAlias | Replace aliased errors with `OSError` | 🛠 |
|
||||
| UP025 | RewriteUnicodeLiteral | Remove unicode literals from strings | 🛠 |
|
||||
| UP026 | RewriteMockImport | `mock` is deprecated, use `unittest.mock` | 🛠 |
|
||||
|
||||
### pep8-naming (N)
|
||||
|
||||
@@ -850,6 +864,16 @@ For more, see [flake8-errmsg](https://pypi.org/project/flake8-errmsg/0.4.0/) on
|
||||
| EM102 | FStringInException | Exception must not use an f-string literal, assign to variable first | |
|
||||
| EM103 | DotFormatInException | Exception must not use a `.format()` string directly, assign to variable first | |
|
||||
|
||||
### flake8-implicit-str-concat (ISC)
|
||||
|
||||
For more, see [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/0.3.0/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| ISC001 | SingleLineImplicitStringConcatenation | Implicitly concatenated string literals on one line | |
|
||||
| ISC002 | MultiLineImplicitStringConcatenation | Implicitly concatenated string literals over continuation line | |
|
||||
| ISC003 | ExplicitStringConcatenation | Explicitly concatenated string should be implicitly concatenated | |
|
||||
|
||||
### flake8-import-conventions (ICN)
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
@@ -905,6 +929,7 @@ For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| TID251 | BannedApi | `...` is banned: ... | |
|
||||
| TID252 | BannedRelativeImport | Relative imports are banned | |
|
||||
|
||||
### flake8-unused-arguments (ARG)
|
||||
@@ -971,6 +996,7 @@ For more, see [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks) on GitH
|
||||
| PGH001 | NoEval | No builtin `eval()` allowed | |
|
||||
| PGH002 | DeprecatedLogWarn | `warn` is deprecated in favor of `warning` | |
|
||||
| PGH003 | BlanketTypeIgnore | Use specific error codes when ignoring type issues | |
|
||||
| PGH004 | BlanketNOQA | Use specific error codes when using `noqa` | |
|
||||
|
||||
### Pylint (PLC, PLE, PLR, PLW)
|
||||
|
||||
@@ -997,7 +1023,7 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
|
||||
| ---- | ---- | ------- | --- |
|
||||
| RUF001 | AmbiguousUnicodeCharacterString | String contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
|
||||
| RUF002 | AmbiguousUnicodeCharacterDocstring | Docstring contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
|
||||
| RUF003 | AmbiguousUnicodeCharacterComment | Comment contains ambiguous unicode character '𝐁' (did you mean 'B'?) | |
|
||||
| RUF003 | AmbiguousUnicodeCharacterComment | Comment contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
|
||||
| RUF004 | KeywordArgumentBeforeStarArgument | Keyword argument `...` must come after starred arguments | |
|
||||
| RUF100 | UnusedNOQA | Unused blanket `noqa` directive | 🛠 |
|
||||
|
||||
@@ -1268,10 +1294,12 @@ natively, including:
|
||||
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
|
||||
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
|
||||
- [`flake8-errmsg`](https://pypi.org/project/flake8-errmsg/)
|
||||
- [`flake8-implicit-str-concat`](https://pypi.org/project/flake8-implicit-str-concat/)
|
||||
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
- [`flake8-return`](https://pypi.org/project/flake8-return/)
|
||||
- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) (1/37)
|
||||
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
||||
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
|
||||
- [`isort`](https://pypi.org/project/isort/)
|
||||
@@ -1279,7 +1307,7 @@ natively, including:
|
||||
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
|
||||
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
|
||||
- [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (20/33)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (21/33)
|
||||
- [`yesqa`](https://github.com/asottile/yesqa)
|
||||
|
||||
Note that, in some cases, Ruff uses different error code prefixes than would be found in the
|
||||
@@ -1323,10 +1351,12 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
|
||||
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
|
||||
- [`flake8-errmsg`](https://pypi.org/project/flake8-errmsg/)
|
||||
- [`flake8-implicit-str-concat`](https://pypi.org/project/flake8-implicit-str-concat/)
|
||||
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
- [`flake8-return`](https://pypi.org/project/flake8-return/)
|
||||
- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) (1/37)
|
||||
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
||||
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
|
||||
- [`mccabe`](https://pypi.org/project/mccabe/)
|
||||
@@ -1336,7 +1366,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
Ruff can also replace [`isort`](https://pypi.org/project/isort/),
|
||||
[`yesqa`](https://github.com/asottile/yesqa), [`eradicate`](https://pypi.org/project/eradicate/),
|
||||
[`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10), and a subset of the rules
|
||||
implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (20/33).
|
||||
implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (21/33).
|
||||
|
||||
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
|
||||
|
||||
@@ -1648,7 +1678,6 @@ Summary
|
||||
|
||||
<!-- Sections automatically generated by `cargo dev generate-options`. -->
|
||||
<!-- Begin auto-generated options sections. -->
|
||||
|
||||
#### [`allowed-confusables`](#allowed-confusables)
|
||||
|
||||
A list of allowed "confusable" Unicode characters to ignore when
|
||||
@@ -2197,6 +2226,24 @@ unfixable = ["F401"]
|
||||
|
||||
---
|
||||
|
||||
#### [`update-check`](#update-check)
|
||||
|
||||
Enable or disable automatic update checks (overridden by the
|
||||
`--update-check` and `--no-update-check` command-line flags).
|
||||
|
||||
**Default value**: `true`
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
update-check = false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `flake8-annotations`
|
||||
|
||||
#### [`allow-star-arg-any`](#allow-star-arg-any)
|
||||
@@ -2440,7 +2487,7 @@ multiline-quotes = "single"
|
||||
#### [`ban-relative-imports`](#ban-relative-imports)
|
||||
|
||||
Whether to ban all relative imports (`"all"`), or only those imports
|
||||
that extend into the parent module and beyond (`"parents"`).
|
||||
that extend into the parent module or beyond (`"parents"`).
|
||||
|
||||
**Default value**: `"parents"`
|
||||
|
||||
@@ -2456,6 +2503,27 @@ ban-relative-imports = "all"
|
||||
|
||||
---
|
||||
|
||||
#### [`banned-api`](#banned-api)
|
||||
|
||||
Specific modules or module members that may not be imported or accessed.
|
||||
Note that this check is only meant to flag accidental uses,
|
||||
and can be circumvented via `eval` or `importlib`.
|
||||
|
||||
**Default value**: `{}`
|
||||
|
||||
**Type**: `HashMap<String, BannedApi>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-tidy-imports]
|
||||
[tool.ruff.flake8-tidy-imports.banned-api]
|
||||
"cgi".msg = "The cgi module is deprecated, see https://peps.python.org/pep-0594/#cgi."
|
||||
"typing.TypedDict".msg = "Use typing_extensions.TypedDict instead."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `flake8-unused-arguments`
|
||||
|
||||
#### [`ignore-variadic-names`](#ignore-variadic-names)
|
||||
@@ -2725,7 +2793,7 @@ Whether to use Google-style or Numpy-style conventions when detecting
|
||||
docstring sections. By default, conventions will be inferred from
|
||||
the available sections.
|
||||
|
||||
**Default value**: `"convention"`
|
||||
**Default value**: `None`
|
||||
|
||||
**Type**: `Convention`
|
||||
|
||||
|
||||
4
flake8_to_ruff/Cargo.lock
generated
4
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.199"
|
||||
version = "0.0.206"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.199"
|
||||
version = "0.0.206"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.199-dev.0"
|
||||
version = "0.0.206-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -164,17 +164,17 @@ pub fn convert(
|
||||
// flake8-quotes
|
||||
"quotes" | "inline-quotes" | "inline_quotes" => match value.trim() {
|
||||
"'" | "single" => flake8_quotes.inline_quotes = Some(Quote::Single),
|
||||
"\"" | "double" => flake8_quotes.inline_quotes = Some(Quote::Single),
|
||||
"\"" | "double" => flake8_quotes.inline_quotes = Some(Quote::Double),
|
||||
_ => eprintln!("Unexpected '{key}' value: {value}"),
|
||||
},
|
||||
"multiline-quotes" | "multiline_quotes" => match value.trim() {
|
||||
"'" | "single" => flake8_quotes.multiline_quotes = Some(Quote::Single),
|
||||
"\"" | "double" => flake8_quotes.multiline_quotes = Some(Quote::Single),
|
||||
"\"" | "double" => flake8_quotes.multiline_quotes = Some(Quote::Double),
|
||||
_ => eprintln!("Unexpected '{key}' value: {value}"),
|
||||
},
|
||||
"docstring-quotes" | "docstring_quotes" => match value.trim() {
|
||||
"'" | "single" => flake8_quotes.docstring_quotes = Some(Quote::Single),
|
||||
"\"" | "double" => flake8_quotes.docstring_quotes = Some(Quote::Single),
|
||||
"\"" | "double" => flake8_quotes.docstring_quotes = Some(Quote::Double),
|
||||
_ => eprintln!("Unexpected '{key}' value: {value}"),
|
||||
},
|
||||
"avoid-escape" | "avoid_escape" => match parser::parse_bool(value.as_ref()) {
|
||||
@@ -296,6 +296,7 @@ mod tests {
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
cache_dir: None,
|
||||
dummy_variable_rgx: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
@@ -323,7 +324,7 @@ mod tests {
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
cache_dir: None,
|
||||
update_check: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
@@ -354,6 +355,7 @@ mod tests {
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
cache_dir: None,
|
||||
dummy_variable_rgx: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
@@ -381,7 +383,7 @@ mod tests {
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
cache_dir: None,
|
||||
update_check: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
@@ -412,6 +414,7 @@ mod tests {
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
cache_dir: None,
|
||||
dummy_variable_rgx: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
@@ -439,7 +442,7 @@ mod tests {
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
cache_dir: None,
|
||||
update_check: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
@@ -470,6 +473,7 @@ mod tests {
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
cache_dir: None,
|
||||
dummy_variable_rgx: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
@@ -497,7 +501,7 @@ mod tests {
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
cache_dir: None,
|
||||
update_check: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
@@ -528,6 +532,7 @@ mod tests {
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
cache_dir: None,
|
||||
dummy_variable_rgx: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
@@ -555,7 +560,7 @@ mod tests {
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
cache_dir: None,
|
||||
update_check: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
@@ -594,6 +599,7 @@ mod tests {
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
cache_dir: None,
|
||||
dummy_variable_rgx: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
@@ -657,7 +663,7 @@ mod tests {
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
cache_dir: None,
|
||||
update_check: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
@@ -690,6 +696,7 @@ mod tests {
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
cache_dir: None,
|
||||
dummy_variable_rgx: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
@@ -718,7 +725,7 @@ mod tests {
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
cache_dir: None,
|
||||
update_check: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
|
||||
@@ -16,16 +16,17 @@ pub enum Plugin {
|
||||
Flake8Datetimez,
|
||||
Flake8Debugger,
|
||||
Flake8Docstrings,
|
||||
Flake8ErrMsg,
|
||||
Flake8Eradicate,
|
||||
Flake8ErrMsg,
|
||||
Flake8ImplicitStrConcat,
|
||||
Flake8Print,
|
||||
Flake8Quotes,
|
||||
Flake8Return,
|
||||
Flake8Simplify,
|
||||
Flake8TidyImports,
|
||||
McCabe,
|
||||
PandasVet,
|
||||
PEP8Naming,
|
||||
PandasVet,
|
||||
Pyupgrade,
|
||||
}
|
||||
|
||||
@@ -45,6 +46,7 @@ impl FromStr for Plugin {
|
||||
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
|
||||
"flake8-eradicate" => Ok(Plugin::Flake8Eradicate),
|
||||
"flake8-errmsg" => Ok(Plugin::Flake8ErrMsg),
|
||||
"flake8-implicit-str-concat" => Ok(Plugin::Flake8ImplicitStrConcat),
|
||||
"flake8-print" => Ok(Plugin::Flake8Print),
|
||||
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
|
||||
"flake8-return" => Ok(Plugin::Flake8Return),
|
||||
@@ -76,14 +78,15 @@ impl fmt::Debug for Plugin {
|
||||
Plugin::Flake8Docstrings => "flake8-docstrings",
|
||||
Plugin::Flake8Eradicate => "flake8-eradicate",
|
||||
Plugin::Flake8ErrMsg => "flake8-errmsg",
|
||||
Plugin::Flake8ImplicitStrConcat => "flake8-implicit-str-concat",
|
||||
Plugin::Flake8Print => "flake8-print",
|
||||
Plugin::Flake8Quotes => "flake8-quotes",
|
||||
Plugin::Flake8Return => "flake8-return",
|
||||
Plugin::Flake8Simplify => "flake8-simplify",
|
||||
Plugin::Flake8TidyImports => "flake8-tidy-imports",
|
||||
Plugin::McCabe => "mccabe",
|
||||
Plugin::PandasVet => "pandas-vet",
|
||||
Plugin::PEP8Naming => "pep8-naming",
|
||||
Plugin::PandasVet => "pandas-vet",
|
||||
Plugin::Pyupgrade => "pyupgrade",
|
||||
}
|
||||
)
|
||||
@@ -106,6 +109,7 @@ impl Plugin {
|
||||
// TODO(charlie): Handle rename of `E` to `ERA`.
|
||||
Plugin::Flake8Eradicate => CheckCodePrefix::ERA,
|
||||
Plugin::Flake8ErrMsg => CheckCodePrefix::EM,
|
||||
Plugin::Flake8ImplicitStrConcat => CheckCodePrefix::ISC,
|
||||
Plugin::Flake8Print => CheckCodePrefix::T2,
|
||||
Plugin::Flake8Quotes => CheckCodePrefix::Q,
|
||||
Plugin::Flake8Return => CheckCodePrefix::RET,
|
||||
@@ -146,6 +150,7 @@ impl Plugin {
|
||||
}
|
||||
Plugin::Flake8Eradicate => vec![CheckCodePrefix::ERA],
|
||||
Plugin::Flake8ErrMsg => vec![CheckCodePrefix::EM],
|
||||
Plugin::Flake8ImplicitStrConcat => vec![CheckCodePrefix::ISC],
|
||||
Plugin::Flake8Print => vec![CheckCodePrefix::T2],
|
||||
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
|
||||
Plugin::Flake8Return => vec![CheckCodePrefix::RET],
|
||||
@@ -449,6 +454,7 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin
|
||||
Plugin::Flake8Docstrings,
|
||||
Plugin::Flake8Eradicate,
|
||||
Plugin::Flake8ErrMsg,
|
||||
Plugin::Flake8ImplicitStrConcat,
|
||||
Plugin::Flake8Print,
|
||||
Plugin::Flake8Quotes,
|
||||
Plugin::Flake8Return,
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
src/ruff_options.ts
|
||||
3
playground/.prettierrc.json
Normal file
3
playground/.prettierrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"trailingComma": "all"
|
||||
}
|
||||
@@ -8,3 +8,7 @@ In-browser playground for Ruff. Available [https://ruff.pages.dev/](https://ruff
|
||||
root directory.
|
||||
- Install TypeScript dependencies with: `npm install`.
|
||||
- Start the development server with: `npm run dev`.
|
||||
|
||||
## Implementation
|
||||
|
||||
Design based on [Tailwind Play](https://play.tailwindcss.com/). Themed with [`ayu`](https://github.com/dempfi/ayu).
|
||||
|
||||
@@ -13,10 +13,11 @@
|
||||
rel="icon"
|
||||
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🛠️</text></svg>"
|
||||
/>
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div style="display: flex; position: fixed; right: 16px; top: 16px">
|
||||
<div style="display: flex; position: fixed; right: 16px; bottom: 16px">
|
||||
<a href="https://GitHub.com/charliermarsh/ruff"
|
||||
><img
|
||||
src="https://img.shields.io/github/stars/charliermarsh/ruff.svg?style=social&label=GitHub&maxAge=2592000&?logoWidth=100"
|
||||
|
||||
915
playground/package-lock.json
generated
915
playground/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"classnames": "^2.3.2",
|
||||
"lz-string": "^1.4.4",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"react": "^18.2.0",
|
||||
@@ -25,13 +26,16 @@
|
||||
"@typescript-eslint/eslint-plugin": "^5.47.1",
|
||||
"@typescript-eslint/parser": "^5.47.1",
|
||||
"@vitejs/plugin-react-swc": "^3.0.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "^8.30.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.31.11",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"postcss": "^8.4.20",
|
||||
"prettier": "^2.8.1",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.0.0"
|
||||
}
|
||||
|
||||
6
playground/postcss.config.cjs
Normal file
6
playground/postcss.config.cjs
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
@@ -1,200 +0,0 @@
|
||||
import lzstring from "lz-string";
|
||||
import Editor, { useMonaco } from "@monaco-editor/react";
|
||||
import { MarkerSeverity } from "monaco-editor/esm/vs/editor/editor.api";
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
|
||||
import init, { Check, check } from "./pkg/ruff.js";
|
||||
import { AVAILABLE_OPTIONS } from "./ruff_options";
|
||||
import { Config, getDefaultConfig, toRuffConfig } from "./config";
|
||||
import { Options } from "./Options";
|
||||
|
||||
const DEFAULT_SOURCE =
|
||||
"# Define a function that takes an integer n and returns the nth number in the Fibonacci\n" +
|
||||
"# sequence.\n" +
|
||||
"def fibonacci(n):\n" +
|
||||
" if n == 0:\n" +
|
||||
" return 0\n" +
|
||||
" elif n == 1:\n" +
|
||||
" return 1\n" +
|
||||
" else:\n" +
|
||||
" return fibonacci(n-1) + fibonacci(n-2)\n" +
|
||||
"\n" +
|
||||
"# Use a for loop to generate and print the first 10 numbers in the Fibonacci sequence.\n" +
|
||||
"for i in range(10):\n" +
|
||||
" print(fibonacci(i))\n" +
|
||||
"\n" +
|
||||
"# Output:\n" +
|
||||
"# 0\n" +
|
||||
"# 1\n" +
|
||||
"# 1\n" +
|
||||
"# 2\n" +
|
||||
"# 3\n" +
|
||||
"# 5\n" +
|
||||
"# 8\n" +
|
||||
"# 13\n" +
|
||||
"# 21\n" +
|
||||
"# 34\n";
|
||||
|
||||
function restoreConfigAndSource(): [Config, string] {
|
||||
const value = lzstring.decompressFromEncodedURIComponent(
|
||||
window.location.hash.slice(1)
|
||||
);
|
||||
let config = {};
|
||||
let source = DEFAULT_SOURCE;
|
||||
|
||||
if (value) {
|
||||
const parts = value.split("$$$");
|
||||
config = JSON.parse(parts[0]);
|
||||
source = parts[1];
|
||||
}
|
||||
|
||||
return [config, source];
|
||||
}
|
||||
|
||||
function persistConfigAndSource(config: Config, source: string) {
|
||||
window.location.hash = lzstring.compressToEncodedURIComponent(
|
||||
JSON.stringify(config) + "$$$" + source
|
||||
);
|
||||
}
|
||||
|
||||
const defaultConfig = getDefaultConfig(AVAILABLE_OPTIONS);
|
||||
|
||||
export default function App() {
|
||||
const monaco = useMonaco();
|
||||
const [initialized, setInitialized] = useState<boolean>(false);
|
||||
const [config, setConfig] = useState<Config | null>(null);
|
||||
const [source, setSource] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
init().then(() => setInitialized(true));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (source == null && config == null && monaco) {
|
||||
const [config, source] = restoreConfigAndSource();
|
||||
setConfig(config);
|
||||
setSource(source);
|
||||
}
|
||||
}, [monaco, source, config]);
|
||||
|
||||
useEffect(() => {
|
||||
if (config != null && source != null) {
|
||||
persistConfigAndSource(config, source);
|
||||
}
|
||||
}, [config, source]);
|
||||
|
||||
useEffect(() => {
|
||||
const editor = monaco?.editor;
|
||||
const model = editor?.getModels()[0];
|
||||
if (!editor || !model || !initialized || source == null || config == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let checks: Check[];
|
||||
try {
|
||||
checks = check(source, toRuffConfig(config));
|
||||
setError(null);
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
return;
|
||||
}
|
||||
|
||||
editor.setModelMarkers(
|
||||
model,
|
||||
"owner",
|
||||
checks.map((check) => ({
|
||||
startLineNumber: check.location.row,
|
||||
startColumn: check.location.column + 1,
|
||||
endLineNumber: check.end_location.row,
|
||||
endColumn: check.end_location.column + 1,
|
||||
message: `${check.code}: ${check.message}`,
|
||||
severity: MarkerSeverity.Error,
|
||||
}))
|
||||
);
|
||||
|
||||
const codeActionProvider = monaco?.languages.registerCodeActionProvider(
|
||||
"python",
|
||||
{
|
||||
// @ts-expect-error: The type definition is wrong.
|
||||
provideCodeActions: function (model, position) {
|
||||
const actions = checks
|
||||
.filter((check) => position.startLineNumber === check.location.row)
|
||||
.filter((check) => check.fix)
|
||||
.map((check) => ({
|
||||
title: `Fix ${check.code}`,
|
||||
id: `fix-${check.code}`,
|
||||
kind: "quickfix",
|
||||
edit: check.fix
|
||||
? {
|
||||
edits: [
|
||||
{
|
||||
resource: model.uri,
|
||||
versionId: model.getVersionId(),
|
||||
edit: {
|
||||
range: {
|
||||
startLineNumber: check.fix.location.row,
|
||||
startColumn: check.fix.location.column + 1,
|
||||
endLineNumber: check.fix.end_location.row,
|
||||
endColumn: check.fix.end_location.column + 1,
|
||||
},
|
||||
text: check.fix.content,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
}));
|
||||
return { actions, dispose: () => {} };
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
codeActionProvider?.dispose();
|
||||
};
|
||||
}, [config, source, monaco, initialized]);
|
||||
|
||||
const handleEditorChange = useCallback(
|
||||
(value: string | undefined) => {
|
||||
setSource(value || "");
|
||||
},
|
||||
[setSource]
|
||||
);
|
||||
|
||||
const handleOptionChange = useCallback(
|
||||
(groupName: string, fieldName: string, value: string) => {
|
||||
const group = Object.assign({}, (config || {})[groupName]);
|
||||
if (value === defaultConfig[groupName][fieldName] || value === "") {
|
||||
delete group[fieldName];
|
||||
} else {
|
||||
group[fieldName] = value;
|
||||
}
|
||||
|
||||
setConfig({
|
||||
...config,
|
||||
[groupName]: group,
|
||||
});
|
||||
},
|
||||
[config]
|
||||
);
|
||||
|
||||
return (
|
||||
<div id="app">
|
||||
<Options
|
||||
config={config}
|
||||
defaultConfig={defaultConfig}
|
||||
onChange={handleOptionChange}
|
||||
/>
|
||||
<Editor
|
||||
options={{ readOnly: false, minimap: { enabled: false } }}
|
||||
wrapperProps={{ className: "editor" }}
|
||||
defaultLanguage="python"
|
||||
value={source || ""}
|
||||
theme={"light"}
|
||||
onChange={handleEditorChange}
|
||||
/>
|
||||
{error && <div id="error">{error}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
151
playground/src/Editor/Editor.tsx
Normal file
151
playground/src/Editor/Editor.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { DEFAULT_PYTHON_SOURCE } from "../constants";
|
||||
import init, { check, Check, currentVersion, defaultSettings } from "../pkg";
|
||||
import { ErrorMessage } from "./ErrorMessage";
|
||||
import Header from "./Header";
|
||||
import { useTheme } from "./theme";
|
||||
import { persist, restore, stringify } from "./settings";
|
||||
import SettingsEditor from "./SettingsEditor";
|
||||
import SourceEditor from "./SourceEditor";
|
||||
import MonacoThemes from "./MonacoThemes";
|
||||
|
||||
type Tab = "Source" | "Settings";
|
||||
|
||||
export default function Editor() {
|
||||
const [initialized, setInitialized] = useState<boolean>(false);
|
||||
const [version, setVersion] = useState<string | null>(null);
|
||||
const [tab, setTab] = useState<Tab>("Source");
|
||||
const [edit, setEdit] = useState<number>(0);
|
||||
const [settingsSource, setSettingsSource] = useState<string | null>(null);
|
||||
const [pythonSource, setPythonSource] = useState<string | null>(null);
|
||||
const [checks, setChecks] = useState<Check[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [theme, setTheme] = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
init().then(() => setInitialized(true));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialized || settingsSource == null || pythonSource == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let config: any;
|
||||
let checks: Check[];
|
||||
|
||||
try {
|
||||
config = JSON.parse(settingsSource);
|
||||
} catch (e) {
|
||||
setChecks([]);
|
||||
setError((e as Error).message);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
checks = check(pythonSource, config);
|
||||
} catch (e) {
|
||||
setError(e as string);
|
||||
return;
|
||||
}
|
||||
|
||||
setError(null);
|
||||
setChecks(checks);
|
||||
}, [initialized, settingsSource, pythonSource]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (settingsSource == null || pythonSource == null) {
|
||||
const payload = restore();
|
||||
if (payload) {
|
||||
const [settingsSource, pythonSource] = payload;
|
||||
setSettingsSource(settingsSource);
|
||||
setPythonSource(pythonSource);
|
||||
} else {
|
||||
setSettingsSource(stringify(defaultSettings()));
|
||||
setPythonSource(DEFAULT_PYTHON_SOURCE);
|
||||
}
|
||||
}
|
||||
}, [initialized, settingsSource, pythonSource]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
setVersion(currentVersion());
|
||||
}, [initialized]);
|
||||
|
||||
const handleShare = useCallback(() => {
|
||||
if (!initialized || settingsSource == null || pythonSource == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
persist(settingsSource, pythonSource);
|
||||
}, [initialized, settingsSource, pythonSource]);
|
||||
|
||||
const handlePythonSourceChange = useCallback((pythonSource: string) => {
|
||||
setEdit((edit) => edit + 1);
|
||||
setPythonSource(pythonSource);
|
||||
}, []);
|
||||
|
||||
const handleSettingsSourceChange = useCallback((settingsSource: string) => {
|
||||
setEdit((edit) => edit + 1);
|
||||
setSettingsSource(settingsSource);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main
|
||||
className={
|
||||
"h-full w-full flex flex-auto bg-ayu-background dark:bg-ayu-background-dark"
|
||||
}
|
||||
>
|
||||
<Header
|
||||
edit={edit}
|
||||
tab={tab}
|
||||
theme={theme}
|
||||
version={version}
|
||||
onChangeTab={setTab}
|
||||
onChangeTheme={setTheme}
|
||||
onShare={initialized ? handleShare : undefined}
|
||||
/>
|
||||
|
||||
<MonacoThemes />
|
||||
|
||||
<div className={"mt-12 relative flex-auto"}>
|
||||
{initialized && settingsSource != null && pythonSource != null ? (
|
||||
<>
|
||||
<SourceEditor
|
||||
visible={tab === "Source"}
|
||||
source={pythonSource}
|
||||
theme={theme}
|
||||
checks={checks}
|
||||
onChange={handlePythonSourceChange}
|
||||
/>
|
||||
<SettingsEditor
|
||||
visible={tab === "Settings"}
|
||||
source={settingsSource}
|
||||
theme={theme}
|
||||
onChange={handleSettingsSourceChange}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
{error && tab === "Source" ? (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
left: "10%",
|
||||
right: "10%",
|
||||
bottom: "10%",
|
||||
}}
|
||||
>
|
||||
<ErrorMessage>{error}</ErrorMessage>
|
||||
</div>
|
||||
) : null}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
26
playground/src/Editor/ErrorMessage.tsx
Normal file
26
playground/src/Editor/ErrorMessage.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
function truncate(str: string, length: number) {
|
||||
if (str.length > length) {
|
||||
return str.slice(0, length) + "...";
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
export function ErrorMessage({ children }: { children: string }) {
|
||||
return (
|
||||
<div
|
||||
className="bg-orange-100 border-l-4 border-orange-500 text-orange-700 p-4"
|
||||
role="alert"
|
||||
>
|
||||
<p className="font-bold">Error</p>
|
||||
<p className="block sm:inline">
|
||||
{truncate(
|
||||
children.startsWith("Error: ")
|
||||
? children.slice("Error: ".length)
|
||||
: children,
|
||||
120,
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
99
playground/src/Editor/Header.tsx
Normal file
99
playground/src/Editor/Header.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import classNames from "classnames";
|
||||
import ThemeButton from "./ThemeButton";
|
||||
import ShareButton from "./ShareButton";
|
||||
import { Theme } from "./theme";
|
||||
import VersionTag from "./VersionTag";
|
||||
|
||||
export type Tab = "Source" | "Settings";
|
||||
|
||||
export default function Header({
|
||||
edit,
|
||||
tab,
|
||||
theme,
|
||||
version,
|
||||
onChangeTab,
|
||||
onChangeTheme,
|
||||
onShare,
|
||||
}: {
|
||||
edit: number;
|
||||
tab: Tab;
|
||||
theme: Theme;
|
||||
version: string | null;
|
||||
onChangeTab: (tab: Tab) => void;
|
||||
onChangeTheme: (theme: Theme) => void;
|
||||
onShare?: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"w-full",
|
||||
"flex",
|
||||
"items-center",
|
||||
"justify-between",
|
||||
"flex-none",
|
||||
"pl-5",
|
||||
"sm:pl-6",
|
||||
"pr-4",
|
||||
"lg:pr-6",
|
||||
"absolute",
|
||||
"z-10",
|
||||
"top-0",
|
||||
"left-0",
|
||||
"-mb-px",
|
||||
"antialiased",
|
||||
"border-b",
|
||||
"border-gray-200",
|
||||
"dark:border-gray-800",
|
||||
)}
|
||||
>
|
||||
<div className="flex space-x-5">
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
"relative flex py-3 text-sm leading-6 font-semibold focus:outline-none",
|
||||
tab === "Source"
|
||||
? "text-ayu-accent"
|
||||
: "text-gray-700 hover:text-gray-900 focus:text-gray-900 dark:text-gray-300 dark:hover:text-white",
|
||||
)}
|
||||
onClick={() => onChangeTab("Source")}
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
"absolute bottom-0 inset-x-0 bg-ayu-accent h-0.5 rounded-full transition-opacity duration-150",
|
||||
tab === "Source" ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
Source
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
"relative flex py-3 text-sm leading-6 font-semibold focus:outline-none",
|
||||
tab === "Settings"
|
||||
? "text-ayu-accent"
|
||||
: "text-gray-700 hover:text-gray-900 focus:text-gray-900 dark:text-gray-300 dark:hover:text-white",
|
||||
)}
|
||||
onClick={() => onChangeTab("Settings")}
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
"absolute bottom-0 inset-x-0 bg-ayu-accent h-0.5 rounded-full transition-opacity duration-150",
|
||||
tab === "Settings" ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
Settings
|
||||
</button>
|
||||
{version ? (
|
||||
<div className={"flex items-center"}>
|
||||
<VersionTag>v{version}</VersionTag>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={"hidden sm:flex items-center min-w-0"}>
|
||||
<ShareButton key={edit} onShare={onShare} />
|
||||
<div className="hidden sm:block mx-6 lg:mx-4 w-px h-6 bg-gray-200 dark:bg-gray-700" />
|
||||
<ThemeButton theme={theme} onChange={onChangeTheme} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1284
playground/src/Editor/MonacoThemes.tsx
Normal file
1284
playground/src/Editor/MonacoThemes.tsx
Normal file
File diff suppressed because it is too large
Load Diff
57
playground/src/Editor/SettingsEditor.tsx
Normal file
57
playground/src/Editor/SettingsEditor.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Editor for the settings JSON.
|
||||
*/
|
||||
|
||||
import Editor, { useMonaco } from "@monaco-editor/react";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import schema from "../../../ruff.schema.json";
|
||||
import { Theme } from "./theme";
|
||||
|
||||
export default function SettingsEditor({
|
||||
visible,
|
||||
source,
|
||||
theme,
|
||||
onChange,
|
||||
}: {
|
||||
visible: boolean;
|
||||
source: string;
|
||||
theme: Theme;
|
||||
onChange: (source: string) => void;
|
||||
}) {
|
||||
const monaco = useMonaco();
|
||||
|
||||
useEffect(() => {
|
||||
monaco?.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||
schemas: [
|
||||
{
|
||||
uri: "https://raw.githubusercontent.com/charliermarsh/ruff/main/ruff.schema.json",
|
||||
fileMatch: ["*"],
|
||||
schema,
|
||||
},
|
||||
],
|
||||
});
|
||||
}, [monaco]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(value: string | undefined) => {
|
||||
onChange(value ?? "");
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
return (
|
||||
<Editor
|
||||
options={{
|
||||
readOnly: false,
|
||||
minimap: { enabled: false },
|
||||
fontSize: 14,
|
||||
roundedSelection: false,
|
||||
scrollBeyondLastLine: false,
|
||||
}}
|
||||
wrapperProps={visible ? {} : { style: { display: "none" } }}
|
||||
language={"json"}
|
||||
value={source}
|
||||
theme={theme === "light" ? "Ayu-Light" : "Ayu-Dark"}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
53
playground/src/Editor/ShareButton.tsx
Normal file
53
playground/src/Editor/ShareButton.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function ShareButton({ onShare }: { onShare?: () => void }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (copied) {
|
||||
const timeout = setTimeout(() => setCopied(false), 2000);
|
||||
return () => clearTimeout(timeout);
|
||||
}
|
||||
}, [copied]);
|
||||
|
||||
return copied ? (
|
||||
<button
|
||||
type="button"
|
||||
className="relative flex-none rounded-md text-sm font-semibold leading-6 py-1.5 px-3 cursor-auto text-ayu-accent shadow-copied dark:bg-ayu-accent/10"
|
||||
>
|
||||
<span
|
||||
className="absolute inset-0 flex items-center justify-center invisible"
|
||||
aria-hidden="true"
|
||||
>
|
||||
Share
|
||||
</span>
|
||||
<span className="" aria-hidden="false">
|
||||
Copied!
|
||||
</span>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="relative flex-none rounded-md text-sm font-semibold leading-6 py-1.5 px-3 enabled:hover:bg-ayu-accent/80 bg-ayu-accent text-white shadow-sm dark:shadow-highlight/20 disabled:opacity-50"
|
||||
disabled={!onShare || copied}
|
||||
onClick={
|
||||
onShare
|
||||
? () => {
|
||||
setCopied(true);
|
||||
onShare();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="absolute inset-0 flex items-center justify-center"
|
||||
aria-hidden="false"
|
||||
>
|
||||
Share
|
||||
</span>
|
||||
<span className="invisible" aria-hidden="true">
|
||||
Copied!
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
117
playground/src/Editor/SourceEditor.tsx
Normal file
117
playground/src/Editor/SourceEditor.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Editor for the Python source code.
|
||||
*/
|
||||
|
||||
import Editor, { useMonaco } from "@monaco-editor/react";
|
||||
import { MarkerSeverity, MarkerTag } from "monaco-editor";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { Check } from "../pkg";
|
||||
import { Theme } from "./theme";
|
||||
|
||||
export default function SourceEditor({
|
||||
visible,
|
||||
source,
|
||||
theme,
|
||||
checks,
|
||||
onChange,
|
||||
}: {
|
||||
visible: boolean;
|
||||
source: string;
|
||||
checks: Check[];
|
||||
theme: Theme;
|
||||
onChange: (pythonSource: string) => void;
|
||||
}) {
|
||||
const monaco = useMonaco();
|
||||
|
||||
useEffect(() => {
|
||||
const editor = monaco?.editor;
|
||||
const model = editor?.getModels()[0];
|
||||
if (!editor || !model) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor.setModelMarkers(
|
||||
model,
|
||||
"owner",
|
||||
checks.map((check) => ({
|
||||
startLineNumber: check.location.row,
|
||||
startColumn: check.location.column + 1,
|
||||
endLineNumber: check.end_location.row,
|
||||
endColumn: check.end_location.column + 1,
|
||||
message: `${check.code}: ${check.message}`,
|
||||
severity: MarkerSeverity.Error,
|
||||
tags:
|
||||
check.code === "F401" || check.code === "F841"
|
||||
? [MarkerTag.Unnecessary]
|
||||
: [],
|
||||
})),
|
||||
);
|
||||
|
||||
const codeActionProvider = monaco?.languages.registerCodeActionProvider(
|
||||
"python",
|
||||
{
|
||||
// @ts-expect-error: The type definition is wrong.
|
||||
provideCodeActions: function (model, position) {
|
||||
const actions = checks
|
||||
.filter((check) => position.startLineNumber === check.location.row)
|
||||
.filter((check) => check.fix)
|
||||
.map((check) => ({
|
||||
title: check.fix
|
||||
? `${check.code}: ${check.fix.message}` ?? `Fix ${check.code}`
|
||||
: "Autofix",
|
||||
id: `fix-${check.code}`,
|
||||
kind: "quickfix",
|
||||
edit: check.fix
|
||||
? {
|
||||
edits: [
|
||||
{
|
||||
resource: model.uri,
|
||||
versionId: model.getVersionId(),
|
||||
edit: {
|
||||
range: {
|
||||
startLineNumber: check.fix.location.row,
|
||||
startColumn: check.fix.location.column + 1,
|
||||
endLineNumber: check.fix.end_location.row,
|
||||
endColumn: check.fix.end_location.column + 1,
|
||||
},
|
||||
text: check.fix.content,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
}));
|
||||
return { actions, dispose: () => {} };
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
codeActionProvider?.dispose();
|
||||
};
|
||||
}, [checks, monaco]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(value: string | undefined) => {
|
||||
onChange(value ?? "");
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<Editor
|
||||
options={{
|
||||
readOnly: false,
|
||||
minimap: { enabled: false },
|
||||
fontSize: 14,
|
||||
roundedSelection: false,
|
||||
scrollBeyondLastLine: false,
|
||||
}}
|
||||
language={"python"}
|
||||
wrapperProps={visible ? {} : { style: { display: "none" } }}
|
||||
theme={theme === "light" ? "Ayu-Light" : "Ayu-Dark"}
|
||||
value={source}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
49
playground/src/Editor/ThemeButton.tsx
Normal file
49
playground/src/Editor/ThemeButton.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Button to toggle between light and dark mode themes.
|
||||
*/
|
||||
import { Theme } from "./theme";
|
||||
|
||||
export default function ThemeButton({
|
||||
theme,
|
||||
onChange,
|
||||
}: {
|
||||
theme: Theme;
|
||||
onChange: (theme: Theme) => void;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="ml-4 sm:ml-0 ring-1 ring-gray-900/5 shadow-sm hover:bg-gray-50 dark:ring-0 dark:bg-gray-800 dark:hover:bg-gray-700 dark:shadow-highlight/4 group focus:outline-none focus-visible:ring-2 rounded-md focus-visible:ring-ayu-accent dark:focus-visible:ring-2 dark:focus-visible:ring-gray-400"
|
||||
onClick={() => onChange(theme === "light" ? "dark" : "light")}
|
||||
>
|
||||
<span className="sr-only">
|
||||
<span className="dark:hidden">Switch to dark theme</span>
|
||||
<span className="hidden dark:inline">Switch to light theme</span>
|
||||
</span>
|
||||
<svg
|
||||
width="36"
|
||||
height="36"
|
||||
viewBox="-6 -6 36 36"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="stroke-ayu-accent fill-ayu-accent/10 group-hover:stroke-ayu-accent/80 dark:stroke-gray-400 dark:fill-gray-400/20 dark:group-hover:stroke-gray-300"
|
||||
>
|
||||
<g className="dark:opacity-0">
|
||||
<path d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"></path>
|
||||
<path
|
||||
d="M12 4v.01M17.66 6.345l-.007.007M20.005 12.005h-.01M17.66 17.665l-.007-.007M12 20.01V20M6.34 17.665l.007-.007M3.995 12.005h.01M6.34 6.344l.007.007"
|
||||
fill="none"
|
||||
/>
|
||||
</g>
|
||||
<g className="opacity-0 dark:opacity-100">
|
||||
<path d="M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z" />
|
||||
<path
|
||||
d="M12 3v1M18.66 5.345l-.828.828M21.005 12.005h-1M18.66 18.665l-.828-.828M12 21.01V20M5.34 18.666l.835-.836M2.995 12.005h1.01M5.34 5.344l.835.836"
|
||||
fill="none"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
26
playground/src/Editor/VersionTag.tsx
Normal file
26
playground/src/Editor/VersionTag.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import classNames from "classnames";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export default function VersionTag({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"text-gray-500",
|
||||
"text-xs",
|
||||
"leading-5",
|
||||
"font-semibold",
|
||||
"bg-gray-400/10",
|
||||
"rounded-full",
|
||||
"py-1",
|
||||
"px-3",
|
||||
"flex",
|
||||
"items-center",
|
||||
"dark:bg-gray-800",
|
||||
"dark:text-gray-400",
|
||||
"dark:shadow-highlight/4",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
3
playground/src/Editor/index.tsx
Normal file
3
playground/src/Editor/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import Editor from "./Editor";
|
||||
|
||||
export default Editor;
|
||||
50
playground/src/Editor/settings.ts
Normal file
50
playground/src/Editor/settings.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import lzstring from "lz-string";
|
||||
|
||||
export type Settings = { [K: string]: any };
|
||||
|
||||
/**
|
||||
* Stringify a settings object to JSON.
|
||||
*/
|
||||
export function stringify(settings: Settings): string {
|
||||
return JSON.stringify(
|
||||
settings,
|
||||
(k, v) => {
|
||||
if (v instanceof Map) {
|
||||
return Object.fromEntries(v.entries());
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
},
|
||||
2,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist the configuration to a URL.
|
||||
*/
|
||||
export async function persist(settingsSource: string, pythonSource: string) {
|
||||
const hash = lzstring.compressToEncodedURIComponent(
|
||||
settingsSource + "$$$" + pythonSource,
|
||||
);
|
||||
await navigator.clipboard.writeText(
|
||||
window.location.href.split("#")[0] + "#" + hash,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the configuration from a URL.
|
||||
*/
|
||||
export function restore(): [string, string] | null {
|
||||
const value = lzstring.decompressFromEncodedURIComponent(
|
||||
window.location.hash.slice(1),
|
||||
);
|
||||
|
||||
if (value) {
|
||||
const parts = value.split("$$$");
|
||||
const settingsSource = parts[0];
|
||||
const pythonSource = parts[1];
|
||||
return [settingsSource, pythonSource];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
35
playground/src/Editor/theme.ts
Normal file
35
playground/src/Editor/theme.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Light and dark mode theming.
|
||||
*/
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export type Theme = "dark" | "light";
|
||||
|
||||
export function useTheme(): [Theme, (theme: Theme) => void] {
|
||||
const [localTheme, setLocalTheme] = useState<Theme>("light");
|
||||
|
||||
const setTheme = (mode: Theme) => {
|
||||
if (mode === "dark") {
|
||||
document.body.classList.add("dark");
|
||||
} else {
|
||||
document.body.classList.remove("dark");
|
||||
}
|
||||
localStorage.setItem("theme", mode);
|
||||
setLocalTheme(mode);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const initialTheme = localStorage.getItem("theme");
|
||||
if (initialTheme === "dark") {
|
||||
setTheme("dark");
|
||||
} else if (initialTheme === "light") {
|
||||
setTheme("light");
|
||||
} else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
setTheme("dark");
|
||||
} else {
|
||||
setTheme("light");
|
||||
}
|
||||
}, []);
|
||||
|
||||
return [localTheme, setTheme];
|
||||
}
|
||||
7
playground/src/Editor/utils.ts
Normal file
7
playground/src/Editor/utils.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export async function copyTextToClipboard(text: string) {
|
||||
if ("clipboard" in navigator) {
|
||||
return await navigator.clipboard.writeText(text);
|
||||
} else {
|
||||
return document.execCommand("copy", true, text);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import { Config } from "./config";
|
||||
import { AVAILABLE_OPTIONS } from "./ruff_options";
|
||||
|
||||
function OptionEntry({
|
||||
config,
|
||||
defaultConfig,
|
||||
groupName,
|
||||
fieldName,
|
||||
onChange,
|
||||
}: {
|
||||
config: Config | null;
|
||||
defaultConfig: Config;
|
||||
groupName: string;
|
||||
fieldName: string;
|
||||
onChange: (groupName: string, fieldName: string, value: string) => void;
|
||||
}) {
|
||||
const value =
|
||||
config && config[groupName] && config[groupName][fieldName]
|
||||
? config[groupName][fieldName]
|
||||
: "";
|
||||
|
||||
return (
|
||||
<span>
|
||||
<label>
|
||||
{fieldName}
|
||||
<input
|
||||
value={value}
|
||||
placeholder={defaultConfig[groupName][fieldName]}
|
||||
type="text"
|
||||
onChange={(event) => {
|
||||
onChange(groupName, fieldName, event.target.value);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function Options({
|
||||
config,
|
||||
defaultConfig,
|
||||
onChange,
|
||||
}: {
|
||||
config: Config | null;
|
||||
defaultConfig: Config;
|
||||
onChange: (groupName: string, fieldName: string, value: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="options">
|
||||
{AVAILABLE_OPTIONS.map((group) => (
|
||||
<details key={group.name}>
|
||||
<summary>{group.name}</summary>
|
||||
<div>
|
||||
<ul>
|
||||
{group.fields.map((field) => (
|
||||
<li key={field.name}>
|
||||
<OptionEntry
|
||||
config={config}
|
||||
defaultConfig={defaultConfig}
|
||||
groupName={group.name}
|
||||
fieldName={field.name}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { OptionGroup } from "./ruff_options";
|
||||
|
||||
export type Config = { [key: string]: { [key: string]: string } };
|
||||
|
||||
export function getDefaultConfig(availableOptions: OptionGroup[]): Config {
|
||||
const config: Config = {};
|
||||
availableOptions.forEach((group) => {
|
||||
config[group.name] = {};
|
||||
group.fields.forEach((f) => {
|
||||
config[group.name][f.name] = f.default;
|
||||
});
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the config in the application to something Ruff accepts.
|
||||
*
|
||||
* Application config is always nested one level. Ruff allows for some
|
||||
* top-level options.
|
||||
*
|
||||
* Any option value is parsed as JSON to convert it to a native JS object.
|
||||
* If that fails, e.g. while a user is typing, we let the application handle that
|
||||
* and show an error.
|
||||
*/
|
||||
export function toRuffConfig(config: Config): any {
|
||||
const convertValue = (value: string): any => {
|
||||
return value === "None" ? null : JSON.parse(value);
|
||||
};
|
||||
|
||||
const result: any = {};
|
||||
Object.keys(config).forEach((group_name) => {
|
||||
const fields = config[group_name];
|
||||
if (!fields || Object.keys(fields).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (group_name === "globals") {
|
||||
Object.keys(fields).forEach((field_name) => {
|
||||
result[field_name] = convertValue(fields[field_name]);
|
||||
});
|
||||
} else {
|
||||
result[group_name] = {};
|
||||
|
||||
Object.keys(fields).forEach((field_name) => {
|
||||
result[group_name][field_name] = convertValue(fields[field_name]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
31
playground/src/constants.ts
Normal file
31
playground/src/constants.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export const DEFAULT_PYTHON_SOURCE =
|
||||
"import os\n" +
|
||||
"\n" +
|
||||
"# Define a function that takes an integer n and returns the nth number in the Fibonacci\n" +
|
||||
"# sequence.\n" +
|
||||
"def fibonacci(n):\n" +
|
||||
' """Compute the nth number in the Fibonacci sequence."""\n' +
|
||||
" x = 1\n" +
|
||||
" if n == 0:\n" +
|
||||
" return 0\n" +
|
||||
" elif n == 1:\n" +
|
||||
" return 1\n" +
|
||||
" else:\n" +
|
||||
" return fibonacci(n - 1) + fibonacci(n - 2)\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"# Use a for loop to generate and print the first 10 numbers in the Fibonacci sequence.\n" +
|
||||
"for i in range(10):\n" +
|
||||
" print(fibonacci(i))\n" +
|
||||
"\n" +
|
||||
"# Output:\n" +
|
||||
"# 0\n" +
|
||||
"# 1\n" +
|
||||
"# 1\n" +
|
||||
"# 2\n" +
|
||||
"# 3\n" +
|
||||
"# 5\n" +
|
||||
"# 8\n" +
|
||||
"# 13\n" +
|
||||
"# 21\n" +
|
||||
"# 34\n";
|
||||
24
playground/src/index.css
Normal file
24
playground/src/index.css
Normal file
@@ -0,0 +1,24 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body,
|
||||
html,
|
||||
#root {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.shadow-copied {
|
||||
--tw-shadow: 0 0 0 1px #f07171, inset 0 0 0 1px #f07171;
|
||||
--tw-shadow-colored: 0 0 0 1px var(--tw-shadow-color),
|
||||
inset 0 0 0 1px var(--tw-shadow-color);
|
||||
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
|
||||
var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./style.css";
|
||||
import Editor from "./Editor";
|
||||
import "./index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
<Editor />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
|
||||
// This file is auto-generated by `cargo dev generate-playground-options`.
|
||||
export interface OptionGroup {
|
||||
name: string;
|
||||
fields: {
|
||||
name: string;
|
||||
default: string;
|
||||
type: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const AVAILABLE_OPTIONS: OptionGroup[] = [
|
||||
{"name": "globals", "fields": [
|
||||
{
|
||||
"name": "allowed-confusables",
|
||||
"default": '[]',
|
||||
"type": 'Vec<char>',
|
||||
},
|
||||
{
|
||||
"name": "dummy-variable-rgx",
|
||||
"default": '"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"',
|
||||
"type": 'Regex',
|
||||
},
|
||||
{
|
||||
"name": "extend-ignore",
|
||||
"default": '[]',
|
||||
"type": 'Vec<CheckCodePrefix>',
|
||||
},
|
||||
{
|
||||
"name": "extend-select",
|
||||
"default": '[]',
|
||||
"type": 'Vec<CheckCodePrefix>',
|
||||
},
|
||||
{
|
||||
"name": "external",
|
||||
"default": '[]',
|
||||
"type": 'Vec<String>',
|
||||
},
|
||||
{
|
||||
"name": "fix-only",
|
||||
"default": 'false',
|
||||
"type": 'bool',
|
||||
},
|
||||
{
|
||||
"name": "ignore",
|
||||
"default": '[]',
|
||||
"type": 'Vec<CheckCodePrefix>',
|
||||
},
|
||||
{
|
||||
"name": "line-length",
|
||||
"default": '88',
|
||||
"type": 'usize',
|
||||
},
|
||||
{
|
||||
"name": "required-version",
|
||||
"default": 'None',
|
||||
"type": 'String',
|
||||
},
|
||||
{
|
||||
"name": "select",
|
||||
"default": '["E", "F"]',
|
||||
"type": 'Vec<CheckCodePrefix>',
|
||||
},
|
||||
{
|
||||
"name": "target-version",
|
||||
"default": '"py310"',
|
||||
"type": 'PythonVersion',
|
||||
},
|
||||
{
|
||||
"name": "unfixable",
|
||||
"default": '[]',
|
||||
"type": 'Vec<CheckCodePrefix>',
|
||||
},
|
||||
]},
|
||||
{"name": "flake8-annotations", "fields": [
|
||||
{
|
||||
"name": "allow-star-arg-any",
|
||||
"default": 'false',
|
||||
"type": 'bool',
|
||||
},
|
||||
{
|
||||
"name": "mypy-init-return",
|
||||
"default": 'false',
|
||||
"type": 'bool',
|
||||
},
|
||||
{
|
||||
"name": "suppress-dummy-args",
|
||||
"default": 'false',
|
||||
"type": 'bool',
|
||||
},
|
||||
{
|
||||
"name": "suppress-none-returning",
|
||||
"default": 'false',
|
||||
"type": 'bool',
|
||||
},
|
||||
]},
|
||||
{"name": "flake8-bugbear", "fields": [
|
||||
{
|
||||
"name": "extend-immutable-calls",
|
||||
"default": '[]',
|
||||
"type": 'Vec<String>',
|
||||
},
|
||||
]},
|
||||
{"name": "flake8-errmsg", "fields": [
|
||||
{
|
||||
"name": "max-string-length",
|
||||
"default": '0',
|
||||
"type": 'usize',
|
||||
},
|
||||
]},
|
||||
{"name": "flake8-import-conventions", "fields": [
|
||||
{
|
||||
"name": "aliases",
|
||||
"default": '{"altair": "alt", "matplotlib.pyplot": "plt", "numpy": "np", "pandas": "pd", "seaborn": "sns"}',
|
||||
"type": 'FxHashMap<String, String>',
|
||||
},
|
||||
{
|
||||
"name": "extend-aliases",
|
||||
"default": '{}',
|
||||
"type": 'FxHashMap<String, String>',
|
||||
},
|
||||
]},
|
||||
{"name": "flake8-quotes", "fields": [
|
||||
{
|
||||
"name": "avoid-escape",
|
||||
"default": 'true',
|
||||
"type": 'bool',
|
||||
},
|
||||
{
|
||||
"name": "docstring-quotes",
|
||||
"default": '"double"',
|
||||
"type": 'Quote',
|
||||
},
|
||||
{
|
||||
"name": "inline-quotes",
|
||||
"default": '"double"',
|
||||
"type": 'Quote',
|
||||
},
|
||||
{
|
||||
"name": "multiline-quotes",
|
||||
"default": '"double"',
|
||||
"type": 'Quote',
|
||||
},
|
||||
]},
|
||||
{"name": "flake8-tidy-imports", "fields": [
|
||||
{
|
||||
"name": "ban-relative-imports",
|
||||
"default": '"parents"',
|
||||
"type": 'Strictness',
|
||||
},
|
||||
]},
|
||||
{"name": "flake8-unused-arguments", "fields": [
|
||||
{
|
||||
"name": "ignore-variadic-names",
|
||||
"default": 'false',
|
||||
"type": 'bool',
|
||||
},
|
||||
]},
|
||||
{"name": "isort", "fields": [
|
||||
{
|
||||
"name": "combine-as-imports",
|
||||
"default": 'false',
|
||||
"type": 'bool',
|
||||
},
|
||||
{
|
||||
"name": "extra-standard-library",
|
||||
"default": '[]',
|
||||
"type": 'Vec<String>',
|
||||
},
|
||||
{
|
||||
"name": "force-single-line",
|
||||
"default": 'false',
|
||||
"type": 'bool',
|
||||
},
|
||||
{
|
||||
"name": "force-wrap-aliases",
|
||||
"default": 'false',
|
||||
"type": 'bool',
|
||||
},
|
||||
{
|
||||
"name": "known-first-party",
|
||||
"default": '[]',
|
||||
"type": 'Vec<String>',
|
||||
},
|
||||
{
|
||||
"name": "known-third-party",
|
||||
"default": '[]',
|
||||
"type": 'Vec<String>',
|
||||
},
|
||||
{
|
||||
"name": "single-line-exclusions",
|
||||
"default": '[]',
|
||||
"type": 'Vec<String>',
|
||||
},
|
||||
{
|
||||
"name": "split-on-trailing-comma",
|
||||
"default": 'true',
|
||||
"type": 'bool',
|
||||
},
|
||||
]},
|
||||
{"name": "mccabe", "fields": [
|
||||
{
|
||||
"name": "max-complexity",
|
||||
"default": '10',
|
||||
"type": 'usize',
|
||||
},
|
||||
]},
|
||||
{"name": "pep8-naming", "fields": [
|
||||
{
|
||||
"name": "classmethod-decorators",
|
||||
"default": '["classmethod"]',
|
||||
"type": 'Vec<String>',
|
||||
},
|
||||
{
|
||||
"name": "ignore-names",
|
||||
"default": '["setUp", "tearDown", "setUpClass", "tearDownClass", "setUpModule", "tearDownModule", "asyncSetUp", "asyncTearDown", "setUpTestData", "failureException", "longMessage", "maxDiff"]',
|
||||
"type": 'Vec<String>',
|
||||
},
|
||||
{
|
||||
"name": "staticmethod-decorators",
|
||||
"default": '["staticmethod"]',
|
||||
"type": 'Vec<String>',
|
||||
},
|
||||
]},
|
||||
{"name": "pydocstyle", "fields": [
|
||||
{
|
||||
"name": "convention",
|
||||
"default": '"convention"',
|
||||
"type": 'Convention',
|
||||
},
|
||||
]},
|
||||
{"name": "pyupgrade", "fields": [
|
||||
{
|
||||
"name": "keep-runtime-typing",
|
||||
"default": 'false',
|
||||
"type": 'bool',
|
||||
},
|
||||
]},
|
||||
];
|
||||
@@ -1,60 +0,0 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body,
|
||||
html,
|
||||
#root,
|
||||
#app {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.options {
|
||||
height: 100vh;
|
||||
overflow-y: scroll;
|
||||
padding: 1em;
|
||||
min-width: 300px;
|
||||
border-right: 1px solid lightgray;
|
||||
}
|
||||
|
||||
.options ul {
|
||||
padding-left: 1em;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.options li {
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
.options details {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.options summary {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.options input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.editor {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#error {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
min-height: 1em;
|
||||
padding: 1em;
|
||||
background: darkred;
|
||||
color: white;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
declare module "lz-string" {
|
||||
function decompressFromEncodedURIComponent(
|
||||
input: string | null
|
||||
input: string | null,
|
||||
): string | null;
|
||||
function compressToEncodedURIComponent(input: string | null): string;
|
||||
}
|
||||
22
playground/tailwind.config.cjs
Normal file
22
playground/tailwind.config.cjs
Normal file
@@ -0,0 +1,22 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
const defaultTheme = require("tailwindcss/defaultTheme");
|
||||
|
||||
module.exports = {
|
||||
darkMode: "class",
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
"ayu-accent": "#f07171",
|
||||
"ayu-background": {
|
||||
DEFAULT: "#f8f9fa",
|
||||
dark: "#0b0e14",
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["Inter var", ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
@@ -1,5 +1,19 @@
|
||||
[build-system]
|
||||
requires = ["maturin>=0.14,<0.15"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "ruff"
|
||||
version = "0.0.206"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
authors = [
|
||||
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },
|
||||
]
|
||||
maintainers = [
|
||||
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },
|
||||
]
|
||||
requires-python = ">=3.7"
|
||||
license = { file = "LICENSE" }
|
||||
keywords = ["automation", "flake8", "pycodestyle", "pyflakes", "pylint", "clippy"]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
@@ -17,23 +31,14 @@ classifiers = [
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Topic :: Software Development :: Quality Assurance",
|
||||
]
|
||||
author = "Charlie Marsh"
|
||||
author_email = "charlie.r.marsh@gmail.com"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
requires-python = ">=3.7"
|
||||
|
||||
[project.urls]
|
||||
repository = "https://github.com/charliermarsh/ruff"
|
||||
|
||||
[build-system]
|
||||
requires = ["maturin>=0.14,<0.15"]
|
||||
build-backend = "maturin"
|
||||
urls = { repository = "https://github.com/charliermarsh/ruff-lsp" }
|
||||
|
||||
[tool.maturin]
|
||||
bindings = "bin"
|
||||
strip = true
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
|
||||
[tool.ruff.isort]
|
||||
force-wrap-aliases = true
|
||||
|
||||
@@ -51,3 +51,12 @@ secret == "s3cr3t"
|
||||
token == "s3cr3t"
|
||||
secrete == "s3cr3t"
|
||||
password == safe == "s3cr3t"
|
||||
|
||||
if token == "1\n2":
|
||||
pass
|
||||
|
||||
if token == "3\t4":
|
||||
pass
|
||||
|
||||
if token == "5\r6":
|
||||
pass
|
||||
|
||||
36
resources/test/fixtures/flake8_implicit_str_concat/ISC.py
vendored
Normal file
36
resources/test/fixtures/flake8_implicit_str_concat/ISC.py
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
_ = "a" "b" "c"
|
||||
|
||||
_ = "abc" + "def"
|
||||
|
||||
_ = "abc" \
|
||||
"def"
|
||||
|
||||
_ = (
|
||||
"abc" +
|
||||
"def"
|
||||
)
|
||||
|
||||
_ = (
|
||||
f"abc" +
|
||||
"def"
|
||||
)
|
||||
|
||||
_ = (
|
||||
b"abc" +
|
||||
b"def"
|
||||
)
|
||||
|
||||
_ = (
|
||||
"abc"
|
||||
"def"
|
||||
)
|
||||
|
||||
_ = (
|
||||
f"abc"
|
||||
"def"
|
||||
)
|
||||
|
||||
_ = (
|
||||
b"abc"
|
||||
b"def"
|
||||
)
|
||||
11
resources/test/fixtures/flake8_return/RET504.py
vendored
11
resources/test/fixtures/flake8_return/RET504.py
vendored
@@ -137,6 +137,14 @@ def x():
|
||||
return a
|
||||
|
||||
|
||||
# Considered OK, since attribute assignments can have side effects.
|
||||
class X:
|
||||
def x(self):
|
||||
a = self.property
|
||||
self.property = None
|
||||
return a
|
||||
|
||||
|
||||
# Test cases for using value for assignment then returning it
|
||||
# See:https://github.com/afonasev/flake8-return/issues/47
|
||||
def resolve_from_url(self, url: str) -> dict:
|
||||
@@ -238,13 +246,16 @@ def close(self):
|
||||
report(traceback.format_exc())
|
||||
return any_failed
|
||||
|
||||
|
||||
def global_assignment():
|
||||
global X
|
||||
X = 1
|
||||
return X
|
||||
|
||||
|
||||
def nonlocal_assignment():
|
||||
X = 1
|
||||
|
||||
def inner():
|
||||
nonlocal X
|
||||
X = 1
|
||||
|
||||
33
resources/test/fixtures/flake8_tidy_imports/TID251.py
vendored
Normal file
33
resources/test/fixtures/flake8_tidy_imports/TID251.py
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
## Banned modules ##
|
||||
import cgi
|
||||
|
||||
from cgi import *
|
||||
|
||||
from cgi import a, b, c
|
||||
|
||||
# banning a module also bans any submodules
|
||||
import cgi.foo.bar
|
||||
|
||||
from cgi.foo import bar
|
||||
|
||||
from cgi.foo.bar import *
|
||||
|
||||
## Banned module members ##
|
||||
|
||||
from typing import TypedDict
|
||||
|
||||
import typing
|
||||
|
||||
# attribute access is checked
|
||||
typing.TypedDict
|
||||
|
||||
typing.TypedDict.anything
|
||||
|
||||
# function calls are checked
|
||||
typing.TypedDict()
|
||||
|
||||
typing.TypedDict.anything()
|
||||
|
||||
# import aliases are resolved
|
||||
import typing as totally_not_typing
|
||||
totally_not_typing.TypedDict
|
||||
11
resources/test/fixtures/flake8_tidy_imports/TID251_false_positives.py
vendored
Normal file
11
resources/test/fixtures/flake8_tidy_imports/TID251_false_positives.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# module members cannot be imported with that syntax
|
||||
import typing.TypedDict
|
||||
|
||||
# we don't track reassignments
|
||||
import typing, other
|
||||
typing = other
|
||||
typing.TypedDict()
|
||||
|
||||
# yet another false positive
|
||||
def foo(typing):
|
||||
typing.TypedDict()
|
||||
1
resources/test/fixtures/isort/line_ending_cr.py
vendored
Normal file
1
resources/test/fixtures/isort/line_ending_cr.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
from long_module_name import member_one, member_two, member_three, member_four, member_five
|
||||
2
resources/test/fixtures/isort/line_ending_crlf.py
vendored
Normal file
2
resources/test/fixtures/isort/line_ending_crlf.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
from long_module_name import member_one, member_two, member_three, member_four, member_five
|
||||
|
||||
2
resources/test/fixtures/isort/line_ending_lf.py
vendored
Normal file
2
resources/test/fixtures/isort/line_ending_lf.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
from long_module_name import member_one, member_two, member_three, member_four, member_five
|
||||
|
||||
22
resources/test/fixtures/pydocstyle/D400.py
vendored
22
resources/test/fixtures/pydocstyle/D400.py
vendored
@@ -35,7 +35,6 @@ def f():
|
||||
...
|
||||
|
||||
|
||||
|
||||
def f():
|
||||
r"Here's a line without a period"
|
||||
...
|
||||
@@ -71,3 +70,24 @@ def f():
|
||||
Here's a line without a period,
|
||||
but here's the next line with trailing space """
|
||||
...
|
||||
|
||||
|
||||
def f(rounds: list[int], number: int) -> bool:
|
||||
"""
|
||||
:param rounds: list - rounds played.
|
||||
:param number: int - round number.
|
||||
:return: bool - was the round played?
|
||||
"""
|
||||
return number in rounds
|
||||
|
||||
|
||||
def f(rounds: list[int], number: int) -> bool:
|
||||
"""
|
||||
Args:
|
||||
rounds (list): rounds played.
|
||||
number (int): round number.
|
||||
|
||||
Returns:
|
||||
bool: was the round played?
|
||||
"""
|
||||
return number in rounds
|
||||
|
||||
68
resources/test/fixtures/pyflakes/F401_7.py
vendored
68
resources/test/fixtures/pyflakes/F401_7.py
vendored
@@ -1,16 +1,66 @@
|
||||
"""Test: noqa directives."""
|
||||
|
||||
from module import (
|
||||
A, # noqa: F401
|
||||
B,
|
||||
# This should ignore both errors.
|
||||
from typing import ( # noqa: F401
|
||||
List,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
from module import (
|
||||
A, # noqa: F401
|
||||
B, # noqa: F401
|
||||
# This should ignore both errors.
|
||||
from typing import ( # noqa
|
||||
List,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
from module import (
|
||||
A,
|
||||
B,
|
||||
# This should ignore both errors.
|
||||
from typing import (
|
||||
List, # noqa: F401
|
||||
Sequence, # noqa: F401
|
||||
)
|
||||
|
||||
# This should ignore both errors.
|
||||
from typing import (
|
||||
List, # noqa
|
||||
Sequence, # noqa
|
||||
)
|
||||
|
||||
# This should ignore the first error.
|
||||
from typing import (
|
||||
Mapping, # noqa: F401
|
||||
Union,
|
||||
)
|
||||
|
||||
# This should ignore both errors.
|
||||
from typing import ( # noqa
|
||||
List,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
# This should ignore the error, but the inner noqa should be marked as unused.
|
||||
from typing import ( # noqa: F401
|
||||
Optional, # noqa: F401
|
||||
)
|
||||
|
||||
# This should ignore the error, but the inner noqa should be marked as unused.
|
||||
from typing import ( # noqa
|
||||
Optional, # noqa: F401
|
||||
)
|
||||
|
||||
# This should ignore the error, but mark F501 as unused.
|
||||
from typing import ( # noqa: F401
|
||||
Dict, # noqa: F501
|
||||
)
|
||||
|
||||
# This should ignore the error, but mark F501 as unused.
|
||||
from typing import ( # noqa: F501
|
||||
Tuple, # noqa: F401
|
||||
)
|
||||
|
||||
# This should ignore both errors.
|
||||
from typing import Any, AnyStr # noqa: F401
|
||||
|
||||
# This should ignore both errors.
|
||||
from typing import AsyncIterable, AsyncGenerator # noqa
|
||||
|
||||
# This should mark F501 as unused.
|
||||
from typing import Awaitable, AwaitableGenerator # noqa: F501
|
||||
|
||||
15
resources/test/fixtures/pyflakes/F541.py
vendored
15
resources/test/fixtures/pyflakes/F541.py
vendored
@@ -1,6 +1,8 @@
|
||||
# OK
|
||||
a = "abc"
|
||||
b = f"ghi{'jkl'}"
|
||||
|
||||
# Errors
|
||||
c = f"def"
|
||||
d = f"def" + "ghi"
|
||||
e = (
|
||||
@@ -8,5 +10,18 @@ e = (
|
||||
"ghi"
|
||||
)
|
||||
|
||||
# OK
|
||||
g = f"ghi{123:{45}}"
|
||||
|
||||
# Error
|
||||
h = "x" "y" f"z"
|
||||
|
||||
v = 23.234234
|
||||
|
||||
# OK
|
||||
f"{v:0.2f}"
|
||||
f"{f'{v:0.2f}'}"
|
||||
|
||||
# Errors
|
||||
f"{v:{f'0.2f'}}"
|
||||
f"{f''}"
|
||||
|
||||
3
resources/test/fixtures/pyflakes/F821_0.py
vendored
3
resources/test/fixtures/pyflakes/F821_0.py
vendored
@@ -89,7 +89,8 @@ A = (
|
||||
f'B'
|
||||
f'{B}'
|
||||
)
|
||||
|
||||
C = f'{A:{B}}'
|
||||
C = f'{A:{f"{B}"}}'
|
||||
|
||||
from typing import Annotated, Literal
|
||||
|
||||
|
||||
10
resources/test/fixtures/pyflakes/F831.py
vendored
10
resources/test/fixtures/pyflakes/F831.py
vendored
@@ -1,10 +0,0 @@
|
||||
def foo(x: int, y: int, x: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def bar(x: int, y: int, *, x: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def baz(x: int, y: int, **x: int) -> None:
|
||||
pass
|
||||
11
resources/test/fixtures/pygrep-hooks/PGH004_0.py
vendored
Normal file
11
resources/test/fixtures/pygrep-hooks/PGH004_0.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
x = 1 # noqa
|
||||
x = 1 # NOQA:F401,W203
|
||||
# noqa
|
||||
# NOQA
|
||||
# noqa:F401
|
||||
# noqa:F401,W203
|
||||
|
||||
x = 1
|
||||
x = 1 # noqa: F401, W203
|
||||
# noqa: F401
|
||||
# noqa: F401, W203
|
||||
4
resources/test/fixtures/pyproject.toml
vendored
4
resources/test/fixtures/pyproject.toml
vendored
@@ -42,6 +42,10 @@ staticmethod-decorators = ["staticmethod"]
|
||||
[tool.ruff.flake8-tidy-imports]
|
||||
ban-relative-imports = "parents"
|
||||
|
||||
[tool.ruff.flake8-tidy-imports.banned-api]
|
||||
"cgi".msg = "The cgi module is deprecated."
|
||||
"typing.TypedDict".msg = "Use typing_extensions.TypedDict instead."
|
||||
|
||||
[tool.ruff.flake8-errmsg]
|
||||
max-string-length = 20
|
||||
|
||||
|
||||
98
resources/test/fixtures/pyupgrade/UP024_0.py
vendored
Normal file
98
resources/test/fixtures/pyupgrade/UP024_0.py
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
import mmap, select, socket
|
||||
from mmap import error
|
||||
# These should be fixed
|
||||
try:
|
||||
pass
|
||||
except EnvironmentError:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except WindowsError:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except mmap.error:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except select.error:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except socket.error:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except error:
|
||||
pass
|
||||
|
||||
# Should NOT be in parentheses when replaced
|
||||
|
||||
try:
|
||||
pass
|
||||
except (IOError,):
|
||||
pass
|
||||
try:
|
||||
pass
|
||||
except (mmap.error,):
|
||||
pass
|
||||
try:
|
||||
pass
|
||||
except (EnvironmentError, IOError, OSError, select.error):
|
||||
pass
|
||||
|
||||
# Should be kept in parentheses (because multiple)
|
||||
|
||||
try:
|
||||
pass
|
||||
except (IOError, KeyError, OSError):
|
||||
pass
|
||||
|
||||
# First should change, second should not
|
||||
from .mmap import error
|
||||
try:
|
||||
pass
|
||||
except (IOError, error):
|
||||
pass
|
||||
# These should not change
|
||||
|
||||
from foo import error
|
||||
|
||||
try:
|
||||
pass
|
||||
except (OSError, error):
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except AssertionError:
|
||||
pass
|
||||
try:
|
||||
pass
|
||||
except (mmap).error:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except (OSError, KeyError):
|
||||
pass
|
||||
17
resources/test/fixtures/pyupgrade/UP024_1.py
vendored
Normal file
17
resources/test/fixtures/pyupgrade/UP024_1.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import mmap, socket, select
|
||||
|
||||
try:
|
||||
pass
|
||||
except (OSError, mmap.error, IOError):
|
||||
pass
|
||||
except (OSError, socket.error, KeyError):
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except (
|
||||
OSError,
|
||||
select.error,
|
||||
IOError,
|
||||
):
|
||||
pass
|
||||
50
resources/test/fixtures/pyupgrade/UP024_2.py
vendored
Normal file
50
resources/test/fixtures/pyupgrade/UP024_2.py
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
# These should not change
|
||||
raise ValueError
|
||||
raise ValueError(1)
|
||||
|
||||
from .mmap import error
|
||||
raise error
|
||||
|
||||
# Testing the modules
|
||||
import socket, mmap, select
|
||||
raise socket.error
|
||||
raise mmap.error
|
||||
raise select.error
|
||||
|
||||
raise socket.error()
|
||||
raise mmap.error(1)
|
||||
raise select.error(1, 2)
|
||||
|
||||
raise socket.error(
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
)
|
||||
|
||||
from mmap import error
|
||||
raise error
|
||||
|
||||
from socket import error
|
||||
raise error(1)
|
||||
|
||||
from select import error
|
||||
raise error(1, 2)
|
||||
|
||||
# Testing the names
|
||||
raise EnvironmentError
|
||||
raise IOError
|
||||
raise WindowsError
|
||||
|
||||
raise EnvironmentError()
|
||||
raise IOError(1)
|
||||
raise WindowsError(1, 2)
|
||||
|
||||
raise EnvironmentError(
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
)
|
||||
|
||||
raise WindowsError
|
||||
raise EnvironmentError(1)
|
||||
raise IOError(1, 2)
|
||||
27
resources/test/fixtures/pyupgrade/UP025.py
vendored
Normal file
27
resources/test/fixtures/pyupgrade/UP025.py
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# These should change
|
||||
x = u"Hello"
|
||||
|
||||
u'world'
|
||||
|
||||
print(u"Hello")
|
||||
|
||||
print(u'world')
|
||||
|
||||
import foo
|
||||
|
||||
foo(u"Hello", U"world", a=u"Hello", b=u"world")
|
||||
|
||||
# These should stay quoted they way they are
|
||||
|
||||
x = u'hello'
|
||||
x = u"""hello"""
|
||||
x = u'''hello'''
|
||||
x = u'Hello "World"'
|
||||
|
||||
# These should not change
|
||||
u = "Hello"
|
||||
|
||||
u = u
|
||||
|
||||
def hello():
|
||||
return"Hello"
|
||||
74
resources/test/fixtures/pyupgrade/UP026.py
vendored
Normal file
74
resources/test/fixtures/pyupgrade/UP026.py
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
# These should be changed
|
||||
if True:
|
||||
import mock
|
||||
|
||||
if True:
|
||||
import mock, sys
|
||||
|
||||
# This goes to from unitest import mock
|
||||
import mock.mock
|
||||
|
||||
# Mock should go on a new line as `from unittest import mock`
|
||||
import contextlib, mock, sys
|
||||
|
||||
# Mock should go on a new line as `from unittest import mock`
|
||||
import mock, sys
|
||||
x = "This code should be preserved one line below the mock"
|
||||
|
||||
# Mock should go on a new line as `from unittest import mock`
|
||||
from mock import mock
|
||||
|
||||
# Should keep trailing comma
|
||||
from mock import (
|
||||
mock,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
)
|
||||
|
||||
# Should not get a trailing comma
|
||||
from mock import (
|
||||
mock,
|
||||
a,
|
||||
b,
|
||||
c
|
||||
)
|
||||
|
||||
if True:
|
||||
if False:
|
||||
from mock import (
|
||||
mock,
|
||||
a,
|
||||
b,
|
||||
c
|
||||
)
|
||||
|
||||
# These should not change:
|
||||
import os, io
|
||||
|
||||
# Mock should go on a new line as `from unittest import mock`
|
||||
import mock, mock
|
||||
|
||||
# Mock should go on a new line as `from unittest import mock as foo`
|
||||
import mock as foo
|
||||
|
||||
# Mock should go on a new line as `from unittest import mock as foo`
|
||||
from mock import mock as foo
|
||||
|
||||
if True:
|
||||
# This should yield multiple, aliased imports.
|
||||
import mock as foo, mock as bar, mock
|
||||
|
||||
# This should yield multiple, aliased imports, and preserve `os`.
|
||||
import mock as foo, mock as bar, mock, os
|
||||
|
||||
if True:
|
||||
# This should yield multiple, aliased imports.
|
||||
from mock import mock as foo, mock as bar, mock
|
||||
|
||||
|
||||
# This should be unchanged.
|
||||
x = mock.Mock()
|
||||
|
||||
# This should change to `mock.Mock`().
|
||||
x = mock.mock.Mock()
|
||||
64
resources/test/fixtures/ruff/RUF100_1.py
vendored
Normal file
64
resources/test/fixtures/ruff/RUF100_1.py
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
# This should ignore both errors.
|
||||
from typing import ( # noqa: F401
|
||||
List,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
# This should ignore both errors.
|
||||
from typing import ( # noqa
|
||||
List,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
# This should ignore both errors.
|
||||
from typing import (
|
||||
List, # noqa: F401
|
||||
Sequence, # noqa: F401
|
||||
)
|
||||
|
||||
# This should ignore both errors.
|
||||
from typing import (
|
||||
List, # noqa
|
||||
Sequence, # noqa
|
||||
)
|
||||
|
||||
# This should ignore the first error.
|
||||
from typing import (
|
||||
Mapping, # noqa: F401
|
||||
Union,
|
||||
)
|
||||
|
||||
# This should ignore both errors.
|
||||
from typing import ( # noqa
|
||||
List,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
# This should ignore the error, but the inner noqa should be marked as unused.
|
||||
from typing import ( # noqa: F401
|
||||
Optional, # noqa: F401
|
||||
)
|
||||
|
||||
# This should ignore the error, but the inner noqa should be marked as unused.
|
||||
from typing import ( # noqa
|
||||
Optional, # noqa: F401
|
||||
)
|
||||
|
||||
# This should ignore the error, but mark F501 as unused.
|
||||
from typing import ( # noqa: F401
|
||||
Dict, # noqa: F501
|
||||
)
|
||||
|
||||
# This should ignore the error, but mark F501 as unused.
|
||||
from typing import ( # noqa: F501
|
||||
Tuple, # noqa: F401
|
||||
)
|
||||
|
||||
# This should ignore both errors.
|
||||
from typing import Any, AnyStr # noqa: F401
|
||||
|
||||
# This should ignore both errors.
|
||||
from typing import AsyncIterable, AsyncGenerator # noqa
|
||||
|
||||
# This should mark F501 as unused.
|
||||
from typing import Awaitable, AwaitableGenerator # noqa: F501
|
||||
102
ruff.schema.json
102
ruff.schema.json
@@ -364,10 +364,30 @@
|
||||
"items": {
|
||||
"$ref": "#/definitions/CheckCodePrefix"
|
||||
}
|
||||
},
|
||||
"update-check": {
|
||||
"description": "Enable or disable automatic update checks (overridden by the `--update-check` and `--no-update-check` command-line flags).",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"BannedApi": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"msg"
|
||||
],
|
||||
"properties": {
|
||||
"msg": {
|
||||
"description": "The message to display when the API is used.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"CheckCodePrefix": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -632,8 +652,6 @@
|
||||
"F821",
|
||||
"F822",
|
||||
"F823",
|
||||
"F83",
|
||||
"F831",
|
||||
"F84",
|
||||
"F841",
|
||||
"F842",
|
||||
@@ -663,6 +681,12 @@
|
||||
"ICN0",
|
||||
"ICN00",
|
||||
"ICN001",
|
||||
"ISC",
|
||||
"ISC0",
|
||||
"ISC00",
|
||||
"ISC001",
|
||||
"ISC002",
|
||||
"ISC003",
|
||||
"M",
|
||||
"M0",
|
||||
"M001",
|
||||
@@ -726,6 +750,7 @@
|
||||
"PGH001",
|
||||
"PGH002",
|
||||
"PGH003",
|
||||
"PGH004",
|
||||
"PLC",
|
||||
"PLC0",
|
||||
"PLC04",
|
||||
@@ -834,6 +859,7 @@
|
||||
"TID",
|
||||
"TID2",
|
||||
"TID25",
|
||||
"TID251",
|
||||
"TID252",
|
||||
"U",
|
||||
"U0",
|
||||
@@ -883,6 +909,9 @@
|
||||
"UP021",
|
||||
"UP022",
|
||||
"UP023",
|
||||
"UP024",
|
||||
"UP025",
|
||||
"UP026",
|
||||
"W",
|
||||
"W2",
|
||||
"W29",
|
||||
@@ -910,10 +939,21 @@
|
||||
]
|
||||
},
|
||||
"Convention": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"google",
|
||||
"numpy"
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Use Google-style docstrings.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"google"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Use NumPy-style docstrings.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"numpy"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"Flake8AnnotationsOptions": {
|
||||
@@ -1057,7 +1097,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ban-relative-imports": {
|
||||
"description": "Whether to ban all relative imports (`\"all\"`), or only those imports that extend into the parent module and beyond (`\"parents\"`).",
|
||||
"description": "Whether to ban all relative imports (`\"all\"`), or only those imports that extend into the parent module or beyond (`\"parents\"`).",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Strictness"
|
||||
@@ -1066,6 +1106,16 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"banned-api": {
|
||||
"description": "Specific modules or module members that may not be imported or accessed. Note that this check is only meant to flag accidental uses, and can be circumvented via `eval` or `importlib`.",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/BannedApi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -1253,10 +1303,21 @@
|
||||
]
|
||||
},
|
||||
"Quote": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"single",
|
||||
"double"
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Use single quotes (`'`).",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"single"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Use double quotes (`\"`).",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"double"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"SerializationFormat": {
|
||||
@@ -1271,10 +1332,21 @@
|
||||
]
|
||||
},
|
||||
"Strictness": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"parents",
|
||||
"all"
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Ban imports that extend into the parent module or beyond.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"parents"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Ban all relative imports.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"all"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"Version": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.199"
|
||||
version = "0.0.206"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -11,9 +11,9 @@ itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
|
||||
once_cell = { version = "1.16.0" }
|
||||
ruff = { path = ".." }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "71becd4059fdce4bce7010f1208ed3b1c883abba" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "71becd4059fdce4bce7010f1208ed3b1c883abba" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "71becd4059fdce4bce7010f1208ed3b1c883abba" }
|
||||
schemars = { version = "0.8.11" }
|
||||
serde_json = {version="1.0.91"}
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
|
||||
@@ -4,8 +4,8 @@ use anyhow::Result;
|
||||
use clap::Args;
|
||||
|
||||
use crate::{
|
||||
generate_check_code_prefix, generate_json_schema, generate_options,
|
||||
generate_playground_options, generate_rules_table,
|
||||
generate_check_code_prefix, generate_cli_help, generate_json_schema, generate_options,
|
||||
generate_rules_table,
|
||||
};
|
||||
|
||||
#[derive(Args)]
|
||||
@@ -28,7 +28,7 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
generate_options::main(&generate_options::Cli {
|
||||
dry_run: cli.dry_run,
|
||||
})?;
|
||||
generate_playground_options::main(&generate_playground_options::Cli {
|
||||
generate_cli_help::main(&generate_cli_help::Cli {
|
||||
dry_run: cli.dry_run,
|
||||
})?;
|
||||
Ok(())
|
||||
|
||||
38
ruff_dev/src/generate_cli_help.rs
Normal file
38
ruff_dev/src/generate_cli_help.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
//! Generate CLI help.
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Args, CommandFactory};
|
||||
use ruff::cli::Cli as MainCli;
|
||||
|
||||
use crate::utils::replace_readme_section;
|
||||
|
||||
const HELP_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated cli help. -->";
|
||||
const HELP_END_PRAGMA: &str = "<!-- End auto-generated cli help. -->";
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Cli {
|
||||
/// Write the generated help to stdout (rather than to `README.md`).
|
||||
#[arg(long)]
|
||||
pub(crate) dry_run: bool,
|
||||
}
|
||||
|
||||
fn trim_lines(s: &str) -> String {
|
||||
s.lines().map(str::trim_end).collect::<Vec<_>>().join("\n")
|
||||
}
|
||||
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
let mut cmd = MainCli::command();
|
||||
let output = trim_lines(cmd.render_help().to_string().trim());
|
||||
|
||||
if cli.dry_run {
|
||||
print!("{output}");
|
||||
} else {
|
||||
replace_readme_section(
|
||||
&format!("```shell\n{output}\n```\n"),
|
||||
HELP_BEGIN_PRAGMA,
|
||||
HELP_END_PRAGMA,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,16 +1,13 @@
|
||||
//! Generate a Markdown-compatible listing of configuration options.
|
||||
|
||||
use std::fs;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use itertools::Itertools;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::options_base::{ConfigurationOptions, OptionEntry, OptionField};
|
||||
|
||||
use crate::utils::replace_readme_section;
|
||||
|
||||
const BEGIN_PRAGMA: &str = "<!-- Begin auto-generated options sections. -->";
|
||||
const END_PRAGMA: &str = "<!-- End auto-generated options sections. -->";
|
||||
|
||||
@@ -95,30 +92,7 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
if cli.dry_run {
|
||||
print!("{output}");
|
||||
} else {
|
||||
// Read the existing file.
|
||||
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.expect("Failed to find root directory")
|
||||
.join("README.md");
|
||||
let existing = fs::read_to_string(&file)?;
|
||||
|
||||
// Extract the prefix.
|
||||
let index = existing
|
||||
.find(BEGIN_PRAGMA)
|
||||
.expect("Unable to find begin pragma");
|
||||
let prefix = &existing[..index + BEGIN_PRAGMA.len()];
|
||||
|
||||
// Extract the suffix.
|
||||
let index = existing
|
||||
.find(END_PRAGMA)
|
||||
.expect("Unable to find end pragma");
|
||||
let suffix = &existing[index..];
|
||||
|
||||
// Write the prefix, new contents, and suffix.
|
||||
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
|
||||
write!(f, "{prefix}\n\n")?;
|
||||
write!(f, "{output}")?;
|
||||
write!(f, "{suffix}")?;
|
||||
replace_readme_section(&output, BEGIN_PRAGMA, END_PRAGMA)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
//! Generate typescript file defining options to be used by the web playground.
|
||||
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use itertools::Itertools;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::options_base::{ConfigurationOptions, OptionEntry, OptionField};
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Cli {
|
||||
/// Write the generated table to stdout (rather than to `TODO`).
|
||||
#[arg(long)]
|
||||
pub(crate) dry_run: bool,
|
||||
}
|
||||
|
||||
fn emit_field(output: &mut String, field: &OptionField) {
|
||||
output.push_str(&textwrap::indent(
|
||||
&textwrap::dedent(&format!(
|
||||
"
|
||||
{{
|
||||
\"name\": \"{}\",
|
||||
\"default\": '{}',
|
||||
\"type\": '{}',
|
||||
}},",
|
||||
field.name, field.default, field.value_type
|
||||
)),
|
||||
" ",
|
||||
));
|
||||
}
|
||||
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
let mut output = String::new();
|
||||
|
||||
// Generate all the top-level fields.
|
||||
output.push_str(&format!("{{\"name\": \"{}\", \"fields\": [", "globals"));
|
||||
for field in Options::get_available_options()
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
if let OptionEntry::Field(field) = entry {
|
||||
Some(field)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
// Filter out options that don't make sense in the playground.
|
||||
.filter(|field| {
|
||||
!matches!(
|
||||
field.name,
|
||||
"src"
|
||||
| "fix"
|
||||
| "format"
|
||||
| "exclude"
|
||||
| "extend"
|
||||
| "extend-exclude"
|
||||
| "fixable"
|
||||
| "force-exclude"
|
||||
| "ignore-init-module-imports"
|
||||
| "respect-gitignore"
|
||||
| "show-source"
|
||||
| "cache-dir"
|
||||
| "per-file-ignores"
|
||||
)
|
||||
})
|
||||
.sorted_by_key(|field| field.name)
|
||||
{
|
||||
emit_field(&mut output, &field);
|
||||
}
|
||||
output.push_str("\n]},\n");
|
||||
|
||||
// Generate all the sub-groups.
|
||||
for group in Options::get_available_options()
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
if let OptionEntry::Group(group) = entry {
|
||||
Some(group)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sorted_by_key(|group| group.name)
|
||||
{
|
||||
output.push_str(&format!("{{\"name\": \"{}\", \"fields\": [", group.name));
|
||||
for field in group
|
||||
.fields
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
if let OptionEntry::Field(field) = entry {
|
||||
Some(field)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sorted_by_key(|field| field.name)
|
||||
{
|
||||
emit_field(&mut output, field);
|
||||
}
|
||||
output.push_str("\n]},\n");
|
||||
}
|
||||
|
||||
let prefix = textwrap::dedent(
|
||||
r"
|
||||
// This file is auto-generated by `cargo dev generate-playground-options`.
|
||||
export interface OptionGroup {
|
||||
name: string;
|
||||
fields: {
|
||||
name: string;
|
||||
default: string;
|
||||
type: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const AVAILABLE_OPTIONS: OptionGroup[] = [
|
||||
",
|
||||
);
|
||||
let postfix = "];";
|
||||
|
||||
if cli.dry_run {
|
||||
print!("{output}");
|
||||
} else {
|
||||
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.expect("Failed to find root directory")
|
||||
.join("playground")
|
||||
.join("src")
|
||||
.join("ruff_options.ts");
|
||||
|
||||
let mut f = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(file)?;
|
||||
write!(f, "{prefix}")?;
|
||||
write!(f, "{}", textwrap::indent(&output, " "))?;
|
||||
write!(f, "{postfix}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,16 +1,13 @@
|
||||
//! Generate a Markdown-compatible table of supported lint rules.
|
||||
|
||||
use std::fs;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use itertools::Itertools;
|
||||
use ruff::checks::{CheckCategory, CheckCode};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::utils::replace_readme_section;
|
||||
|
||||
const TABLE_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated sections. -->";
|
||||
const TABLE_END_PRAGMA: &str = "<!-- End auto-generated sections. -->";
|
||||
|
||||
@@ -85,32 +82,3 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn replace_readme_section(content: &str, begin_pragma: &str, end_pragma: &str) -> Result<()> {
|
||||
// Read the existing file.
|
||||
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.expect("Failed to find root directory")
|
||||
.join("README.md");
|
||||
let existing = fs::read_to_string(&file)?;
|
||||
|
||||
// Extract the prefix.
|
||||
let index = existing
|
||||
.find(begin_pragma)
|
||||
.expect("Unable to find begin pragma");
|
||||
let prefix = &existing[..index + begin_pragma.len()];
|
||||
|
||||
// Extract the suffix.
|
||||
let index = existing
|
||||
.find(end_pragma)
|
||||
.expect("Unable to find end pragma");
|
||||
let suffix = &existing[index..];
|
||||
|
||||
// Write the prefix, new contents, and suffix.
|
||||
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
|
||||
writeln!(f, "{prefix}")?;
|
||||
write!(f, "{content}")?;
|
||||
write!(f, "{suffix}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
|
||||
pub mod generate_all;
|
||||
pub mod generate_check_code_prefix;
|
||||
pub mod generate_cli_help;
|
||||
pub mod generate_json_schema;
|
||||
pub mod generate_options;
|
||||
pub mod generate_playground_options;
|
||||
pub mod generate_rules_table;
|
||||
pub mod print_ast;
|
||||
pub mod print_cst;
|
||||
pub mod print_tokens;
|
||||
pub mod round_trip;
|
||||
mod utils;
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use ruff_dev::{
|
||||
generate_all, generate_check_code_prefix, generate_json_schema, generate_options,
|
||||
generate_playground_options, generate_rules_table, print_ast, print_cst, print_tokens,
|
||||
round_trip,
|
||||
generate_all, generate_check_code_prefix, generate_cli_help, generate_json_schema,
|
||||
generate_options, generate_rules_table, print_ast, print_cst, print_tokens, round_trip,
|
||||
};
|
||||
|
||||
#[derive(Parser)]
|
||||
@@ -39,9 +38,8 @@ enum Commands {
|
||||
GenerateRulesTable(generate_rules_table::Cli),
|
||||
/// Generate a Markdown-compatible listing of configuration options.
|
||||
GenerateOptions(generate_options::Cli),
|
||||
/// Generate typescript file defining options to be used by the web
|
||||
/// playground.
|
||||
GeneratePlaygroundOptions(generate_playground_options::Cli),
|
||||
/// Generate CLI help.
|
||||
GenerateCliHelp(generate_cli_help::Cli),
|
||||
/// Print the AST for a given Python file.
|
||||
PrintAST(print_ast::Cli),
|
||||
/// Print the LibCST CST for a given Python file.
|
||||
@@ -60,7 +58,7 @@ fn main() -> Result<()> {
|
||||
Commands::GenerateJSONSchema(args) => generate_json_schema::main(args)?,
|
||||
Commands::GenerateRulesTable(args) => generate_rules_table::main(args)?,
|
||||
Commands::GenerateOptions(args) => generate_options::main(args)?,
|
||||
Commands::GeneratePlaygroundOptions(args) => generate_playground_options::main(args)?,
|
||||
Commands::GenerateCliHelp(args) => generate_cli_help::main(args)?,
|
||||
Commands::PrintAST(args) => print_ast::main(args)?,
|
||||
Commands::PrintCST(args) => print_cst::main(args)?,
|
||||
Commands::PrintTokens(args) => print_tokens::main(args)?,
|
||||
|
||||
@@ -22,7 +22,11 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
let mut generator = SourceCodeGenerator::new(
|
||||
stylist.indentation(),
|
||||
stylist.quote(),
|
||||
stylist.line_ending(),
|
||||
);
|
||||
generator.unparse_suite(&python_ast);
|
||||
println!("{}", generator.generate()?);
|
||||
Ok(())
|
||||
|
||||
35
ruff_dev/src/utils.rs
Normal file
35
ruff_dev/src/utils.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use std::fs;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
pub fn replace_readme_section(content: &str, begin_pragma: &str, end_pragma: &str) -> Result<()> {
|
||||
// Read the existing file.
|
||||
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.expect("Failed to find root directory")
|
||||
.join("README.md");
|
||||
let existing = fs::read_to_string(&file)?;
|
||||
|
||||
// Extract the prefix.
|
||||
let index = existing
|
||||
.find(begin_pragma)
|
||||
.expect("Unable to find begin pragma");
|
||||
let prefix = &existing[..index + begin_pragma.len()];
|
||||
|
||||
// Extract the suffix.
|
||||
let index = existing
|
||||
.find(end_pragma)
|
||||
.expect("Unable to find end pragma");
|
||||
let suffix = &existing[index..];
|
||||
|
||||
// Write the prefix, new contents, and suffix.
|
||||
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
|
||||
writeln!(f, "{prefix}")?;
|
||||
write!(f, "{content}")?;
|
||||
write!(f, "{suffix}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.199"
|
||||
version = "0.0.206"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -5,7 +5,7 @@ use regex::Regex;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::{
|
||||
Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword, KeywordData,
|
||||
Location, Stmt, StmtKind,
|
||||
Located, Location, Stmt, StmtKind,
|
||||
};
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::Tok;
|
||||
@@ -335,20 +335,17 @@ pub fn to_absolute(relative: Location, base: Location) -> Location {
|
||||
|
||||
/// Return `true` if a `Stmt` has leading content.
|
||||
pub fn match_leading_content(stmt: &Stmt, locator: &SourceCodeLocator) -> bool {
|
||||
let range = Range {
|
||||
location: Location::new(stmt.location.row(), 0),
|
||||
end_location: stmt.location,
|
||||
};
|
||||
let range = Range::new(Location::new(stmt.location.row(), 0), stmt.location);
|
||||
let prefix = locator.slice_source_code_range(&range);
|
||||
prefix.chars().any(|char| !char.is_whitespace())
|
||||
}
|
||||
|
||||
/// Return `true` if a `Stmt` has trailing content.
|
||||
pub fn match_trailing_content(stmt: &Stmt, locator: &SourceCodeLocator) -> bool {
|
||||
let range = Range {
|
||||
location: stmt.end_location.unwrap(),
|
||||
end_location: Location::new(stmt.end_location.unwrap().row() + 1, 0),
|
||||
};
|
||||
let range = Range::new(
|
||||
stmt.end_location.unwrap(),
|
||||
Location::new(stmt.end_location.unwrap().row() + 1, 0),
|
||||
);
|
||||
let suffix = locator.slice_source_code_range(&range);
|
||||
for char in suffix.chars() {
|
||||
if char == '#' {
|
||||
@@ -384,10 +381,7 @@ pub fn identifier_range(stmt: &Stmt, locator: &SourceCodeLocator) -> Range {
|
||||
let contents = locator.slice_source_code_range(&Range::from_located(stmt));
|
||||
for (start, tok, end) in lexer::make_tokenizer_located(&contents, stmt.location).flatten() {
|
||||
if matches!(tok, Tok::Name { .. }) {
|
||||
return Range {
|
||||
location: start,
|
||||
end_location: end,
|
||||
};
|
||||
return Range::new(start, end);
|
||||
}
|
||||
}
|
||||
error!("Failed to find identifier for {:?}", stmt);
|
||||
@@ -395,6 +389,19 @@ pub fn identifier_range(stmt: &Stmt, locator: &SourceCodeLocator) -> Range {
|
||||
Range::from_located(stmt)
|
||||
}
|
||||
|
||||
// Return the ranges of `Name` tokens within a specified node.
|
||||
pub fn find_names<T>(located: &Located<T>, locator: &SourceCodeLocator) -> Vec<Range> {
|
||||
let contents = locator.slice_source_code_range(&Range::from_located(located));
|
||||
lexer::make_tokenizer_located(&contents, located.location)
|
||||
.flatten()
|
||||
.filter(|(_, tok, _)| matches!(tok, Tok::Name { .. }))
|
||||
.map(|(start, _, end)| Range {
|
||||
location: start,
|
||||
end_location: end,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Return the `Range` of `name` in `Excepthandler`.
|
||||
pub fn excepthandler_name_range(
|
||||
handler: &Excepthandler,
|
||||
@@ -406,17 +413,70 @@ pub fn excepthandler_name_range(
|
||||
match (name, type_) {
|
||||
(Some(_), Some(type_)) => {
|
||||
let type_end_location = type_.end_location.unwrap();
|
||||
let contents = locator.slice_source_code_range(&Range {
|
||||
location: type_end_location,
|
||||
end_location: body[0].location,
|
||||
});
|
||||
let contents =
|
||||
locator.slice_source_code_range(&Range::new(type_end_location, body[0].location));
|
||||
let range = lexer::make_tokenizer_located(&contents, type_end_location)
|
||||
.flatten()
|
||||
.tuple_windows()
|
||||
.find(|(tok, next_tok)| {
|
||||
matches!(tok.1, Tok::As) && matches!(next_tok.1, Tok::Name { .. })
|
||||
})
|
||||
.map(|((..), (location, _, end_location))| Range {
|
||||
.map(|((..), (location, _, end_location))| Range::new(location, end_location));
|
||||
range
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the `Range` of `except` in `Excepthandler`.
|
||||
pub fn except_range(handler: &Excepthandler, locator: &SourceCodeLocator) -> Range {
|
||||
let ExcepthandlerKind::ExceptHandler { body, type_, .. } = &handler.node;
|
||||
let end = if let Some(type_) = type_ {
|
||||
type_.location
|
||||
} else {
|
||||
body.first()
|
||||
.expect("Expected body to be non-empty")
|
||||
.location
|
||||
};
|
||||
let contents = locator.slice_source_code_range(&Range {
|
||||
location: handler.location,
|
||||
end_location: end,
|
||||
});
|
||||
let range = lexer::make_tokenizer_located(&contents, handler.location)
|
||||
.flatten()
|
||||
.find(|(_, kind, _)| matches!(kind, Tok::Except { .. }))
|
||||
.map(|(location, _, end_location)| Range {
|
||||
location,
|
||||
end_location,
|
||||
})
|
||||
.expect("Failed to find `except` range");
|
||||
range
|
||||
}
|
||||
|
||||
/// Return the `Range` of `else` in `For`, `AsyncFor`, and `While` statements.
|
||||
pub fn else_range(stmt: &Stmt, locator: &SourceCodeLocator) -> Option<Range> {
|
||||
match &stmt.node {
|
||||
StmtKind::For { body, orelse, .. }
|
||||
| StmtKind::AsyncFor { body, orelse, .. }
|
||||
| StmtKind::While { body, orelse, .. }
|
||||
if !orelse.is_empty() =>
|
||||
{
|
||||
let body_end = body
|
||||
.last()
|
||||
.expect("Expected body to be non-empty")
|
||||
.end_location
|
||||
.unwrap();
|
||||
let contents = locator.slice_source_code_range(&Range {
|
||||
location: body_end,
|
||||
end_location: orelse
|
||||
.first()
|
||||
.expect("Expected orelse to be non-empty")
|
||||
.location,
|
||||
});
|
||||
let range = lexer::make_tokenizer_located(&contents, body_end)
|
||||
.flatten()
|
||||
.find(|(_, kind, _)| matches!(kind, Tok::Else))
|
||||
.map(|(location, _, end_location)| Range {
|
||||
location,
|
||||
end_location,
|
||||
});
|
||||
@@ -435,10 +495,10 @@ pub fn preceded_by_continuation(stmt: &Stmt, locator: &SourceCodeLocator) -> boo
|
||||
// make conservative choices.
|
||||
// TODO(charlie): Come up with a more robust strategy.
|
||||
if stmt.location.row() > 1 {
|
||||
let range = Range {
|
||||
location: Location::new(stmt.location.row() - 1, 0),
|
||||
end_location: Location::new(stmt.location.row(), 0),
|
||||
};
|
||||
let range = Range::new(
|
||||
Location::new(stmt.location.row() - 1, 0),
|
||||
Location::new(stmt.location.row(), 0),
|
||||
);
|
||||
let line = locator.slice_source_code_range(&range);
|
||||
if line.trim().ends_with('\\') {
|
||||
return true;
|
||||
@@ -466,7 +526,9 @@ mod tests {
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::ast::helpers::{identifier_range, match_module_member, match_trailing_content};
|
||||
use crate::ast::helpers::{
|
||||
else_range, identifier_range, match_module_member, match_trailing_content,
|
||||
};
|
||||
use crate::ast::types::Range;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
@@ -660,10 +722,7 @@ y = 2
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
identifier_range(stmt, &locator),
|
||||
Range {
|
||||
location: Location::new(1, 4),
|
||||
end_location: Location::new(1, 5),
|
||||
}
|
||||
Range::new(Location::new(1, 4), Location::new(1, 5),)
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
@@ -677,10 +736,7 @@ def \
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
identifier_range(stmt, &locator),
|
||||
Range {
|
||||
location: Location::new(2, 2),
|
||||
end_location: Location::new(2, 3),
|
||||
}
|
||||
Range::new(Location::new(2, 2), Location::new(2, 3),)
|
||||
);
|
||||
|
||||
let contents = "class Class(): pass".trim();
|
||||
@@ -689,10 +745,7 @@ def \
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
identifier_range(stmt, &locator),
|
||||
Range {
|
||||
location: Location::new(1, 6),
|
||||
end_location: Location::new(1, 11),
|
||||
}
|
||||
Range::new(Location::new(1, 6), Location::new(1, 11),)
|
||||
);
|
||||
|
||||
let contents = "class Class: pass".trim();
|
||||
@@ -701,10 +754,7 @@ def \
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
identifier_range(stmt, &locator),
|
||||
Range {
|
||||
location: Location::new(1, 6),
|
||||
end_location: Location::new(1, 11),
|
||||
}
|
||||
Range::new(Location::new(1, 6), Location::new(1, 11),)
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
@@ -718,10 +768,7 @@ class Class():
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
identifier_range(stmt, &locator),
|
||||
Range {
|
||||
location: Location::new(2, 6),
|
||||
end_location: Location::new(2, 11),
|
||||
}
|
||||
Range::new(Location::new(2, 6), Location::new(2, 11),)
|
||||
);
|
||||
|
||||
let contents = r#"x = y + 1"#.trim();
|
||||
@@ -730,12 +777,29 @@ class Class():
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
identifier_range(stmt, &locator),
|
||||
Range {
|
||||
location: Location::new(1, 0),
|
||||
end_location: Location::new(1, 9),
|
||||
}
|
||||
Range::new(Location::new(1, 0), Location::new(1, 9),)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_else_range() -> Result<()> {
|
||||
let contents = r#"
|
||||
for x in y:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
"#
|
||||
.trim();
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
let range = else_range(stmt, &locator).unwrap();
|
||||
assert_eq!(range.location.row(), 3);
|
||||
assert_eq!(range.location.column(), 0);
|
||||
assert_eq!(range.end_location.row(), 3);
|
||||
assert_eq!(range.end_location.column(), 4);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,16 @@ pub struct Range {
|
||||
}
|
||||
|
||||
impl Range {
|
||||
pub fn from_located<T>(located: &Located<T>) -> Self {
|
||||
Range {
|
||||
location: located.location,
|
||||
end_location: located.end_location.unwrap(),
|
||||
pub fn new(location: Location, end_location: Location) -> Self {
|
||||
Self {
|
||||
location,
|
||||
end_location,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_located<T>(located: &Located<T>) -> Self {
|
||||
Range::new(located.location, located.end_location.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -38,6 +38,9 @@ pub trait Visitor<'a> {
|
||||
fn visit_excepthandler(&mut self, excepthandler: &'a Excepthandler) {
|
||||
walk_excepthandler(self, excepthandler);
|
||||
}
|
||||
fn visit_format_spec(&mut self, format_spec: &'a Expr) {
|
||||
walk_expr(self, format_spec);
|
||||
}
|
||||
fn visit_arguments(&mut self, arguments: &'a Arguments) {
|
||||
walk_arguments(self, arguments);
|
||||
}
|
||||
@@ -387,7 +390,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
|
||||
} => {
|
||||
visitor.visit_expr(value);
|
||||
if let Some(expr) = format_spec {
|
||||
visitor.visit_expr(expr);
|
||||
visitor.visit_format_spec(expr);
|
||||
}
|
||||
}
|
||||
ExprKind::JoinedStr { values } => {
|
||||
|
||||
@@ -9,10 +9,10 @@ use crate::checkers::ast::Checker;
|
||||
/// Extract the leading indentation from a line.
|
||||
pub fn indentation<'a, T>(checker: &'a Checker, located: &'a Located<T>) -> Cow<'a, str> {
|
||||
let range = Range::from_located(located);
|
||||
checker.locator.slice_source_code_range(&Range {
|
||||
location: Location::new(range.location.row(), 0),
|
||||
end_location: Location::new(range.location.row(), range.location.column()),
|
||||
})
|
||||
checker.locator.slice_source_code_range(&Range::new(
|
||||
Location::new(range.location.row(), 0),
|
||||
Location::new(range.location.row(), range.location.column()),
|
||||
))
|
||||
}
|
||||
|
||||
/// Extract the leading words from a line of text.
|
||||
|
||||
@@ -68,10 +68,7 @@ fn apply_fixes<'a>(
|
||||
}
|
||||
|
||||
// Add all contents from `last_pos` to `fix.location`.
|
||||
let slice = locator.slice_source_code_range(&Range {
|
||||
location: last_pos,
|
||||
end_location: fix.location,
|
||||
});
|
||||
let slice = locator.slice_source_code_range(&Range::new(last_pos, fix.location));
|
||||
output.append(&slice);
|
||||
|
||||
// Add the patch itself.
|
||||
|
||||
@@ -15,8 +15,9 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::message::Message;
|
||||
use crate::settings::{flags, Settings};
|
||||
|
||||
static CACHE_DIR: Lazy<Option<String>> = Lazy::new(|| std::env::var("RUFF_CACHE_DIR").ok());
|
||||
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static CACHE_DIR: Lazy<Option<String>> = Lazy::new(|| std::env::var("RUFF_CACHE_DIR").ok());
|
||||
pub const DEFAULT_CACHE_DIR_NAME: &str = ".ruff_cache";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct CacheMetadata {
|
||||
@@ -40,7 +41,7 @@ struct CheckResult {
|
||||
pub fn cache_dir(project_root: &Path) -> PathBuf {
|
||||
CACHE_DIR
|
||||
.as_ref()
|
||||
.map_or_else(|| project_root.join(".ruff_cache"), PathBuf::from)
|
||||
.map_or_else(|| project_root.join(DEFAULT_CACHE_DIR_NAME), PathBuf::from)
|
||||
}
|
||||
|
||||
fn content_dir() -> &'static Path {
|
||||
|
||||
@@ -39,10 +39,10 @@ use crate::visibility::{module_visibility, transition_scope, Modifier, Visibilit
|
||||
use crate::{
|
||||
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
|
||||
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez,
|
||||
flake8_debugger, flake8_errmsg, flake8_import_conventions, flake8_print, flake8_return,
|
||||
flake8_simplify, flake8_tidy_imports, flake8_unused_arguments, mccabe, noqa, pandas_vet,
|
||||
pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff,
|
||||
visibility,
|
||||
flake8_debugger, flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions,
|
||||
flake8_print, flake8_return, flake8_simplify, flake8_tidy_imports, flake8_unused_arguments,
|
||||
mccabe, noqa, pandas_vet, pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint,
|
||||
pyupgrade, ruff, visibility,
|
||||
};
|
||||
|
||||
const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
@@ -87,7 +87,6 @@ pub struct Checker<'a> {
|
||||
deferred_assignments: Vec<DeferralContext<'a>>,
|
||||
// Internal, derivative state.
|
||||
visible_scope: VisibleScope,
|
||||
in_f_string: Option<Range>,
|
||||
in_annotation: bool,
|
||||
in_type_definition: bool,
|
||||
in_deferred_string_type_definition: bool,
|
||||
@@ -144,7 +143,6 @@ impl<'a> Checker<'a> {
|
||||
modifier: Modifier::Module,
|
||||
visibility: module_visibility(path),
|
||||
},
|
||||
in_f_string: None,
|
||||
in_annotation: false,
|
||||
in_type_definition: false,
|
||||
in_deferred_string_type_definition: false,
|
||||
@@ -161,14 +159,7 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
/// Add a `Check` to the `Checker`.
|
||||
pub(crate) fn add_check(&mut self, mut check: Check) {
|
||||
// If we're in an f-string, override the location. RustPython doesn't produce
|
||||
// reliable locations for expressions within f-strings, so we use the
|
||||
// span of the f-string itself as a best-effort default.
|
||||
if let Some(range) = self.in_f_string {
|
||||
check.location = range.location;
|
||||
check.end_location = range.end_location;
|
||||
}
|
||||
pub(crate) fn add_check(&mut self, check: Check) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
|
||||
@@ -184,16 +175,7 @@ impl<'a> Checker<'a> {
|
||||
pub fn patch(&self, code: &CheckCode) -> bool {
|
||||
// TODO(charlie): We can't fix errors in f-strings until RustPython adds
|
||||
// location data.
|
||||
matches!(self.autofix, flags::Autofix::Enabled)
|
||||
&& self.in_f_string.is_none()
|
||||
&& self.settings.fixable.contains(code)
|
||||
}
|
||||
|
||||
/// Return the amended `Range` from a `Located`.
|
||||
pub fn range_for<T>(&self, located: &Located<T>) -> Range {
|
||||
// If we're in an f-string, override the location.
|
||||
self.in_f_string
|
||||
.unwrap_or_else(|| Range::from_located(located))
|
||||
matches!(self.autofix, flags::Autofix::Enabled) && self.settings.fixable.contains(code)
|
||||
}
|
||||
|
||||
/// Return `true` if the `Expr` is a reference to `typing.${target}`.
|
||||
@@ -231,10 +213,10 @@ impl<'a> Checker<'a> {
|
||||
return false;
|
||||
}
|
||||
let noqa_lineno = self.noqa_line_for.get(&lineno).unwrap_or(&lineno);
|
||||
let line = self.locator.slice_source_code_range(&Range {
|
||||
location: Location::new(*noqa_lineno, 0),
|
||||
end_location: Location::new(noqa_lineno + 1, 0),
|
||||
});
|
||||
let line = self.locator.slice_source_code_range(&Range::new(
|
||||
Location::new(*noqa_lineno, 0),
|
||||
Location::new(noqa_lineno + 1, 0),
|
||||
));
|
||||
match noqa::extract_noqa_directive(&line) {
|
||||
Directive::None => false,
|
||||
Directive::All(..) => true,
|
||||
@@ -282,16 +264,17 @@ where
|
||||
match &stmt.node {
|
||||
StmtKind::Global { names } => {
|
||||
let scope_index = *self.scope_stack.last().expect("No current scope found");
|
||||
let ranges = helpers::find_names(stmt, self.locator);
|
||||
if scope_index != GLOBAL_SCOPE_INDEX {
|
||||
// Add the binding to the current scope.
|
||||
let scope = &mut self.scopes[scope_index];
|
||||
let usage = Some((scope.id, Range::from_located(stmt)));
|
||||
for name in names {
|
||||
for (name, range) in names.iter().zip(ranges.iter()) {
|
||||
let index = self.bindings.len();
|
||||
self.bindings.push(Binding {
|
||||
kind: BindingKind::Global,
|
||||
used: usage,
|
||||
range: Range::from_located(stmt),
|
||||
range: *range,
|
||||
source: Some(RefEquality(stmt)),
|
||||
});
|
||||
scope.values.insert(name, index);
|
||||
@@ -302,8 +285,9 @@ where
|
||||
self.add_checks(
|
||||
names
|
||||
.iter()
|
||||
.filter_map(|name| {
|
||||
pycodestyle::checks::ambiguous_variable_name(name, stmt)
|
||||
.zip(ranges.iter())
|
||||
.filter_map(|(name, range)| {
|
||||
pycodestyle::checks::ambiguous_variable_name(name, *range)
|
||||
})
|
||||
.into_iter(),
|
||||
);
|
||||
@@ -311,16 +295,17 @@ where
|
||||
}
|
||||
StmtKind::Nonlocal { names } => {
|
||||
let scope_index = *self.scope_stack.last().expect("No current scope found");
|
||||
let ranges = helpers::find_names(stmt, self.locator);
|
||||
if scope_index != GLOBAL_SCOPE_INDEX {
|
||||
let scope = &mut self.scopes[scope_index];
|
||||
let usage = Some((scope.id, Range::from_located(stmt)));
|
||||
for name in names {
|
||||
for (name, range) in names.iter().zip(ranges.iter()) {
|
||||
// Add a binding to the current scope.
|
||||
let index = self.bindings.len();
|
||||
self.bindings.push(Binding {
|
||||
kind: BindingKind::Nonlocal,
|
||||
used: usage,
|
||||
range: Range::from_located(stmt),
|
||||
range: *range,
|
||||
source: Some(RefEquality(stmt)),
|
||||
});
|
||||
scope.values.insert(name, index);
|
||||
@@ -328,7 +313,7 @@ where
|
||||
|
||||
// Mark the binding in the defining scopes as used too. (Skip the global scope
|
||||
// and the current scope.)
|
||||
for name in names {
|
||||
for (name, range) in names.iter().zip(ranges.iter()) {
|
||||
let mut exists = false;
|
||||
for index in self.scope_stack.iter().skip(1).rev().skip(1) {
|
||||
if let Some(index) = self.scopes[*index].values.get(&name.as_str()) {
|
||||
@@ -342,7 +327,7 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::PLE0117) {
|
||||
self.add_check(Check::new(
|
||||
CheckKind::NonlocalWithoutBinding(name.to_string()),
|
||||
Range::from_located(stmt),
|
||||
*range,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -353,8 +338,9 @@ where
|
||||
self.add_checks(
|
||||
names
|
||||
.iter()
|
||||
.filter_map(|name| {
|
||||
pycodestyle::checks::ambiguous_variable_name(name, stmt)
|
||||
.zip(ranges.iter())
|
||||
.filter_map(|(name, range)| {
|
||||
pycodestyle::checks::ambiguous_variable_name(name, *range)
|
||||
})
|
||||
.into_iter(),
|
||||
);
|
||||
@@ -397,10 +383,9 @@ where
|
||||
..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::E743) {
|
||||
if let Some(check) = pycodestyle::checks::ambiguous_function_name(
|
||||
name,
|
||||
Range::from_located(stmt),
|
||||
) {
|
||||
if let Some(check) = pycodestyle::checks::ambiguous_function_name(name, || {
|
||||
helpers::identifier_range(stmt, self.locator)
|
||||
}) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
@@ -551,7 +536,7 @@ where
|
||||
Binding {
|
||||
kind: BindingKind::FunctionDefinition,
|
||||
used: None,
|
||||
range: Range::from_located(stmt),
|
||||
range: helpers::identifier_range(stmt, self.locator),
|
||||
source: Some(self.current_stmt().clone()),
|
||||
},
|
||||
);
|
||||
@@ -585,9 +570,9 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::E742) {
|
||||
if let Some(check) =
|
||||
pycodestyle::checks::ambiguous_class_name(name, Range::from_located(stmt))
|
||||
{
|
||||
if let Some(check) = pycodestyle::checks::ambiguous_class_name(name, || {
|
||||
helpers::identifier_range(stmt, self.locator)
|
||||
}) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
@@ -601,9 +586,12 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N818) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::error_suffix_on_exception_name(stmt, bases, name)
|
||||
{
|
||||
if let Some(check) = pep8_naming::checks::error_suffix_on_exception_name(
|
||||
stmt,
|
||||
bases,
|
||||
name,
|
||||
self.locator,
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
@@ -653,6 +641,9 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::UP023) {
|
||||
pyupgrade::plugins::replace_c_element_tree(self, stmt);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP026) {
|
||||
pyupgrade::plugins::rewrite_mock_import(self, stmt);
|
||||
}
|
||||
|
||||
for alias in names {
|
||||
if alias.node.name.contains('.') && alias.node.asname.is_none() {
|
||||
@@ -723,6 +714,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// flake8_tidy_imports
|
||||
if self.settings.enabled.contains(&CheckCode::TID251) {
|
||||
if let Some(check) = flake8_tidy_imports::checks::name_or_parent_is_banned(
|
||||
alias,
|
||||
&alias.node.name,
|
||||
&self.settings.flake8_tidy_imports.banned_api,
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
// pylint
|
||||
if self.settings.enabled.contains(&CheckCode::PLC0414) {
|
||||
pylint::plugins::useless_import_alias(self, alias);
|
||||
@@ -853,6 +855,30 @@ where
|
||||
pyupgrade::plugins::unnecessary_future_import(self, stmt, names);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP026) {
|
||||
pyupgrade::plugins::rewrite_mock_import(self, stmt);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::TID251) {
|
||||
if let Some(module) = module {
|
||||
for name in names {
|
||||
if let Some(check) = flake8_tidy_imports::checks::name_is_banned(
|
||||
module,
|
||||
name,
|
||||
&self.settings.flake8_tidy_imports.banned_api,
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
if let Some(check) = flake8_tidy_imports::checks::name_or_parent_is_banned(
|
||||
stmt,
|
||||
module,
|
||||
&self.settings.flake8_tidy_imports.banned_api,
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for alias in names {
|
||||
if let Some("__future__") = module.as_deref() {
|
||||
@@ -1085,6 +1111,11 @@ where
|
||||
flake8_errmsg::plugins::string_in_exception(self, exc);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP024) {
|
||||
if let Some(item) = exc {
|
||||
pyupgrade::plugins::os_error_alias(self, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::AugAssign { target, .. } => {
|
||||
self.handle_node_load(target);
|
||||
@@ -1155,7 +1186,9 @@ where
|
||||
}
|
||||
StmtKind::Try { handlers, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::F707) {
|
||||
if let Some(check) = pyflakes::checks::default_except_not_last(handlers) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::default_except_not_last(handlers, self.locator)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
@@ -1167,6 +1200,9 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::B013) {
|
||||
flake8_bugbear::plugins::redundant_tuple_in_exception_handler(self, handlers);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP024) {
|
||||
pyupgrade::plugins::os_error_alias(self, handlers);
|
||||
}
|
||||
}
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E731) {
|
||||
@@ -1399,7 +1435,7 @@ where
|
||||
Binding {
|
||||
kind: BindingKind::ClassDefinition,
|
||||
used: None,
|
||||
range: Range::from_located(stmt),
|
||||
range: helpers::identifier_range(stmt, self.locator),
|
||||
source: Some(self.current_stmt().clone()),
|
||||
},
|
||||
);
|
||||
@@ -1448,7 +1484,6 @@ where
|
||||
|
||||
self.push_expr(expr);
|
||||
|
||||
let prev_in_f_string = self.in_f_string;
|
||||
let prev_in_literal = self.in_literal;
|
||||
let prev_in_type_definition = self.in_type_definition;
|
||||
|
||||
@@ -1523,9 +1558,10 @@ where
|
||||
}
|
||||
ExprContext::Store => {
|
||||
if self.settings.enabled.contains(&CheckCode::E741) {
|
||||
if let Some(check) =
|
||||
pycodestyle::checks::ambiguous_variable_name(id, expr)
|
||||
{
|
||||
if let Some(check) = pycodestyle::checks::ambiguous_variable_name(
|
||||
id,
|
||||
Range::from_located(expr),
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
@@ -1570,6 +1606,10 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::UP019) {
|
||||
pyupgrade::plugins::typing_text_str_alias(self, expr);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP026) {
|
||||
pyupgrade::plugins::rewrite_mock_attribute(self, expr);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::YTT202) {
|
||||
flake8_2020::plugins::name_or_attribute(self, expr);
|
||||
}
|
||||
@@ -1586,6 +1626,15 @@ where
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::TID251) {
|
||||
flake8_tidy_imports::checks::banned_attribute_access(
|
||||
self,
|
||||
&dealias_call_path(collect_call_paths(expr), &self.import_aliases),
|
||||
expr,
|
||||
&self.settings.flake8_tidy_imports.banned_api,
|
||||
);
|
||||
}
|
||||
}
|
||||
ExprKind::Call {
|
||||
func,
|
||||
@@ -1682,12 +1731,15 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::UP022) {
|
||||
pyupgrade::plugins::replace_stdout_stderr(self, expr, keywords);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP024) {
|
||||
pyupgrade::plugins::os_error_alias(self, expr);
|
||||
}
|
||||
|
||||
// flake8-print
|
||||
if self.settings.enabled.contains(&CheckCode::T201)
|
||||
|| self.settings.enabled.contains(&CheckCode::T203)
|
||||
{
|
||||
flake8_print::plugins::print_call(self, expr, func, keywords);
|
||||
flake8_print::plugins::print_call(self, func, keywords);
|
||||
}
|
||||
|
||||
// flake8-bugbear
|
||||
@@ -2126,10 +2178,9 @@ where
|
||||
}
|
||||
ExprKind::JoinedStr { values } => {
|
||||
if self.settings.enabled.contains(&CheckCode::F541) {
|
||||
if self.in_f_string.is_none()
|
||||
&& !values
|
||||
.iter()
|
||||
.any(|value| matches!(value.node, ExprKind::FormattedValue { .. }))
|
||||
if !values
|
||||
.iter()
|
||||
.any(|value| matches!(value.node, ExprKind::FormattedValue { .. }))
|
||||
{
|
||||
self.add_check(Check::new(
|
||||
CheckKind::FStringMissingPlaceholders,
|
||||
@@ -2137,7 +2188,6 @@ where
|
||||
));
|
||||
}
|
||||
}
|
||||
self.in_f_string = Some(Range::from_located(expr));
|
||||
}
|
||||
ExprKind::BinOp {
|
||||
left,
|
||||
@@ -2230,6 +2280,15 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::BinOp {
|
||||
op: Operator::Add, ..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::ISC003) {
|
||||
if let Some(check) = flake8_implicit_str_concat::checks::explicit(expr) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::UnaryOp { op, operand } => {
|
||||
let check_not_in = self.settings.enabled.contains(&CheckCode::E713);
|
||||
let check_not_is = self.settings.enabled.contains(&CheckCode::E714);
|
||||
@@ -2329,7 +2388,7 @@ where
|
||||
}
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
kind,
|
||||
} => {
|
||||
if self.in_type_definition && !self.in_literal {
|
||||
self.deferred_string_type_definitions.push((
|
||||
@@ -2347,6 +2406,9 @@ where
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP025) {
|
||||
pyupgrade::plugins::rewrite_unicode_literal(self, expr, kind);
|
||||
}
|
||||
}
|
||||
ExprKind::Lambda { args, body, .. } => {
|
||||
// Visit the arguments, but avoid the body, which will be deferred.
|
||||
@@ -2618,6 +2680,9 @@ where
|
||||
}
|
||||
self.in_subscript = prev_in_subscript;
|
||||
}
|
||||
ExprKind::JoinedStr { .. } => {
|
||||
visitor::walk_expr(self, expr);
|
||||
}
|
||||
_ => visitor::walk_expr(self, expr),
|
||||
}
|
||||
|
||||
@@ -2635,7 +2700,6 @@ where
|
||||
|
||||
self.in_type_definition = prev_in_type_definition;
|
||||
self.in_literal = prev_in_literal;
|
||||
self.in_f_string = prev_in_f_string;
|
||||
|
||||
self.pop_expr();
|
||||
}
|
||||
@@ -2649,7 +2713,8 @@ where
|
||||
if let Some(check) = pycodestyle::checks::do_not_use_bare_except(
|
||||
type_.as_deref(),
|
||||
body,
|
||||
Range::from_located(excepthandler),
|
||||
excepthandler,
|
||||
self.locator,
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
@@ -2668,9 +2733,11 @@ where
|
||||
match name {
|
||||
Some(name) => {
|
||||
if self.settings.enabled.contains(&CheckCode::E741) {
|
||||
if let Some(check) =
|
||||
pycodestyle::checks::ambiguous_variable_name(name, excepthandler)
|
||||
{
|
||||
if let Some(check) = pycodestyle::checks::ambiguous_variable_name(
|
||||
name,
|
||||
helpers::excepthandler_name_range(excepthandler, self.locator)
|
||||
.expect("Failed to find `name` range"),
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
@@ -2736,11 +2803,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_arguments(&mut self, arguments: &'b Arguments) {
|
||||
if self.settings.enabled.contains(&CheckCode::F831) {
|
||||
self.checks
|
||||
.extend(pyflakes::checks::duplicate_arguments(arguments));
|
||||
fn visit_format_spec(&mut self, format_spec: &'b Expr) {
|
||||
match &format_spec.node {
|
||||
ExprKind::JoinedStr { values } => {
|
||||
for value in values {
|
||||
self.visit_expr(value);
|
||||
}
|
||||
}
|
||||
_ => unreachable!("Unexpected expression for format_spec"),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_arguments(&mut self, arguments: &'b Arguments) {
|
||||
if self.settings.enabled.contains(&CheckCode::B006) {
|
||||
flake8_bugbear::plugins::mutable_argument_default(self, arguments);
|
||||
}
|
||||
@@ -2791,7 +2865,10 @@ where
|
||||
);
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::E741) {
|
||||
if let Some(check) = pycodestyle::checks::ambiguous_variable_name(&arg.node.arg, arg) {
|
||||
if let Some(check) = pycodestyle::checks::ambiguous_variable_name(
|
||||
&arg.node.arg,
|
||||
Range::from_located(arg),
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
@@ -3621,7 +3698,22 @@ impl<'a> Checker<'a> {
|
||||
|
||||
let defined_by = binding.source.as_ref().unwrap();
|
||||
let defined_in = self.child_to_parent.get(defined_by);
|
||||
if self.is_ignored(&CheckCode::F401, binding.range.location.row()) {
|
||||
let child = defined_by.0;
|
||||
|
||||
let check_lineno = binding.range.location.row();
|
||||
let parent_lineno = if matches!(child.node, StmtKind::ImportFrom { .. })
|
||||
&& child.location.row() != check_lineno
|
||||
{
|
||||
Some(child.location.row())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if self.is_ignored(&CheckCode::F401, check_lineno)
|
||||
|| parent_lineno.map_or(false, |parent_lineno| {
|
||||
self.is_ignored(&CheckCode::F401, parent_lineno)
|
||||
})
|
||||
{
|
||||
ignored
|
||||
.entry((defined_by, defined_in))
|
||||
.or_default()
|
||||
@@ -3668,26 +3760,40 @@ impl<'a> Checker<'a> {
|
||||
None
|
||||
};
|
||||
|
||||
let multiple = unused_imports.len() > 1;
|
||||
for (full_name, range) in unused_imports {
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedImport(full_name.clone(), ignore_init),
|
||||
CheckKind::UnusedImport(full_name.clone(), ignore_init, multiple),
|
||||
*range,
|
||||
);
|
||||
if matches!(child.node, StmtKind::ImportFrom { .. })
|
||||
&& child.location.row() != range.location.row()
|
||||
{
|
||||
check.parent(child.location);
|
||||
}
|
||||
if let Some(fix) = fix.as_ref() {
|
||||
check.amend(fix.clone());
|
||||
}
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
for (_, unused_imports) in ignored
|
||||
for ((defined_by, ..), unused_imports) in ignored
|
||||
.into_iter()
|
||||
.sorted_by_key(|((defined_by, _), _)| defined_by.0.location)
|
||||
{
|
||||
let child = defined_by.0;
|
||||
let multiple = unused_imports.len() > 1;
|
||||
for (full_name, range) in unused_imports {
|
||||
checks.push(Check::new(
|
||||
CheckKind::UnusedImport(full_name.clone(), ignore_init),
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedImport(full_name.clone(), ignore_init, multiple),
|
||||
*range,
|
||||
));
|
||||
);
|
||||
if matches!(child.node, StmtKind::ImportFrom { .. })
|
||||
&& child.location.row() != range.location.row()
|
||||
{
|
||||
check.parent(child.location);
|
||||
}
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3787,10 +3893,10 @@ impl<'a> Checker<'a> {
|
||||
let content = self
|
||||
.locator
|
||||
.slice_source_code_range(&Range::from_located(expr));
|
||||
let indentation = self.locator.slice_source_code_range(&Range {
|
||||
location: Location::new(expr.location.row(), 0),
|
||||
end_location: Location::new(expr.location.row(), expr.location.column()),
|
||||
});
|
||||
let indentation = self.locator.slice_source_code_range(&Range::new(
|
||||
Location::new(expr.location.row(), 0),
|
||||
Location::new(expr.location.row(), expr.location.column()),
|
||||
));
|
||||
let body = pydocstyle::helpers::raw_contents(&content);
|
||||
let docstring = Docstring {
|
||||
kind: definition.kind,
|
||||
|
||||
@@ -11,11 +11,13 @@ use crate::isort;
|
||||
use crate::isort::track::ImportTracker;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
|
||||
fn check_import_blocks(
|
||||
tracker: ImportTracker,
|
||||
locator: &SourceCodeLocator,
|
||||
settings: &Settings,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
autofix: flags::Autofix,
|
||||
package: Option<&Path>,
|
||||
) -> Vec<Check> {
|
||||
@@ -23,7 +25,7 @@ fn check_import_blocks(
|
||||
for block in tracker.into_iter() {
|
||||
if !block.imports.is_empty() {
|
||||
if let Some(check) =
|
||||
isort::plugins::check_imports(&block, locator, settings, autofix, package)
|
||||
isort::plugins::check_imports(&block, locator, settings, stylist, autofix, package)
|
||||
{
|
||||
checks.push(check);
|
||||
}
|
||||
@@ -32,11 +34,13 @@ fn check_import_blocks(
|
||||
checks
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn check_imports(
|
||||
python_ast: &Suite,
|
||||
locator: &SourceCodeLocator,
|
||||
directives: &IsortDirectives,
|
||||
settings: &Settings,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
autofix: flags::Autofix,
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
@@ -45,5 +49,5 @@ pub fn check_imports(
|
||||
for stmt in python_ast {
|
||||
tracker.visit_stmt(stmt);
|
||||
}
|
||||
check_import_blocks(tracker, locator, settings, autofix, package)
|
||||
check_import_blocks(tracker, locator, settings, stylist, autofix, package)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::pycodestyle::checks::{line_too_long, no_newline_at_end_of_file};
|
||||
use crate::pygrep_hooks::plugins::blanket_type_ignore;
|
||||
use crate::pygrep_hooks::plugins::{blanket_noqa, blanket_type_ignore};
|
||||
use crate::pyupgrade::checks::unnecessary_coding_comment;
|
||||
use crate::settings::{flags, Settings};
|
||||
|
||||
@@ -18,6 +18,7 @@ pub fn check_lines(
|
||||
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
|
||||
let enforce_no_newline_at_end_of_file = settings.enabled.contains(&CheckCode::W292);
|
||||
let enforce_blanket_type_ignore = settings.enabled.contains(&CheckCode::PGH003);
|
||||
let enforce_blanket_noqa = settings.enabled.contains(&CheckCode::PGH004);
|
||||
|
||||
let mut commented_lines_iter = commented_lines.iter().peekable();
|
||||
for (index, line) in contents.lines().enumerate() {
|
||||
@@ -45,6 +46,14 @@ pub fn check_lines(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if enforce_blanket_noqa {
|
||||
if commented_lines.contains(&(index + 1)) {
|
||||
if let Some(check) = blanket_noqa(index, line) {
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if enforce_line_too_long {
|
||||
|
||||
@@ -25,12 +25,6 @@ pub fn check_noqa(
|
||||
|
||||
let enforce_noqa = settings.enabled.contains(&CheckCode::RUF100);
|
||||
|
||||
checks.sort_by_key(|check| check.location);
|
||||
let mut checks_iter = checks.iter().enumerate().peekable();
|
||||
if let Some((_index, check)) = checks_iter.peek() {
|
||||
assert!(check.location.row() >= 1);
|
||||
}
|
||||
|
||||
let lines: Vec<&str> = contents.lines().collect();
|
||||
for lineno in commented_lines {
|
||||
// If we hit an exemption for the entire file, bail.
|
||||
@@ -44,17 +38,18 @@ pub fn check_noqa(
|
||||
.entry(lineno - 1)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[lineno - 1]), vec![]));
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any ignored checks.
|
||||
while let Some((index, check)) =
|
||||
checks_iter.next_if(|(_index, check)| check.location.row() <= *lineno)
|
||||
{
|
||||
// Grab the noqa (logical) line number for the current (physical) line.
|
||||
// If there are newlines at the end of the file, they won't be represented in
|
||||
// `noqa_line_for`, so fallback to the current line.
|
||||
let check_lineno = check.location.row();
|
||||
let noqa_lineno = noqa_line_for.get(&check_lineno).unwrap_or(&check_lineno);
|
||||
if noqa_lineno == lineno {
|
||||
// Remove any ignored checks.
|
||||
for (index, check) in checks.iter().enumerate() {
|
||||
if check.kind == CheckKind::BlanketNOQA {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is the check ignored by a `noqa` directive on the parent line?
|
||||
if let Some(parent_lineno) = check.parent.map(|location| location.row()) {
|
||||
let noqa_lineno = noqa_line_for.get(&parent_lineno).unwrap_or(&parent_lineno);
|
||||
if commented_lines.contains(noqa_lineno) {
|
||||
let noqa = noqa_directives.entry(noqa_lineno - 1).or_insert_with(|| {
|
||||
(noqa::extract_noqa_directive(lines[noqa_lineno - 1]), vec![])
|
||||
});
|
||||
@@ -62,17 +57,41 @@ pub fn check_noqa(
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index);
|
||||
continue;
|
||||
}
|
||||
(Directive::Codes(.., codes), matches) => {
|
||||
if noqa::includes(check.kind.code(), codes) {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
(Directive::None, ..) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Is the check ignored by a `noqa` directive on the same line?
|
||||
let check_lineno = check.location.row();
|
||||
let noqa_lineno = noqa_line_for.get(&check_lineno).unwrap_or(&check_lineno);
|
||||
if commented_lines.contains(noqa_lineno) {
|
||||
let noqa = noqa_directives
|
||||
.entry(noqa_lineno - 1)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno - 1]), vec![]));
|
||||
match noqa {
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index);
|
||||
}
|
||||
(Directive::Codes(.., codes), matches) => {
|
||||
if noqa::includes(check.kind.code(), codes) {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index);
|
||||
}
|
||||
}
|
||||
(Directive::None, ..) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce that the noqa directive was actually used (RUF100).
|
||||
@@ -83,10 +102,7 @@ pub fn check_noqa(
|
||||
if matches.is_empty() {
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedNOQA(None),
|
||||
Range {
|
||||
location: Location::new(row + 1, start),
|
||||
end_location: Location::new(row + 1, end),
|
||||
},
|
||||
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
|
||||
);
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.fixable.contains(check.kind.code())
|
||||
@@ -150,10 +166,7 @@ pub fn check_noqa(
|
||||
.map(|code| (*code).to_string())
|
||||
.collect(),
|
||||
})),
|
||||
Range {
|
||||
location: Location::new(row + 1, start),
|
||||
end_location: Location::new(row + 1, end),
|
||||
},
|
||||
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
|
||||
);
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.fixable.contains(check.kind.code())
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::lex::docstring_detection::StateMachine;
|
||||
use crate::ruff::checks::Context;
|
||||
use crate::settings::flags;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::{eradicate, flake8_quotes, pycodestyle, ruff, Settings};
|
||||
use crate::{eradicate, flake8_implicit_str_concat, flake8_quotes, pycodestyle, ruff, Settings};
|
||||
|
||||
pub fn check_tokens(
|
||||
locator: &SourceCodeLocator,
|
||||
@@ -26,6 +26,8 @@ pub fn check_tokens(
|
||||
|| settings.enabled.contains(&CheckCode::Q003);
|
||||
let enforce_commented_out_code = settings.enabled.contains(&CheckCode::ERA001);
|
||||
let enforce_invalid_escape_sequence = settings.enabled.contains(&CheckCode::W605);
|
||||
let enforce_implicit_string_concatenation = settings.enabled.contains(&CheckCode::ISC001)
|
||||
|| settings.enabled.contains(&CheckCode::ISC002);
|
||||
|
||||
let mut state_machine = StateMachine::default();
|
||||
for &(start, ref tok, end) in tokens.iter().flatten() {
|
||||
@@ -99,5 +101,14 @@ pub fn check_tokens(
|
||||
}
|
||||
}
|
||||
|
||||
// ISC001, ISC002
|
||||
if enforce_implicit_string_concatenation {
|
||||
checks.extend(
|
||||
flake8_implicit_str_concat::checks::implicit(tokens, locator)
|
||||
.into_iter()
|
||||
.filter(|check| settings.enabled.contains(check.kind.code())),
|
||||
);
|
||||
}
|
||||
|
||||
checks
|
||||
}
|
||||
|
||||
530
src/checks.rs
530
src/checks.rs
@@ -3,6 +3,7 @@ use std::fmt;
|
||||
use itertools::Itertools;
|
||||
use once_cell::sync::Lazy;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_ast::Cmpop;
|
||||
use rustpython_parser::ast::Location;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
|
||||
@@ -91,7 +92,6 @@ pub enum CheckCode {
|
||||
F821,
|
||||
F822,
|
||||
F823,
|
||||
F831,
|
||||
F841,
|
||||
F842,
|
||||
F901,
|
||||
@@ -165,6 +165,7 @@ pub enum CheckCode {
|
||||
// mccabe
|
||||
C901,
|
||||
// flake8-tidy-imports
|
||||
TID251,
|
||||
TID252,
|
||||
// flake8-return
|
||||
RET501,
|
||||
@@ -175,6 +176,10 @@ pub enum CheckCode {
|
||||
RET506,
|
||||
RET507,
|
||||
RET508,
|
||||
// flake8-implicit-str-concat
|
||||
ISC001,
|
||||
ISC002,
|
||||
ISC003,
|
||||
// flake8-print
|
||||
T201,
|
||||
T203,
|
||||
@@ -231,6 +236,9 @@ pub enum CheckCode {
|
||||
UP021,
|
||||
UP022,
|
||||
UP023,
|
||||
UP024,
|
||||
UP025,
|
||||
UP026,
|
||||
// pydocstyle
|
||||
D100,
|
||||
D101,
|
||||
@@ -336,6 +344,7 @@ pub enum CheckCode {
|
||||
PGH001,
|
||||
PGH002,
|
||||
PGH003,
|
||||
PGH004,
|
||||
// pandas-vet
|
||||
PD002,
|
||||
PD003,
|
||||
@@ -374,6 +383,7 @@ pub enum CheckCategory {
|
||||
Flake8Comprehensions,
|
||||
Flake8Debugger,
|
||||
Flake8ErrMsg,
|
||||
Flake8ImplicitStrConcat,
|
||||
Flake8ImportConventions,
|
||||
Flake8Print,
|
||||
Flake8Quotes,
|
||||
@@ -417,6 +427,7 @@ impl CheckCategory {
|
||||
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
|
||||
CheckCategory::Flake8Debugger => "flake8-debugger",
|
||||
CheckCategory::Flake8ErrMsg => "flake8-errmsg",
|
||||
CheckCategory::Flake8ImplicitStrConcat => "flake8-implicit-str-concat",
|
||||
CheckCategory::Flake8ImportConventions => "flake8-import-conventions",
|
||||
CheckCategory::Flake8Print => "flake8-print",
|
||||
CheckCategory::Flake8Quotes => "flake8-quotes",
|
||||
@@ -450,19 +461,21 @@ impl CheckCategory {
|
||||
CheckCategory::Flake8Bugbear => vec![CheckCodePrefix::B],
|
||||
CheckCategory::Flake8Builtins => vec![CheckCodePrefix::A],
|
||||
CheckCategory::Flake8Comprehensions => vec![CheckCodePrefix::C4],
|
||||
CheckCategory::Flake8Datetimez => vec![CheckCodePrefix::DTZ],
|
||||
CheckCategory::Flake8Debugger => vec![CheckCodePrefix::T10],
|
||||
CheckCategory::Flake8ErrMsg => vec![CheckCodePrefix::EM],
|
||||
CheckCategory::Flake8ImplicitStrConcat => vec![CheckCodePrefix::ISC],
|
||||
CheckCategory::Flake8ImportConventions => vec![CheckCodePrefix::ICN],
|
||||
CheckCategory::Flake8Print => vec![CheckCodePrefix::T20],
|
||||
CheckCategory::Flake8Quotes => vec![CheckCodePrefix::Q],
|
||||
CheckCategory::Flake8Return => vec![CheckCodePrefix::RET],
|
||||
CheckCategory::Flake8Simplify => vec![CheckCodePrefix::SIM],
|
||||
CheckCategory::Flake8TidyImports => vec![CheckCodePrefix::TID],
|
||||
CheckCategory::Flake8UnusedArguments => vec![CheckCodePrefix::ARG],
|
||||
CheckCategory::Flake8Datetimez => vec![CheckCodePrefix::DTZ],
|
||||
CheckCategory::Isort => vec![CheckCodePrefix::I],
|
||||
CheckCategory::McCabe => vec![CheckCodePrefix::C90],
|
||||
CheckCategory::PandasVet => vec![CheckCodePrefix::PD],
|
||||
CheckCategory::PEP8Naming => vec![CheckCodePrefix::N],
|
||||
CheckCategory::PandasVet => vec![CheckCodePrefix::PD],
|
||||
CheckCategory::Pycodestyle => vec![CheckCodePrefix::E, CheckCodePrefix::W],
|
||||
CheckCategory::Pydocstyle => vec![CheckCodePrefix::D],
|
||||
CheckCategory::Pyflakes => vec![CheckCodePrefix::F],
|
||||
@@ -474,7 +487,6 @@ impl CheckCategory {
|
||||
CheckCodePrefix::PLW,
|
||||
],
|
||||
CheckCategory::Pyupgrade => vec![CheckCodePrefix::UP],
|
||||
CheckCategory::Flake8ImportConventions => vec![CheckCodePrefix::ICN],
|
||||
CheckCategory::Ruff => vec![CheckCodePrefix::RUF],
|
||||
}
|
||||
}
|
||||
@@ -524,6 +536,10 @@ impl CheckCategory {
|
||||
"https://pypi.org/project/flake8-errmsg/0.4.0/",
|
||||
&Platform::PyPI,
|
||||
)),
|
||||
CheckCategory::Flake8ImplicitStrConcat => Some((
|
||||
"https://pypi.org/project/flake8-implicit-str-concat/0.3.0/",
|
||||
&Platform::PyPI,
|
||||
)),
|
||||
CheckCategory::Flake8ImportConventions => None,
|
||||
CheckCategory::Flake8Print => Some((
|
||||
"https://pypi.org/project/flake8-print/5.0.0/",
|
||||
@@ -604,11 +620,37 @@ pub enum LintSource {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum RejectedCmpop {
|
||||
pub enum EqCmpop {
|
||||
Eq,
|
||||
NotEq,
|
||||
}
|
||||
|
||||
impl From<&Cmpop> for EqCmpop {
|
||||
fn from(cmpop: &Cmpop) -> Self {
|
||||
match cmpop {
|
||||
Cmpop::Eq => EqCmpop::Eq,
|
||||
Cmpop::NotEq => EqCmpop::NotEq,
|
||||
_ => unreachable!("Expected Cmpop::Eq | Cmpop::NotEq"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum IsCmpop {
|
||||
Is,
|
||||
IsNot,
|
||||
}
|
||||
|
||||
impl From<&Cmpop> for IsCmpop {
|
||||
fn from(cmpop: &Cmpop) -> Self {
|
||||
match cmpop {
|
||||
Cmpop::Is => IsCmpop::Is,
|
||||
Cmpop::IsNot => IsCmpop::IsNot,
|
||||
_ => unreachable!("Expected Cmpop::Is | Cmpop::IsNot"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum DeferralKeyword {
|
||||
Yield,
|
||||
@@ -641,6 +683,21 @@ impl fmt::Display for Branch {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum LiteralType {
|
||||
Str,
|
||||
Bytes,
|
||||
}
|
||||
|
||||
impl fmt::Display for LiteralType {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
LiteralType::Str => fmt.write_str("str"),
|
||||
LiteralType::Bytes => fmt.write_str("bytes"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct UnusedCodes {
|
||||
pub unknown: Vec<String>,
|
||||
@@ -648,23 +705,29 @@ pub struct UnusedCodes {
|
||||
pub unmatched: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum MockReference {
|
||||
Import,
|
||||
Attribute,
|
||||
}
|
||||
|
||||
#[derive(AsRefStr, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CheckKind {
|
||||
// pycodestyle errors
|
||||
AmbiguousClassName(String),
|
||||
AmbiguousFunctionName(String),
|
||||
AmbiguousVariableName(String),
|
||||
DoNotAssignLambda,
|
||||
DoNotAssignLambda(String),
|
||||
DoNotUseBareExcept,
|
||||
IOError(String),
|
||||
LineTooLong(usize, usize),
|
||||
ModuleImportNotAtTopOfFile,
|
||||
MultipleImportsOnOneLine,
|
||||
NoneComparison(RejectedCmpop),
|
||||
NoneComparison(EqCmpop),
|
||||
NotInTest,
|
||||
NotIsTest,
|
||||
SyntaxError(String),
|
||||
TrueFalseComparison(bool, RejectedCmpop),
|
||||
TrueFalseComparison(bool, EqCmpop),
|
||||
TypeComparison,
|
||||
// pycodestyle warnings
|
||||
NoNewLineAtEndOfFile,
|
||||
@@ -674,7 +737,6 @@ pub enum CheckKind {
|
||||
BreakOutsideLoop,
|
||||
ContinueOutsideLoop,
|
||||
DefaultExceptNotLast,
|
||||
DuplicateArgumentName,
|
||||
ExpressionsInStarAssignment,
|
||||
FStringMissingPlaceholders,
|
||||
ForwardAnnotationSyntaxError(String),
|
||||
@@ -685,7 +747,7 @@ pub enum CheckKind {
|
||||
ImportStarUsage(String, Vec<String>),
|
||||
ImportStarUsed(String),
|
||||
InvalidPrintSyntax,
|
||||
IsLiteral,
|
||||
IsLiteral(IsCmpop),
|
||||
LateFutureImport,
|
||||
MultiValueRepeatedKeyLiteral,
|
||||
MultiValueRepeatedKeyVariable(String),
|
||||
@@ -711,7 +773,7 @@ pub enum CheckKind {
|
||||
UndefinedLocal(String),
|
||||
UnusedAnnotation(String),
|
||||
UndefinedName(String),
|
||||
UnusedImport(String, bool),
|
||||
UnusedImport(String, bool, bool),
|
||||
UnusedVariable(String),
|
||||
YieldOutsideFunction(DeferralKeyword),
|
||||
// pylint
|
||||
@@ -782,6 +844,7 @@ pub enum CheckKind {
|
||||
// flake8-debugger
|
||||
Debugger(DebuggerUsingType),
|
||||
// flake8-tidy-imports
|
||||
BannedApi { name: String, message: String },
|
||||
BannedRelativeImport(Strictness),
|
||||
// flake8-return
|
||||
UnnecessaryReturnNone,
|
||||
@@ -792,6 +855,10 @@ pub enum CheckKind {
|
||||
SuperfluousElseRaise(Branch),
|
||||
SuperfluousElseContinue(Branch),
|
||||
SuperfluousElseBreak(Branch),
|
||||
// flake8-implicit-str-concat
|
||||
SingleLineImplicitStringConcatenation,
|
||||
MultiLineImplicitStringConcatenation,
|
||||
ExplicitStringConcatenation,
|
||||
// flake8-print
|
||||
PrintFound,
|
||||
PPrintFound,
|
||||
@@ -840,14 +907,17 @@ pub enum CheckKind {
|
||||
UnnecessaryEncodeUTF8,
|
||||
ConvertTypedDictFunctionalToClass(String),
|
||||
ConvertNamedTupleFunctionalToClass(String),
|
||||
RedundantOpenModes,
|
||||
RedundantOpenModes(Option<String>),
|
||||
RemoveSixCompat,
|
||||
DatetimeTimezoneUTC,
|
||||
NativeLiterals,
|
||||
NativeLiterals(LiteralType),
|
||||
OpenAlias,
|
||||
ReplaceUniversalNewlines,
|
||||
ReplaceStdoutStderr,
|
||||
RewriteCElementTree,
|
||||
OSErrorAlias(Option<String>),
|
||||
RewriteUnicodeLiteral,
|
||||
RewriteMockImport(MockReference),
|
||||
// pydocstyle
|
||||
BlankLineAfterLastSection(String),
|
||||
BlankLineAfterSection(String),
|
||||
@@ -931,6 +1001,7 @@ pub enum CheckKind {
|
||||
NoEval,
|
||||
DeprecatedLogWarn,
|
||||
BlanketTypeIgnore,
|
||||
BlanketNOQA,
|
||||
// flake8-unused-arguments
|
||||
UnusedFunctionArgument(String),
|
||||
UnusedMethodArgument(String),
|
||||
@@ -980,10 +1051,14 @@ impl CheckCode {
|
||||
pub fn lint_source(&self) -> &'static LintSource {
|
||||
match self {
|
||||
CheckCode::RUF100 => &LintSource::NoQA,
|
||||
CheckCode::E501 | CheckCode::W292 | CheckCode::UP009 | CheckCode::PGH003 => {
|
||||
&LintSource::Lines
|
||||
}
|
||||
CheckCode::E501
|
||||
| CheckCode::W292
|
||||
| CheckCode::UP009
|
||||
| CheckCode::PGH003
|
||||
| CheckCode::PGH004 => &LintSource::Lines,
|
||||
CheckCode::ERA001
|
||||
| CheckCode::ISC001
|
||||
| CheckCode::ISC002
|
||||
| CheckCode::Q000
|
||||
| CheckCode::Q001
|
||||
| CheckCode::Q002
|
||||
@@ -1005,13 +1080,13 @@ impl CheckCode {
|
||||
CheckCode::E401 => CheckKind::MultipleImportsOnOneLine,
|
||||
CheckCode::E402 => CheckKind::ModuleImportNotAtTopOfFile,
|
||||
CheckCode::E501 => CheckKind::LineTooLong(89, 88),
|
||||
CheckCode::E711 => CheckKind::NoneComparison(RejectedCmpop::Eq),
|
||||
CheckCode::E712 => CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq),
|
||||
CheckCode::E711 => CheckKind::NoneComparison(EqCmpop::Eq),
|
||||
CheckCode::E712 => CheckKind::TrueFalseComparison(true, EqCmpop::Eq),
|
||||
CheckCode::E713 => CheckKind::NotInTest,
|
||||
CheckCode::E714 => CheckKind::NotIsTest,
|
||||
CheckCode::E721 => CheckKind::TypeComparison,
|
||||
CheckCode::E722 => CheckKind::DoNotUseBareExcept,
|
||||
CheckCode::E731 => CheckKind::DoNotAssignLambda,
|
||||
CheckCode::E731 => CheckKind::DoNotAssignLambda("...".to_string()),
|
||||
CheckCode::E741 => CheckKind::AmbiguousVariableName("...".to_string()),
|
||||
CheckCode::E742 => CheckKind::AmbiguousClassName("...".to_string()),
|
||||
CheckCode::E743 => CheckKind::AmbiguousFunctionName("...".to_string()),
|
||||
@@ -1021,7 +1096,7 @@ impl CheckCode {
|
||||
CheckCode::W292 => CheckKind::NoNewLineAtEndOfFile,
|
||||
CheckCode::W605 => CheckKind::InvalidEscapeSequence('c'),
|
||||
// pyflakes
|
||||
CheckCode::F401 => CheckKind::UnusedImport("...".to_string(), false),
|
||||
CheckCode::F401 => CheckKind::UnusedImport("...".to_string(), false, false),
|
||||
CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
|
||||
CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()),
|
||||
CheckCode::F404 => CheckKind::LateFutureImport,
|
||||
@@ -1054,7 +1129,7 @@ impl CheckCode {
|
||||
CheckCode::F621 => CheckKind::ExpressionsInStarAssignment,
|
||||
CheckCode::F622 => CheckKind::TwoStarredExpressions,
|
||||
CheckCode::F631 => CheckKind::AssertTuple,
|
||||
CheckCode::F632 => CheckKind::IsLiteral,
|
||||
CheckCode::F632 => CheckKind::IsLiteral(IsCmpop::Is),
|
||||
CheckCode::F633 => CheckKind::InvalidPrintSyntax,
|
||||
CheckCode::F634 => CheckKind::IfTuple,
|
||||
CheckCode::F701 => CheckKind::BreakOutsideLoop,
|
||||
@@ -1067,7 +1142,6 @@ impl CheckCode {
|
||||
CheckCode::F821 => CheckKind::UndefinedName("...".to_string()),
|
||||
CheckCode::F822 => CheckKind::UndefinedExport("...".to_string()),
|
||||
CheckCode::F823 => CheckKind::UndefinedLocal("...".to_string()),
|
||||
CheckCode::F831 => CheckKind::DuplicateArgumentName,
|
||||
CheckCode::F841 => CheckKind::UnusedVariable("...".to_string()),
|
||||
CheckCode::F842 => CheckKind::UnusedAnnotation("...".to_string()),
|
||||
CheckCode::F901 => CheckKind::RaiseNotImplemented,
|
||||
@@ -1158,6 +1232,10 @@ impl CheckCode {
|
||||
// flake8-debugger
|
||||
CheckCode::T100 => CheckKind::Debugger(DebuggerUsingType::Import("...".to_string())),
|
||||
// flake8-tidy-imports
|
||||
CheckCode::TID251 => CheckKind::BannedApi {
|
||||
name: "...".to_string(),
|
||||
message: "...".to_string(),
|
||||
},
|
||||
CheckCode::TID252 => CheckKind::BannedRelativeImport(Strictness::All),
|
||||
// flake8-return
|
||||
CheckCode::RET501 => CheckKind::UnnecessaryReturnNone,
|
||||
@@ -1168,6 +1246,10 @@ impl CheckCode {
|
||||
CheckCode::RET506 => CheckKind::SuperfluousElseRaise(Branch::Else),
|
||||
CheckCode::RET507 => CheckKind::SuperfluousElseContinue(Branch::Else),
|
||||
CheckCode::RET508 => CheckKind::SuperfluousElseBreak(Branch::Else),
|
||||
// flake8-implicit-str-concat
|
||||
CheckCode::ISC001 => CheckKind::SingleLineImplicitStringConcatenation,
|
||||
CheckCode::ISC002 => CheckKind::MultiLineImplicitStringConcatenation,
|
||||
CheckCode::ISC003 => CheckKind::ExplicitStringConcatenation,
|
||||
// flake8-print
|
||||
CheckCode::T201 => CheckKind::PrintFound,
|
||||
CheckCode::T203 => CheckKind::PPrintFound,
|
||||
@@ -1220,15 +1302,18 @@ impl CheckCode {
|
||||
CheckCode::UP012 => CheckKind::UnnecessaryEncodeUTF8,
|
||||
CheckCode::UP013 => CheckKind::ConvertTypedDictFunctionalToClass("...".to_string()),
|
||||
CheckCode::UP014 => CheckKind::ConvertNamedTupleFunctionalToClass("...".to_string()),
|
||||
CheckCode::UP015 => CheckKind::RedundantOpenModes,
|
||||
CheckCode::UP015 => CheckKind::RedundantOpenModes(None),
|
||||
CheckCode::UP016 => CheckKind::RemoveSixCompat,
|
||||
CheckCode::UP017 => CheckKind::DatetimeTimezoneUTC,
|
||||
CheckCode::UP018 => CheckKind::NativeLiterals,
|
||||
CheckCode::UP018 => CheckKind::NativeLiterals(LiteralType::Str),
|
||||
CheckCode::UP019 => CheckKind::TypingTextStrAlias,
|
||||
CheckCode::UP020 => CheckKind::OpenAlias,
|
||||
CheckCode::UP021 => CheckKind::ReplaceUniversalNewlines,
|
||||
CheckCode::UP022 => CheckKind::ReplaceStdoutStderr,
|
||||
CheckCode::UP023 => CheckKind::RewriteCElementTree,
|
||||
CheckCode::UP024 => CheckKind::OSErrorAlias(None),
|
||||
CheckCode::UP025 => CheckKind::RewriteUnicodeLiteral,
|
||||
CheckCode::UP026 => CheckKind::RewriteMockImport(MockReference::Import),
|
||||
// pydocstyle
|
||||
CheckCode::D100 => CheckKind::PublicModule,
|
||||
CheckCode::D101 => CheckKind::PublicClass,
|
||||
@@ -1327,6 +1412,7 @@ impl CheckCode {
|
||||
CheckCode::PGH001 => CheckKind::NoEval,
|
||||
CheckCode::PGH002 => CheckKind::DeprecatedLogWarn,
|
||||
CheckCode::PGH003 => CheckKind::BlanketTypeIgnore,
|
||||
CheckCode::PGH004 => CheckKind::BlanketNOQA,
|
||||
// flake8-unused-arguments
|
||||
CheckCode::ARG001 => CheckKind::UnusedFunctionArgument("...".to_string()),
|
||||
CheckCode::ARG002 => CheckKind::UnusedMethodArgument("...".to_string()),
|
||||
@@ -1554,7 +1640,6 @@ impl CheckCode {
|
||||
CheckCode::F821 => CheckCategory::Pyflakes,
|
||||
CheckCode::F822 => CheckCategory::Pyflakes,
|
||||
CheckCode::F823 => CheckCategory::Pyflakes,
|
||||
CheckCode::F831 => CheckCategory::Pyflakes,
|
||||
CheckCode::F841 => CheckCategory::Pyflakes,
|
||||
CheckCode::F842 => CheckCategory::Pyflakes,
|
||||
CheckCode::F901 => CheckCategory::Pyflakes,
|
||||
@@ -1562,8 +1647,10 @@ impl CheckCode {
|
||||
CheckCode::FBT002 => CheckCategory::Flake8BooleanTrap,
|
||||
CheckCode::FBT003 => CheckCategory::Flake8BooleanTrap,
|
||||
CheckCode::I001 => CheckCategory::Isort,
|
||||
CheckCode::TID252 => CheckCategory::Flake8TidyImports,
|
||||
CheckCode::ICN001 => CheckCategory::Flake8ImportConventions,
|
||||
CheckCode::ISC001 => CheckCategory::Flake8ImplicitStrConcat,
|
||||
CheckCode::ISC002 => CheckCategory::Flake8ImplicitStrConcat,
|
||||
CheckCode::ISC003 => CheckCategory::Flake8ImplicitStrConcat,
|
||||
CheckCode::N801 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N802 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N803 => CheckCategory::PEP8Naming,
|
||||
@@ -1594,6 +1681,7 @@ impl CheckCode {
|
||||
CheckCode::PGH001 => CheckCategory::PygrepHooks,
|
||||
CheckCode::PGH002 => CheckCategory::PygrepHooks,
|
||||
CheckCode::PGH003 => CheckCategory::PygrepHooks,
|
||||
CheckCode::PGH004 => CheckCategory::PygrepHooks,
|
||||
CheckCode::PLC0414 => CheckCategory::Pylint,
|
||||
CheckCode::PLC2201 => CheckCategory::Pylint,
|
||||
CheckCode::PLC3002 => CheckCategory::Pylint,
|
||||
@@ -1633,6 +1721,8 @@ impl CheckCode {
|
||||
CheckCode::T100 => CheckCategory::Flake8Debugger,
|
||||
CheckCode::T201 => CheckCategory::Flake8Print,
|
||||
CheckCode::T203 => CheckCategory::Flake8Print,
|
||||
CheckCode::TID251 => CheckCategory::Flake8TidyImports,
|
||||
CheckCode::TID252 => CheckCategory::Flake8TidyImports,
|
||||
CheckCode::UP001 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP003 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP004 => CheckCategory::Pyupgrade,
|
||||
@@ -1655,6 +1745,9 @@ impl CheckCode {
|
||||
CheckCode::UP021 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP022 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP023 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP024 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP025 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP026 => CheckCategory::Pyupgrade,
|
||||
CheckCode::W292 => CheckCategory::Pycodestyle,
|
||||
CheckCode::W605 => CheckCategory::Pycodestyle,
|
||||
CheckCode::YTT101 => CheckCategory::Flake82020,
|
||||
@@ -1683,9 +1776,8 @@ impl CheckKind {
|
||||
CheckKind::BreakOutsideLoop => &CheckCode::F701,
|
||||
CheckKind::ContinueOutsideLoop => &CheckCode::F702,
|
||||
CheckKind::DefaultExceptNotLast => &CheckCode::F707,
|
||||
CheckKind::DoNotAssignLambda => &CheckCode::E731,
|
||||
CheckKind::DoNotAssignLambda(..) => &CheckCode::E731,
|
||||
CheckKind::DoNotUseBareExcept => &CheckCode::E722,
|
||||
CheckKind::DuplicateArgumentName => &CheckCode::F831,
|
||||
CheckKind::FStringMissingPlaceholders => &CheckCode::F541,
|
||||
CheckKind::ForwardAnnotationSyntaxError(..) => &CheckCode::F722,
|
||||
CheckKind::FutureFeatureNotDefined(..) => &CheckCode::F407,
|
||||
@@ -1696,7 +1788,7 @@ impl CheckKind {
|
||||
CheckKind::ImportStarUsage(..) => &CheckCode::F405,
|
||||
CheckKind::ImportStarUsed(..) => &CheckCode::F403,
|
||||
CheckKind::InvalidPrintSyntax => &CheckCode::F633,
|
||||
CheckKind::IsLiteral => &CheckCode::F632,
|
||||
CheckKind::IsLiteral(..) => &CheckCode::F632,
|
||||
CheckKind::LateFutureImport => &CheckCode::F404,
|
||||
CheckKind::LineTooLong(..) => &CheckCode::E501,
|
||||
CheckKind::MultipleImportsOnOneLine => &CheckCode::E401,
|
||||
@@ -1806,6 +1898,7 @@ impl CheckKind {
|
||||
// flake8-debugger
|
||||
CheckKind::Debugger(..) => &CheckCode::T100,
|
||||
// flake8-tidy-imports
|
||||
CheckKind::BannedApi { .. } => &CheckCode::TID251,
|
||||
CheckKind::BannedRelativeImport(..) => &CheckCode::TID252,
|
||||
// flake8-return
|
||||
CheckKind::UnnecessaryReturnNone => &CheckCode::RET501,
|
||||
@@ -1816,6 +1909,10 @@ impl CheckKind {
|
||||
CheckKind::SuperfluousElseRaise(..) => &CheckCode::RET506,
|
||||
CheckKind::SuperfluousElseContinue(..) => &CheckCode::RET507,
|
||||
CheckKind::SuperfluousElseBreak(..) => &CheckCode::RET508,
|
||||
// flake8-implicit-str-concat
|
||||
CheckKind::SingleLineImplicitStringConcatenation => &CheckCode::ISC001,
|
||||
CheckKind::MultiLineImplicitStringConcatenation => &CheckCode::ISC002,
|
||||
CheckKind::ExplicitStringConcatenation => &CheckCode::ISC003,
|
||||
// flake8-print
|
||||
CheckKind::PrintFound => &CheckCode::T201,
|
||||
CheckKind::PPrintFound => &CheckCode::T203,
|
||||
@@ -1863,15 +1960,18 @@ impl CheckKind {
|
||||
CheckKind::UnnecessaryEncodeUTF8 => &CheckCode::UP012,
|
||||
CheckKind::ConvertTypedDictFunctionalToClass(..) => &CheckCode::UP013,
|
||||
CheckKind::ConvertNamedTupleFunctionalToClass(..) => &CheckCode::UP014,
|
||||
CheckKind::RedundantOpenModes => &CheckCode::UP015,
|
||||
CheckKind::RedundantOpenModes(..) => &CheckCode::UP015,
|
||||
CheckKind::RemoveSixCompat => &CheckCode::UP016,
|
||||
CheckKind::DatetimeTimezoneUTC => &CheckCode::UP017,
|
||||
CheckKind::NativeLiterals => &CheckCode::UP018,
|
||||
CheckKind::NativeLiterals(..) => &CheckCode::UP018,
|
||||
CheckKind::TypingTextStrAlias => &CheckCode::UP019,
|
||||
CheckKind::OpenAlias => &CheckCode::UP020,
|
||||
CheckKind::ReplaceUniversalNewlines => &CheckCode::UP021,
|
||||
CheckKind::ReplaceStdoutStderr => &CheckCode::UP022,
|
||||
CheckKind::RewriteCElementTree => &CheckCode::UP023,
|
||||
CheckKind::OSErrorAlias(..) => &CheckCode::UP024,
|
||||
CheckKind::RewriteUnicodeLiteral => &CheckCode::UP025,
|
||||
CheckKind::RewriteMockImport(..) => &CheckCode::UP026,
|
||||
// pydocstyle
|
||||
CheckKind::BlankLineAfterLastSection(..) => &CheckCode::D413,
|
||||
CheckKind::BlankLineAfterSection(..) => &CheckCode::D410,
|
||||
@@ -1955,6 +2055,7 @@ impl CheckKind {
|
||||
CheckKind::NoEval => &CheckCode::PGH001,
|
||||
CheckKind::DeprecatedLogWarn => &CheckCode::PGH002,
|
||||
CheckKind::BlanketTypeIgnore => &CheckCode::PGH003,
|
||||
CheckKind::BlanketNOQA => &CheckCode::PGH004,
|
||||
// flake8-unused-arguments
|
||||
CheckKind::UnusedFunctionArgument(..) => &CheckCode::ARG001,
|
||||
CheckKind::UnusedMethodArgument(..) => &CheckCode::ARG002,
|
||||
@@ -2020,13 +2121,10 @@ impl CheckKind {
|
||||
CheckKind::DefaultExceptNotLast => {
|
||||
"An `except` block as not the last exception handler".to_string()
|
||||
}
|
||||
CheckKind::DoNotAssignLambda => {
|
||||
"Do not assign a lambda expression, use a def".to_string()
|
||||
CheckKind::DoNotAssignLambda(..) => {
|
||||
"Do not assign a `lambda` expression, use a `def`".to_string()
|
||||
}
|
||||
CheckKind::DoNotUseBareExcept => "Do not use bare `except`".to_string(),
|
||||
CheckKind::DuplicateArgumentName => {
|
||||
"Duplicate argument name in function definition".to_string()
|
||||
}
|
||||
CheckKind::ForwardAnnotationSyntaxError(body) => {
|
||||
format!("Syntax error in forward annotation: `{body}`")
|
||||
}
|
||||
@@ -2057,7 +2155,10 @@ impl CheckKind {
|
||||
.join(", ");
|
||||
format!("`{name}` may be undefined, or defined from star imports: {sources}")
|
||||
}
|
||||
CheckKind::IsLiteral => "Use `==` and `!=` to compare constant literals".to_string(),
|
||||
CheckKind::IsLiteral(cmpop) => match cmpop {
|
||||
IsCmpop::Is => "Use `==` to compare constant literals".to_string(),
|
||||
IsCmpop::IsNot => "Use `!=` to compare constant literals".to_string(),
|
||||
},
|
||||
CheckKind::LateFutureImport => {
|
||||
"`from __future__` imports must occur at the beginning of the file".to_string()
|
||||
}
|
||||
@@ -2075,10 +2176,8 @@ impl CheckKind {
|
||||
format!("Dictionary key `{name}` repeated")
|
||||
}
|
||||
CheckKind::NoneComparison(op) => match op {
|
||||
RejectedCmpop::Eq => "Comparison to `None` should be `cond is None`".to_string(),
|
||||
RejectedCmpop::NotEq => {
|
||||
"Comparison to `None` should be `cond is not None`".to_string()
|
||||
}
|
||||
EqCmpop::Eq => "Comparison to `None` should be `cond is None`".to_string(),
|
||||
EqCmpop::NotEq => "Comparison to `None` should be `cond is not None`".to_string(),
|
||||
},
|
||||
CheckKind::NotInTest => "Test for membership should be `not in`".to_string(),
|
||||
CheckKind::NotIsTest => "Test for object identity should be `is not`".to_string(),
|
||||
@@ -2142,16 +2241,16 @@ impl CheckKind {
|
||||
CheckKind::ExpressionsInStarAssignment => {
|
||||
"Too many expressions in star-unpacking assignment".to_string()
|
||||
}
|
||||
CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq) => {
|
||||
CheckKind::TrueFalseComparison(true, EqCmpop::Eq) => {
|
||||
"Comparison to `True` should be `cond is True`".to_string()
|
||||
}
|
||||
CheckKind::TrueFalseComparison(true, RejectedCmpop::NotEq) => {
|
||||
CheckKind::TrueFalseComparison(true, EqCmpop::NotEq) => {
|
||||
"Comparison to `True` should be `cond is not True`".to_string()
|
||||
}
|
||||
CheckKind::TrueFalseComparison(false, RejectedCmpop::Eq) => {
|
||||
CheckKind::TrueFalseComparison(false, EqCmpop::Eq) => {
|
||||
"Comparison to `False` should be `cond is False`".to_string()
|
||||
}
|
||||
CheckKind::TrueFalseComparison(false, RejectedCmpop::NotEq) => {
|
||||
CheckKind::TrueFalseComparison(false, EqCmpop::NotEq) => {
|
||||
"Comparison to `False` should be `cond is not False`".to_string()
|
||||
}
|
||||
CheckKind::TwoStarredExpressions => "Two starred expressions in assignment".to_string(),
|
||||
@@ -2168,7 +2267,7 @@ impl CheckKind {
|
||||
CheckKind::UnusedAnnotation(name) => {
|
||||
format!("Local variable `{name}` is annotated but never used")
|
||||
}
|
||||
CheckKind::UnusedImport(name, ignore_init) => {
|
||||
CheckKind::UnusedImport(name, ignore_init, ..) => {
|
||||
if *ignore_init {
|
||||
format!(
|
||||
"`{name}` imported but unused; consider adding to `__all__` or using a \
|
||||
@@ -2432,6 +2531,7 @@ impl CheckKind {
|
||||
DebuggerUsingType::Import(name) => format!("Import for `{name}` found"),
|
||||
},
|
||||
// flake8-tidy-imports
|
||||
CheckKind::BannedApi { name, message } => format!("`{name}` is banned: {message}"),
|
||||
CheckKind::BannedRelativeImport(strictness) => match strictness {
|
||||
Strictness::Parents => {
|
||||
"Relative imports from parent modules are banned".to_string()
|
||||
@@ -2463,6 +2563,16 @@ impl CheckKind {
|
||||
CheckKind::SuperfluousElseBreak(branch) => {
|
||||
format!("Unnecessary `{branch}` after `break` statement")
|
||||
}
|
||||
// flake8-implicit-str-concat
|
||||
CheckKind::SingleLineImplicitStringConcatenation => {
|
||||
"Implicitly concatenated string literals on one line".to_string()
|
||||
}
|
||||
CheckKind::MultiLineImplicitStringConcatenation => {
|
||||
"Implicitly concatenated string literals over continuation line".to_string()
|
||||
}
|
||||
CheckKind::ExplicitStringConcatenation => {
|
||||
"Explicitly concatenated string should be implicitly concatenated".to_string()
|
||||
}
|
||||
// flake8-print
|
||||
CheckKind::PrintFound => "`print` found".to_string(),
|
||||
CheckKind::PPrintFound => "`pprint` found".to_string(),
|
||||
@@ -2568,7 +2678,7 @@ impl CheckKind {
|
||||
format!("`{alias}` is deprecated, use `{target}`")
|
||||
}
|
||||
CheckKind::UselessObjectInheritance(name) => {
|
||||
format!("Class `{name}` inherits from object")
|
||||
format!("Class `{name}` inherits from `object`")
|
||||
}
|
||||
CheckKind::UsePEP585Annotation(name) => {
|
||||
format!(
|
||||
@@ -2594,14 +2704,24 @@ impl CheckKind {
|
||||
"Unnecessary parameters to `functools.lru_cache`".to_string()
|
||||
}
|
||||
CheckKind::UnnecessaryEncodeUTF8 => "Unnecessary call to `encode` as UTF-8".to_string(),
|
||||
CheckKind::RedundantOpenModes => "Unnecessary open mode parameters".to_string(),
|
||||
CheckKind::RedundantOpenModes(replacement) => match replacement {
|
||||
None => "Unnecessary open mode parameters".to_string(),
|
||||
Some(replacement) => {
|
||||
format!("Unnecessary open mode parameters, use \"{replacement}\"")
|
||||
}
|
||||
},
|
||||
CheckKind::RemoveSixCompat => "Unnecessary `six` compatibility usage".to_string(),
|
||||
CheckKind::DatetimeTimezoneUTC => "Use `datetime.UTC` alias".to_string(),
|
||||
CheckKind::NativeLiterals => "Unnecessary call to `str` and `bytes`".to_string(),
|
||||
CheckKind::NativeLiterals(literal_type) => {
|
||||
format!("Unnecessary call to `{literal_type}`")
|
||||
}
|
||||
CheckKind::OpenAlias => "Use builtin `open`".to_string(),
|
||||
CheckKind::ConvertTypedDictFunctionalToClass(name) => {
|
||||
format!("Convert `{name}` from `TypedDict` functional to class syntax")
|
||||
}
|
||||
CheckKind::ConvertNamedTupleFunctionalToClass(name) => {
|
||||
format!("Convert `{name}` from `NamedTuple` functional to class syntax")
|
||||
}
|
||||
CheckKind::ReplaceUniversalNewlines => {
|
||||
"`universal_newlines` is deprecated, use `text`".to_string()
|
||||
}
|
||||
@@ -2611,8 +2731,10 @@ impl CheckKind {
|
||||
CheckKind::RewriteCElementTree => {
|
||||
"`cElementTree` is deprecated, use `ElementTree`".to_string()
|
||||
}
|
||||
CheckKind::ConvertNamedTupleFunctionalToClass(name) => {
|
||||
format!("Convert `{name}` from `NamedTuple` functional to class syntax")
|
||||
CheckKind::OSErrorAlias(..) => "Replace aliased errors with `OSError`".to_string(),
|
||||
CheckKind::RewriteUnicodeLiteral => "Remove unicode literals from strings".to_string(),
|
||||
CheckKind::RewriteMockImport(..) => {
|
||||
"`mock` is deprecated, use `unittest.mock`".to_string()
|
||||
}
|
||||
// pydocstyle
|
||||
CheckKind::FitsOnOneLine => "One-line docstring should fit on one line".to_string(),
|
||||
@@ -2790,14 +2912,13 @@ impl CheckKind {
|
||||
CheckKind::HardcodedBindAllInterfaces => {
|
||||
"Possible binding to all interfaces".to_string()
|
||||
}
|
||||
CheckKind::HardcodedPasswordString(string) => {
|
||||
format!("Possible hardcoded password: `\"{string}\"`")
|
||||
}
|
||||
CheckKind::HardcodedPasswordFuncArg(string) => {
|
||||
format!("Possible hardcoded password: `\"{string}\"`")
|
||||
}
|
||||
CheckKind::HardcodedPasswordDefault(string) => {
|
||||
format!("Possible hardcoded password: `\"{string}\"`")
|
||||
CheckKind::HardcodedPasswordString(string)
|
||||
| CheckKind::HardcodedPasswordFuncArg(string)
|
||||
| CheckKind::HardcodedPasswordDefault(string) => {
|
||||
format!(
|
||||
"Possible hardcoded password: `\"{}\"`",
|
||||
string.escape_debug()
|
||||
)
|
||||
}
|
||||
// flake8-blind-except
|
||||
CheckKind::BlindExcept(name) => format!("Do not catch blind exception: `{name}`"),
|
||||
@@ -2816,13 +2937,14 @@ impl CheckKind {
|
||||
"Boolean positional value in function call".to_string()
|
||||
}
|
||||
// pygrep-hooks
|
||||
CheckKind::NoEval => "No builtin `eval()` allowed".to_string(),
|
||||
CheckKind::DeprecatedLogWarn => {
|
||||
"`warn` is deprecated in favor of `warning`".to_string()
|
||||
}
|
||||
CheckKind::BlanketNOQA => "Use specific error codes when using `noqa`".to_string(),
|
||||
CheckKind::BlanketTypeIgnore => {
|
||||
"Use specific error codes when ignoring type issues".to_string()
|
||||
}
|
||||
CheckKind::DeprecatedLogWarn => {
|
||||
"`warn` is deprecated in favor of `warning`".to_string()
|
||||
}
|
||||
CheckKind::NoEval => "No builtin `eval()` allowed".to_string(),
|
||||
// flake8-unused-arguments
|
||||
CheckKind::UnusedFunctionArgument(name) => {
|
||||
format!("Unused function argument: `{name}`")
|
||||
@@ -3027,6 +3149,7 @@ impl CheckKind {
|
||||
matches!(
|
||||
self,
|
||||
CheckKind::AmbiguousUnicodeCharacterString(..)
|
||||
| CheckKind::AmbiguousUnicodeCharacterComment(..)
|
||||
| CheckKind::AmbiguousUnicodeCharacterDocstring(..)
|
||||
| CheckKind::BlankLineAfterLastSection(..)
|
||||
| CheckKind::BlankLineAfterSection(..)
|
||||
@@ -3040,7 +3163,7 @@ impl CheckKind {
|
||||
| CheckKind::DatetimeTimezoneUTC
|
||||
| CheckKind::DeprecatedUnittestAlias(..)
|
||||
| CheckKind::DoNotAssertFalse
|
||||
| CheckKind::DoNotAssignLambda
|
||||
| CheckKind::DoNotAssignLambda(..)
|
||||
| CheckKind::DuplicateHandlerException(..)
|
||||
| CheckKind::EndsInPeriod
|
||||
| CheckKind::EndsInPunctuation
|
||||
@@ -3048,16 +3171,12 @@ impl CheckKind {
|
||||
| CheckKind::ImplicitReturn
|
||||
| CheckKind::ImplicitReturnValue
|
||||
| CheckKind::InvalidEscapeSequence(..)
|
||||
| CheckKind::IsLiteral
|
||||
| CheckKind::IsLiteral(..)
|
||||
| CheckKind::KeyInDict(..)
|
||||
| CheckKind::MisplacedComparisonConstant(..)
|
||||
| CheckKind::MissingReturnTypeSpecialMethod(..)
|
||||
| CheckKind::NativeLiterals
|
||||
| CheckKind::OpenAlias
|
||||
| CheckKind::NativeLiterals(..)
|
||||
| CheckKind::NewLineAfterLastParagraph
|
||||
| CheckKind::ReplaceUniversalNewlines
|
||||
| CheckKind::ReplaceStdoutStderr
|
||||
| CheckKind::RewriteCElementTree
|
||||
| CheckKind::NewLineAfterSectionName(..)
|
||||
| CheckKind::NoBlankLineAfterFunction(..)
|
||||
| CheckKind::NoBlankLineBeforeClass(..)
|
||||
@@ -3070,16 +3189,23 @@ impl CheckKind {
|
||||
| CheckKind::NoneComparison(..)
|
||||
| CheckKind::NotInTest
|
||||
| CheckKind::NotIsTest
|
||||
| CheckKind::OSErrorAlias(..)
|
||||
| CheckKind::OneBlankLineAfterClass(..)
|
||||
| CheckKind::OneBlankLineBeforeClass(..)
|
||||
| CheckKind::OpenAlias
|
||||
| CheckKind::PEP3120UnnecessaryCodingComment
|
||||
| CheckKind::PPrintFound
|
||||
| CheckKind::PercentFormatExtraNamedArguments(..)
|
||||
| CheckKind::PrintFound
|
||||
| CheckKind::RaiseNotImplemented
|
||||
| CheckKind::RedundantOpenModes
|
||||
| CheckKind::RedundantOpenModes(..)
|
||||
| CheckKind::RedundantTupleInExceptionHandler(..)
|
||||
| CheckKind::RemoveSixCompat
|
||||
| CheckKind::ReplaceStdoutStderr
|
||||
| CheckKind::ReplaceUniversalNewlines
|
||||
| CheckKind::RewriteCElementTree
|
||||
| CheckKind::RewriteMockImport(..)
|
||||
| CheckKind::RewriteUnicodeLiteral
|
||||
| CheckKind::SectionNameEndsInColon(..)
|
||||
| CheckKind::SectionNotOverIndented(..)
|
||||
| CheckKind::SectionUnderlineAfterName(..)
|
||||
@@ -3109,7 +3235,7 @@ impl CheckKind {
|
||||
| CheckKind::UnnecessaryLiteralWithinTupleCall(..)
|
||||
| CheckKind::UnnecessaryReturnNone
|
||||
| CheckKind::UnsortedImports
|
||||
| CheckKind::UnusedImport(_, false)
|
||||
| CheckKind::UnusedImport(_, false, _)
|
||||
| CheckKind::UnusedLoopControlVariable(..)
|
||||
| CheckKind::UnusedNOQA(..)
|
||||
| CheckKind::UsePEP585Annotation(..)
|
||||
@@ -3120,6 +3246,243 @@ impl CheckKind {
|
||||
| CheckKind::UselessObjectInheritance(..)
|
||||
)
|
||||
}
|
||||
|
||||
/// The message used to describe the fix action for a given `CheckKind`.
|
||||
pub fn commit(&self) -> Option<String> {
|
||||
match self {
|
||||
CheckKind::AmbiguousUnicodeCharacterString(confusable, representant)
|
||||
| CheckKind::AmbiguousUnicodeCharacterDocstring(confusable, representant)
|
||||
| CheckKind::AmbiguousUnicodeCharacterComment(confusable, representant) => {
|
||||
Some(format!("Replace '{confusable}' with '{representant}'"))
|
||||
}
|
||||
CheckKind::BlankLineAfterLastSection(name) => {
|
||||
Some(format!("Add blank line after \"{name}\""))
|
||||
}
|
||||
CheckKind::BlankLineAfterSection(name) => {
|
||||
Some(format!("Add blank line after \"{name}\""))
|
||||
}
|
||||
CheckKind::BlankLineAfterSummary => Some("Insert single blank line".to_string()),
|
||||
CheckKind::BlankLineBeforeSection(name) => {
|
||||
Some(format!("Add blank line before \"{name}\""))
|
||||
}
|
||||
CheckKind::CapitalizeSectionName(name) => Some(format!("Capitalize \"{name}\"")),
|
||||
CheckKind::CommentedOutCode => Some("Remove commented-out code".to_string()),
|
||||
CheckKind::ConvertTypedDictFunctionalToClass(name)
|
||||
| CheckKind::ConvertNamedTupleFunctionalToClass(name) => {
|
||||
Some(format!("Convert `{name}` to class syntax"))
|
||||
}
|
||||
CheckKind::DashedUnderlineAfterSection(name) => {
|
||||
Some(format!("Add dashed line under \"{name}\""))
|
||||
}
|
||||
CheckKind::DatetimeTimezoneUTC => Some("Convert to `datetime.UTC` alias".to_string()),
|
||||
CheckKind::DeprecatedUnittestAlias(alias, target) => {
|
||||
Some(format!("Replace `{target}` with `{alias}`"))
|
||||
}
|
||||
CheckKind::DoNotAssertFalse => Some("Replace `assert False`".to_string()),
|
||||
CheckKind::DoNotAssignLambda(name) => Some(format!("Rewrite `{name}` as a `def`")),
|
||||
CheckKind::DuplicateHandlerException(..) => Some("De-duplicate exceptions".to_string()),
|
||||
CheckKind::EndsInPeriod => Some("Add period".to_string()),
|
||||
CheckKind::EndsInPunctuation => Some("Add closing punctuation".to_string()),
|
||||
CheckKind::GetAttrWithConstant => {
|
||||
Some("Replace `getattr` with attribute access".to_string())
|
||||
}
|
||||
CheckKind::ImplicitReturnValue => Some("Add explicit `None` return value".to_string()),
|
||||
CheckKind::ImplicitReturn => Some("Add explicit `return` statement".to_string()),
|
||||
CheckKind::InvalidEscapeSequence(..) => {
|
||||
Some("Add backslash to escape sequence".to_string())
|
||||
}
|
||||
CheckKind::IsLiteral(cmpop) => Some(match cmpop {
|
||||
IsCmpop::Is => "Replace `is` with `==`".to_string(),
|
||||
IsCmpop::IsNot => "Replace `is not` with `!=`".to_string(),
|
||||
}),
|
||||
CheckKind::KeyInDict(key, dict) => Some(format!("Convert to `{key} in {dict}`")),
|
||||
CheckKind::MisplacedComparisonConstant(comparison) => {
|
||||
Some(format!("Replace with {comparison}"))
|
||||
}
|
||||
CheckKind::MissingReturnTypeSpecialMethod(..) => {
|
||||
Some("Add `None` return type".to_string())
|
||||
}
|
||||
CheckKind::NativeLiterals(literal_type) => {
|
||||
Some(format!("Replace with `{literal_type}`"))
|
||||
}
|
||||
CheckKind::OpenAlias => Some("Replace with builtin `open`".to_string()),
|
||||
CheckKind::NewLineAfterLastParagraph => {
|
||||
Some("Move closing quotes to new line".to_string())
|
||||
}
|
||||
CheckKind::ReplaceUniversalNewlines => {
|
||||
Some("Replace with `text` keyword argument".to_string())
|
||||
}
|
||||
CheckKind::ReplaceStdoutStderr => {
|
||||
Some("Replace with `capture_output` keyword argument".to_string())
|
||||
}
|
||||
CheckKind::RewriteCElementTree => Some("Replace with `ElementTree`".to_string()),
|
||||
CheckKind::RewriteUnicodeLiteral => Some("Remove unicode prefix".to_string()),
|
||||
CheckKind::RewriteMockImport(reference_type) => Some(match reference_type {
|
||||
MockReference::Import => "Import from `unittest.mock` instead".to_string(),
|
||||
MockReference::Attribute => "Replace `mock.mock` with `mock`".to_string(),
|
||||
}),
|
||||
CheckKind::NewLineAfterSectionName(name) => {
|
||||
Some(format!("Add newline after \"{name}\""))
|
||||
}
|
||||
CheckKind::NoBlankLineBeforeFunction(..) => {
|
||||
Some("Remove blank line(s) before function docstring".to_string())
|
||||
}
|
||||
CheckKind::NoBlankLineAfterFunction(..) => {
|
||||
Some("Remove blank line(s) after function docstring".to_string())
|
||||
}
|
||||
CheckKind::NoBlankLineBeforeClass(..) => {
|
||||
Some("Remove blank line(s) before class docstring".to_string())
|
||||
}
|
||||
CheckKind::OneBlankLineBeforeClass(..) => {
|
||||
Some("Insert 1 blank line before class docstring".to_string())
|
||||
}
|
||||
CheckKind::OneBlankLineAfterClass(..) => {
|
||||
Some("Insert 1 blank line after class docstring".to_string())
|
||||
}
|
||||
CheckKind::OSErrorAlias(name) => Some(match name {
|
||||
None => "Replace with builtin `OSError`".to_string(),
|
||||
Some(name) => format!("Replace `{name}` with builtin `OSError`"),
|
||||
}),
|
||||
CheckKind::NoBlankLinesBetweenHeaderAndContent(..) => {
|
||||
Some("Remove blank line(s)".to_string())
|
||||
}
|
||||
CheckKind::NoNewLineAtEndOfFile => Some("Add trailing newline".to_string()),
|
||||
CheckKind::NoOverIndentation => Some("Remove over-indentation".to_string()),
|
||||
CheckKind::NoSurroundingWhitespace => Some("Trim surrounding whitespace".to_string()),
|
||||
CheckKind::NoUnderIndentation => Some("Increase indentation".to_string()),
|
||||
CheckKind::NoneComparison(op) => Some(match op {
|
||||
EqCmpop::Eq => "Replace with `cond is None`".to_string(),
|
||||
EqCmpop::NotEq => "Replace with `cond is not None`".to_string(),
|
||||
}),
|
||||
CheckKind::NotInTest => Some("Convert to `not in`".to_string()),
|
||||
CheckKind::NotIsTest => Some("Convert to `is not`".to_string()),
|
||||
CheckKind::PEP3120UnnecessaryCodingComment => {
|
||||
Some("Remove unnecessary coding comment".to_string())
|
||||
}
|
||||
CheckKind::PPrintFound => Some("Remove `pprint`".to_string()),
|
||||
CheckKind::PercentFormatExtraNamedArguments(missing)
|
||||
| CheckKind::StringDotFormatExtraNamedArguments(missing) => {
|
||||
let message = missing.join(", ");
|
||||
Some(format!("Remove extra named arguments: {message}"))
|
||||
}
|
||||
CheckKind::PrintFound => Some("Remove `print`".to_string()),
|
||||
CheckKind::RaiseNotImplemented => Some("Use `raise NotImplementedError`".to_string()),
|
||||
CheckKind::RedundantOpenModes(replacement) => Some(match replacement {
|
||||
None => "Remove open mode parameters".to_string(),
|
||||
Some(replacement) => {
|
||||
format!("Replace with \"{replacement}\"")
|
||||
}
|
||||
}),
|
||||
CheckKind::RedundantTupleInExceptionHandler(name) => {
|
||||
Some(format!("Replace with `except {name}`"))
|
||||
}
|
||||
CheckKind::RemoveSixCompat => Some("Remove `six` usage".to_string()),
|
||||
CheckKind::SectionNameEndsInColon(name) => Some(format!("Add colon to \"{name}\"")),
|
||||
CheckKind::SectionNotOverIndented(name) => {
|
||||
Some(format!("Remove over-indentation from \"{name}\""))
|
||||
}
|
||||
CheckKind::SectionUnderlineAfterName(name) => {
|
||||
Some(format!("Add underline to \"{name}\""))
|
||||
}
|
||||
CheckKind::SectionUnderlineMatchesSectionLength(name) => {
|
||||
Some(format!("Adjust underline length to match \"{name}\""))
|
||||
}
|
||||
CheckKind::SectionUnderlineNotOverIndented(name) => {
|
||||
Some(format!("Remove over-indentation from \"{name}\" underline"))
|
||||
}
|
||||
CheckKind::SetAttrWithConstant => Some("Replace `setattr` with assignment".to_string()),
|
||||
CheckKind::SuperCallWithParameters => Some("Remove `__super__` parameters".to_string()),
|
||||
CheckKind::TrueFalseComparison(true, EqCmpop::Eq) => {
|
||||
Some("Replace with `cond is True`".to_string())
|
||||
}
|
||||
CheckKind::TrueFalseComparison(true, EqCmpop::NotEq) => {
|
||||
Some("Replace with `cond is not True`".to_string())
|
||||
}
|
||||
CheckKind::TrueFalseComparison(false, EqCmpop::Eq) => {
|
||||
Some("Replace with `cond is False`".to_string())
|
||||
}
|
||||
CheckKind::TrueFalseComparison(false, EqCmpop::NotEq) => {
|
||||
Some("Replace with `cond is not False`".to_string())
|
||||
}
|
||||
CheckKind::TypeOfPrimitive(primitive) => Some(format!(
|
||||
"Replace `type(...)` with `{}`",
|
||||
primitive.builtin()
|
||||
)),
|
||||
CheckKind::TypingTextStrAlias => Some("Replace with `str`".to_string()),
|
||||
CheckKind::UnnecessaryCallAroundSorted(func) => {
|
||||
Some(format!("Remove unnecessary `{func}` call"))
|
||||
}
|
||||
CheckKind::UnnecessaryCollectionCall(..) => Some("Rewrite as a literal".to_string()),
|
||||
CheckKind::UnnecessaryComprehension(obj_type) => {
|
||||
Some(format!("Rewrite using `{obj_type}()`"))
|
||||
}
|
||||
CheckKind::UnnecessaryEncodeUTF8 => Some("Remove unnecessary `encode`".to_string()),
|
||||
CheckKind::UnnecessaryFutureImport(..) => {
|
||||
Some("Remove unnecessary `__future__` import".to_string())
|
||||
}
|
||||
CheckKind::UnnecessaryGeneratorDict => {
|
||||
Some("Rewrite as a `dict` comprehension".to_string())
|
||||
}
|
||||
CheckKind::UnnecessaryGeneratorList => {
|
||||
Some("Rewrite as a `list` comprehension".to_string())
|
||||
}
|
||||
CheckKind::UnnecessaryGeneratorSet => {
|
||||
Some("Rewrite as a `set` comprehension".to_string())
|
||||
}
|
||||
CheckKind::UnnecessaryLRUCacheParams => {
|
||||
Some("Remove unnecessary parameters".to_string())
|
||||
}
|
||||
CheckKind::UnnecessaryListCall => Some("Remove outer `list` call".to_string()),
|
||||
CheckKind::UnnecessaryListComprehensionDict => {
|
||||
Some("Rewrite as a `dict` comprehension".to_string())
|
||||
}
|
||||
CheckKind::UnnecessaryListComprehensionSet => {
|
||||
Some("Rewrite as a `set` comprehension".to_string())
|
||||
}
|
||||
CheckKind::UnnecessaryLiteralDict(..) => {
|
||||
Some("Rewrite as a `dict` literal".to_string())
|
||||
}
|
||||
CheckKind::UnnecessaryLiteralSet(..) => Some("Rewrite as a `set` literal".to_string()),
|
||||
CheckKind::UnnecessaryLiteralWithinTupleCall(literal) => Some({
|
||||
if literal == "list" {
|
||||
"Rewrite as a `tuple` literal".to_string()
|
||||
} else {
|
||||
"Remove outer `tuple` call".to_string()
|
||||
}
|
||||
}),
|
||||
CheckKind::UnnecessaryLiteralWithinListCall(literal) => Some({
|
||||
if literal == "list" {
|
||||
"Remove outer `list` call".to_string()
|
||||
} else {
|
||||
"Rewrite as a `list` literal".to_string()
|
||||
}
|
||||
}),
|
||||
CheckKind::UnnecessaryReturnNone => Some("Remove explicit `return None`".to_string()),
|
||||
CheckKind::UnsortedImports => Some("Organize imports".to_string()),
|
||||
CheckKind::UnusedImport(name, false, multiple) => {
|
||||
if *multiple {
|
||||
Some("Remove unused import".to_string())
|
||||
} else {
|
||||
Some(format!("Remove unused import: `{name}`"))
|
||||
}
|
||||
}
|
||||
CheckKind::UnusedLoopControlVariable(name) => {
|
||||
Some(format!("Rename unused `{name}` to `_{name}`"))
|
||||
}
|
||||
CheckKind::UnusedNOQA(..) => Some("Remove unused `noqa` directive".to_string()),
|
||||
CheckKind::UsePEP585Annotation(name) => {
|
||||
Some(format!("Replace `{name}` with `{}`", name.to_lowercase(),))
|
||||
}
|
||||
CheckKind::UsePEP604Annotation => Some("Convert to `X | Y`".to_string()),
|
||||
CheckKind::UseSysExit(name) => Some(format!("Replace `{name}` with `sys.exit()`")),
|
||||
CheckKind::UselessImportAlias => Some("Remove import alias".to_string()),
|
||||
CheckKind::UselessMetaclassType => Some("Remove `__metaclass__ = type`".to_string()),
|
||||
CheckKind::UselessObjectInheritance(..) => {
|
||||
Some("Remove `object` inheritance".to_string())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -3128,6 +3491,7 @@ pub struct Check {
|
||||
pub location: Location,
|
||||
pub end_location: Location,
|
||||
pub fix: Option<Fix>,
|
||||
pub parent: Option<Location>,
|
||||
}
|
||||
|
||||
impl Check {
|
||||
@@ -3137,11 +3501,18 @@ impl Check {
|
||||
location: range.location,
|
||||
end_location: range.end_location,
|
||||
fix: None,
|
||||
parent: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn amend(&mut self, fix: Fix) {
|
||||
pub fn amend(&mut self, fix: Fix) -> &mut Self {
|
||||
self.fix = Some(fix);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn parent(&mut self, parent: Location) -> &mut Self {
|
||||
self.parent = Some(parent);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3292,4 +3663,17 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixable_codes() {
|
||||
for check_code in CheckCode::iter() {
|
||||
let kind = check_code.kind();
|
||||
if kind.fixable() {
|
||||
assert!(
|
||||
kind.commit().is_some(),
|
||||
"{check_code:?} is fixable but has no commit message."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,8 +283,6 @@ pub enum CheckCodePrefix {
|
||||
F821,
|
||||
F822,
|
||||
F823,
|
||||
F83,
|
||||
F831,
|
||||
F84,
|
||||
F841,
|
||||
F842,
|
||||
@@ -314,6 +312,12 @@ pub enum CheckCodePrefix {
|
||||
ICN0,
|
||||
ICN00,
|
||||
ICN001,
|
||||
ISC,
|
||||
ISC0,
|
||||
ISC00,
|
||||
ISC001,
|
||||
ISC002,
|
||||
ISC003,
|
||||
M,
|
||||
M0,
|
||||
M001,
|
||||
@@ -377,6 +381,7 @@ pub enum CheckCodePrefix {
|
||||
PGH001,
|
||||
PGH002,
|
||||
PGH003,
|
||||
PGH004,
|
||||
PLC,
|
||||
PLC0,
|
||||
PLC04,
|
||||
@@ -485,6 +490,7 @@ pub enum CheckCodePrefix {
|
||||
TID,
|
||||
TID2,
|
||||
TID25,
|
||||
TID251,
|
||||
TID252,
|
||||
U,
|
||||
U0,
|
||||
@@ -534,6 +540,9 @@ pub enum CheckCodePrefix {
|
||||
UP021,
|
||||
UP022,
|
||||
UP023,
|
||||
UP024,
|
||||
UP025,
|
||||
UP026,
|
||||
W,
|
||||
W2,
|
||||
W29,
|
||||
@@ -638,7 +647,6 @@ impl CheckCodePrefix {
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F842,
|
||||
CheckCode::F901,
|
||||
@@ -704,6 +712,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::C417,
|
||||
CheckCode::T100,
|
||||
CheckCode::C901,
|
||||
CheckCode::TID251,
|
||||
CheckCode::TID252,
|
||||
CheckCode::RET501,
|
||||
CheckCode::RET502,
|
||||
@@ -713,6 +722,9 @@ impl CheckCodePrefix {
|
||||
CheckCode::RET506,
|
||||
CheckCode::RET507,
|
||||
CheckCode::RET508,
|
||||
CheckCode::ISC001,
|
||||
CheckCode::ISC002,
|
||||
CheckCode::ISC003,
|
||||
CheckCode::T201,
|
||||
CheckCode::T203,
|
||||
CheckCode::Q000,
|
||||
@@ -763,6 +775,9 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP021,
|
||||
CheckCode::UP022,
|
||||
CheckCode::UP023,
|
||||
CheckCode::UP024,
|
||||
CheckCode::UP025,
|
||||
CheckCode::UP026,
|
||||
CheckCode::D100,
|
||||
CheckCode::D101,
|
||||
CheckCode::D102,
|
||||
@@ -857,6 +872,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::PGH001,
|
||||
CheckCode::PGH002,
|
||||
CheckCode::PGH003,
|
||||
CheckCode::PGH004,
|
||||
CheckCode::PD002,
|
||||
CheckCode::PD003,
|
||||
CheckCode::PD004,
|
||||
@@ -1491,7 +1507,6 @@ impl CheckCodePrefix {
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F842,
|
||||
CheckCode::F901,
|
||||
@@ -1625,7 +1640,6 @@ impl CheckCodePrefix {
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F842,
|
||||
],
|
||||
@@ -1635,8 +1649,6 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::F821 => vec![CheckCode::F821],
|
||||
CheckCodePrefix::F822 => vec![CheckCode::F822],
|
||||
CheckCodePrefix::F823 => vec![CheckCode::F823],
|
||||
CheckCodePrefix::F83 => vec![CheckCode::F831],
|
||||
CheckCodePrefix::F831 => vec![CheckCode::F831],
|
||||
CheckCodePrefix::F84 => vec![CheckCode::F841, CheckCode::F842],
|
||||
CheckCodePrefix::F841 => vec![CheckCode::F841],
|
||||
CheckCodePrefix::F842 => vec![CheckCode::F842],
|
||||
@@ -1660,7 +1672,7 @@ impl CheckCodePrefix {
|
||||
":".bold(),
|
||||
"`I2` has been remapped to `TID2`".bold()
|
||||
);
|
||||
vec![CheckCode::TID252]
|
||||
vec![CheckCode::TID251, CheckCode::TID252]
|
||||
}
|
||||
CheckCodePrefix::I25 => {
|
||||
one_time_warning!(
|
||||
@@ -1669,7 +1681,7 @@ impl CheckCodePrefix {
|
||||
":".bold(),
|
||||
"`I25` has been remapped to `TID25`".bold()
|
||||
);
|
||||
vec![CheckCode::TID252]
|
||||
vec![CheckCode::TID251, CheckCode::TID252]
|
||||
}
|
||||
CheckCodePrefix::I252 => {
|
||||
one_time_warning!(
|
||||
@@ -1738,6 +1750,12 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::ICN0 => vec![CheckCode::ICN001],
|
||||
CheckCodePrefix::ICN00 => vec![CheckCode::ICN001],
|
||||
CheckCodePrefix::ICN001 => vec![CheckCode::ICN001],
|
||||
CheckCodePrefix::ISC => vec![CheckCode::ISC001, CheckCode::ISC002, CheckCode::ISC003],
|
||||
CheckCodePrefix::ISC0 => vec![CheckCode::ISC001, CheckCode::ISC002, CheckCode::ISC003],
|
||||
CheckCodePrefix::ISC00 => vec![CheckCode::ISC001, CheckCode::ISC002, CheckCode::ISC003],
|
||||
CheckCodePrefix::ISC001 => vec![CheckCode::ISC001],
|
||||
CheckCodePrefix::ISC002 => vec![CheckCode::ISC002],
|
||||
CheckCodePrefix::ISC003 => vec![CheckCode::ISC003],
|
||||
CheckCodePrefix::M => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
@@ -2073,12 +2091,28 @@ impl CheckCodePrefix {
|
||||
);
|
||||
vec![CheckCode::PD901]
|
||||
}
|
||||
CheckCodePrefix::PGH => vec![CheckCode::PGH001, CheckCode::PGH002, CheckCode::PGH003],
|
||||
CheckCodePrefix::PGH0 => vec![CheckCode::PGH001, CheckCode::PGH002, CheckCode::PGH003],
|
||||
CheckCodePrefix::PGH00 => vec![CheckCode::PGH001, CheckCode::PGH002, CheckCode::PGH003],
|
||||
CheckCodePrefix::PGH => vec![
|
||||
CheckCode::PGH001,
|
||||
CheckCode::PGH002,
|
||||
CheckCode::PGH003,
|
||||
CheckCode::PGH004,
|
||||
],
|
||||
CheckCodePrefix::PGH0 => vec![
|
||||
CheckCode::PGH001,
|
||||
CheckCode::PGH002,
|
||||
CheckCode::PGH003,
|
||||
CheckCode::PGH004,
|
||||
],
|
||||
CheckCodePrefix::PGH00 => vec![
|
||||
CheckCode::PGH001,
|
||||
CheckCode::PGH002,
|
||||
CheckCode::PGH003,
|
||||
CheckCode::PGH004,
|
||||
],
|
||||
CheckCodePrefix::PGH001 => vec![CheckCode::PGH001],
|
||||
CheckCodePrefix::PGH002 => vec![CheckCode::PGH002],
|
||||
CheckCodePrefix::PGH003 => vec![CheckCode::PGH003],
|
||||
CheckCodePrefix::PGH004 => vec![CheckCode::PGH004],
|
||||
CheckCodePrefix::PLC => {
|
||||
vec![CheckCode::PLC0414, CheckCode::PLC2201, CheckCode::PLC3002]
|
||||
}
|
||||
@@ -2387,9 +2421,10 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::T20 => vec![CheckCode::T201, CheckCode::T203],
|
||||
CheckCodePrefix::T201 => vec![CheckCode::T201],
|
||||
CheckCodePrefix::T203 => vec![CheckCode::T203],
|
||||
CheckCodePrefix::TID => vec![CheckCode::TID252],
|
||||
CheckCodePrefix::TID2 => vec![CheckCode::TID252],
|
||||
CheckCodePrefix::TID25 => vec![CheckCode::TID252],
|
||||
CheckCodePrefix::TID => vec![CheckCode::TID251, CheckCode::TID252],
|
||||
CheckCodePrefix::TID2 => vec![CheckCode::TID251, CheckCode::TID252],
|
||||
CheckCodePrefix::TID25 => vec![CheckCode::TID251, CheckCode::TID252],
|
||||
CheckCodePrefix::TID251 => vec![CheckCode::TID251],
|
||||
CheckCodePrefix::TID252 => vec![CheckCode::TID252],
|
||||
CheckCodePrefix::U => {
|
||||
one_time_warning!(
|
||||
@@ -2421,6 +2456,9 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP021,
|
||||
CheckCode::UP022,
|
||||
CheckCode::UP023,
|
||||
CheckCode::UP024,
|
||||
CheckCode::UP025,
|
||||
CheckCode::UP026,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U0 => {
|
||||
@@ -2453,6 +2491,9 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP021,
|
||||
CheckCode::UP022,
|
||||
CheckCode::UP023,
|
||||
CheckCode::UP024,
|
||||
CheckCode::UP025,
|
||||
CheckCode::UP026,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U00 => {
|
||||
@@ -2669,6 +2710,9 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP021,
|
||||
CheckCode::UP022,
|
||||
CheckCode::UP023,
|
||||
CheckCode::UP024,
|
||||
CheckCode::UP025,
|
||||
CheckCode::UP026,
|
||||
],
|
||||
CheckCodePrefix::UP0 => vec![
|
||||
CheckCode::UP001,
|
||||
@@ -2693,6 +2737,9 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP021,
|
||||
CheckCode::UP022,
|
||||
CheckCode::UP023,
|
||||
CheckCode::UP024,
|
||||
CheckCode::UP025,
|
||||
CheckCode::UP026,
|
||||
],
|
||||
CheckCodePrefix::UP00 => vec![
|
||||
CheckCode::UP001,
|
||||
@@ -2739,11 +2786,17 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP021,
|
||||
CheckCode::UP022,
|
||||
CheckCode::UP023,
|
||||
CheckCode::UP024,
|
||||
CheckCode::UP025,
|
||||
CheckCode::UP026,
|
||||
],
|
||||
CheckCodePrefix::UP020 => vec![CheckCode::UP020],
|
||||
CheckCodePrefix::UP021 => vec![CheckCode::UP021],
|
||||
CheckCodePrefix::UP022 => vec![CheckCode::UP022],
|
||||
CheckCodePrefix::UP023 => vec![CheckCode::UP023],
|
||||
CheckCodePrefix::UP024 => vec![CheckCode::UP024],
|
||||
CheckCodePrefix::UP025 => vec![CheckCode::UP025],
|
||||
CheckCodePrefix::UP026 => vec![CheckCode::UP026],
|
||||
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
|
||||
CheckCodePrefix::W2 => vec![CheckCode::W292],
|
||||
CheckCodePrefix::W29 => vec![CheckCode::W292],
|
||||
@@ -3058,8 +3111,6 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::F821 => SuffixLength::Three,
|
||||
CheckCodePrefix::F822 => SuffixLength::Three,
|
||||
CheckCodePrefix::F823 => SuffixLength::Three,
|
||||
CheckCodePrefix::F83 => SuffixLength::Two,
|
||||
CheckCodePrefix::F831 => SuffixLength::Three,
|
||||
CheckCodePrefix::F84 => SuffixLength::Two,
|
||||
CheckCodePrefix::F841 => SuffixLength::Three,
|
||||
CheckCodePrefix::F842 => SuffixLength::Three,
|
||||
@@ -3089,6 +3140,12 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::ICN0 => SuffixLength::One,
|
||||
CheckCodePrefix::ICN00 => SuffixLength::Two,
|
||||
CheckCodePrefix::ICN001 => SuffixLength::Three,
|
||||
CheckCodePrefix::ISC => SuffixLength::Zero,
|
||||
CheckCodePrefix::ISC0 => SuffixLength::One,
|
||||
CheckCodePrefix::ISC00 => SuffixLength::Two,
|
||||
CheckCodePrefix::ISC001 => SuffixLength::Three,
|
||||
CheckCodePrefix::ISC002 => SuffixLength::Three,
|
||||
CheckCodePrefix::ISC003 => SuffixLength::Three,
|
||||
CheckCodePrefix::M => SuffixLength::Zero,
|
||||
CheckCodePrefix::M0 => SuffixLength::One,
|
||||
CheckCodePrefix::M001 => SuffixLength::Three,
|
||||
@@ -3152,6 +3209,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::PGH001 => SuffixLength::Three,
|
||||
CheckCodePrefix::PGH002 => SuffixLength::Three,
|
||||
CheckCodePrefix::PGH003 => SuffixLength::Three,
|
||||
CheckCodePrefix::PGH004 => SuffixLength::Three,
|
||||
CheckCodePrefix::PLC => SuffixLength::Zero,
|
||||
CheckCodePrefix::PLC0 => SuffixLength::One,
|
||||
CheckCodePrefix::PLC04 => SuffixLength::Two,
|
||||
@@ -3260,6 +3318,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::TID => SuffixLength::Zero,
|
||||
CheckCodePrefix::TID2 => SuffixLength::One,
|
||||
CheckCodePrefix::TID25 => SuffixLength::Two,
|
||||
CheckCodePrefix::TID251 => SuffixLength::Three,
|
||||
CheckCodePrefix::TID252 => SuffixLength::Three,
|
||||
CheckCodePrefix::U => SuffixLength::Zero,
|
||||
CheckCodePrefix::U0 => SuffixLength::One,
|
||||
@@ -3309,6 +3368,9 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::UP021 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP022 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP023 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP024 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP025 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP026 => SuffixLength::Three,
|
||||
CheckCodePrefix::W => SuffixLength::Zero,
|
||||
CheckCodePrefix::W2 => SuffixLength::One,
|
||||
CheckCodePrefix::W29 => SuffixLength::Two,
|
||||
@@ -3354,6 +3416,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
|
||||
CheckCodePrefix::FBT,
|
||||
CheckCodePrefix::I,
|
||||
CheckCodePrefix::ICN,
|
||||
CheckCodePrefix::ISC,
|
||||
CheckCodePrefix::N,
|
||||
CheckCodePrefix::PD,
|
||||
CheckCodePrefix::PGH,
|
||||
|
||||
144
src/cli.rs
144
src/cli.rs
@@ -17,7 +17,7 @@ use crate::settings::types::{
|
||||
#[command(version)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Cli {
|
||||
#[arg(required_unless_present_any = ["explain", "generate_shell_completion"])]
|
||||
#[arg(required_unless_present_any = ["clean", "explain", "generate_shell_completion"])]
|
||||
pub files: Vec<PathBuf>,
|
||||
/// Path to the `pyproject.toml` or `ruff.toml` file to use for
|
||||
/// configuration.
|
||||
@@ -57,14 +57,15 @@ pub struct Cli {
|
||||
/// Disable cache reads.
|
||||
#[arg(short, long)]
|
||||
pub no_cache: bool,
|
||||
/// List of error codes to enable.
|
||||
/// Comma-separated list of error codes to enable (or ALL, to enable all
|
||||
/// checks).
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub select: Option<Vec<CheckCodePrefix>>,
|
||||
/// Like --select, but adds additional error codes on top of the selected
|
||||
/// ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_select: Option<Vec<CheckCodePrefix>>,
|
||||
/// List of error codes to ignore.
|
||||
/// Comma-separated list of error codes to disable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub ignore: Option<Vec<CheckCodePrefix>>,
|
||||
/// Like --ignore, but adds additional error codes on top of the ignored
|
||||
@@ -92,6 +93,12 @@ pub struct Cli {
|
||||
/// Output serialization format for error messages.
|
||||
#[arg(long, value_enum)]
|
||||
pub format: Option<SerializationFormat>,
|
||||
/// The name of the file when passing it through stdin.
|
||||
#[arg(long)]
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
/// Path to the cache directory.
|
||||
#[arg(long)]
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
/// Show violations with source code.
|
||||
#[arg(long, overrides_with("no_show_source"))]
|
||||
show_source: bool,
|
||||
@@ -105,19 +112,15 @@ pub struct Cli {
|
||||
no_respect_gitignore: bool,
|
||||
/// Enforce exclusions, even for paths passed to Ruff directly on the
|
||||
/// command-line.
|
||||
#[arg(long, overrides_with("no_show_source"))]
|
||||
#[arg(long, overrides_with("no_force_exclude"))]
|
||||
force_exclude: bool,
|
||||
#[clap(long, overrides_with("force_exclude"), hide = true)]
|
||||
no_force_exclude: bool,
|
||||
/// See the files Ruff will be run against with the current settings.
|
||||
#[arg(long)]
|
||||
pub show_files: bool,
|
||||
/// See the settings Ruff will use to check a given Python file.
|
||||
#[arg(long)]
|
||||
pub show_settings: bool,
|
||||
/// Enable automatic additions of noqa directives to failing lines.
|
||||
#[arg(long)]
|
||||
pub add_noqa: bool,
|
||||
/// Enable or disable automatic update checks.
|
||||
#[arg(long, overrides_with("no_update_check"))]
|
||||
update_check: bool,
|
||||
#[clap(long, overrides_with("update_check"), hide = true)]
|
||||
no_update_check: bool,
|
||||
/// Regular expression matching the name of dummy variables.
|
||||
#[arg(long)]
|
||||
pub dummy_variable_rgx: Option<Regex>,
|
||||
@@ -128,25 +131,100 @@ pub struct Cli {
|
||||
/// formatting.
|
||||
#[arg(long)]
|
||||
pub line_length: Option<usize>,
|
||||
/// Max McCabe complexity allowed for a function.
|
||||
/// Maximum McCabe complexity allowed for a given function.
|
||||
#[arg(long)]
|
||||
pub max_complexity: Option<usize>,
|
||||
/// Round-trip auto-formatting.
|
||||
// TODO(charlie): This should be a sub-command.
|
||||
#[arg(long, hide = true)]
|
||||
pub autoformat: bool,
|
||||
/// The name of the file when passing it through stdin.
|
||||
#[arg(long)]
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
/// Enable automatic additions of `noqa` directives to failing lines.
|
||||
#[arg(
|
||||
long,
|
||||
// conflicts_with = "add_noqa",
|
||||
conflicts_with = "clean",
|
||||
conflicts_with = "explain",
|
||||
conflicts_with = "generate_shell_completion",
|
||||
conflicts_with = "show_files",
|
||||
conflicts_with = "show_settings",
|
||||
// Unsupported default-command arguments.
|
||||
conflicts_with = "stdin_filename",
|
||||
conflicts_with = "watch",
|
||||
)]
|
||||
pub add_noqa: bool,
|
||||
/// Clear any caches in the current directory or any subdirectories.
|
||||
#[arg(
|
||||
long,
|
||||
// Fake subcommands.
|
||||
conflicts_with = "add_noqa",
|
||||
// conflicts_with = "clean",
|
||||
conflicts_with = "explain",
|
||||
conflicts_with = "generate_shell_completion",
|
||||
conflicts_with = "show_files",
|
||||
conflicts_with = "show_settings",
|
||||
// Unsupported default-command arguments.
|
||||
conflicts_with = "stdin_filename",
|
||||
conflicts_with = "watch",
|
||||
)]
|
||||
pub clean: bool,
|
||||
/// Explain a rule.
|
||||
#[arg(long)]
|
||||
#[arg(
|
||||
long,
|
||||
// Fake subcommands.
|
||||
conflicts_with = "add_noqa",
|
||||
conflicts_with = "clean",
|
||||
// conflicts_with = "explain",
|
||||
conflicts_with = "generate_shell_completion",
|
||||
conflicts_with = "show_files",
|
||||
conflicts_with = "show_settings",
|
||||
// Unsupported default-command arguments.
|
||||
conflicts_with = "stdin_filename",
|
||||
conflicts_with = "watch",
|
||||
)]
|
||||
pub explain: Option<CheckCode>,
|
||||
/// Generate shell completion
|
||||
#[arg(long, hide = true, value_name = "SHELL")]
|
||||
#[arg(
|
||||
long,
|
||||
hide = true,
|
||||
value_name = "SHELL",
|
||||
// Fake subcommands.
|
||||
conflicts_with = "add_noqa",
|
||||
conflicts_with = "clean",
|
||||
conflicts_with = "explain",
|
||||
// conflicts_with = "generate_shell_completion",
|
||||
conflicts_with = "show_files",
|
||||
conflicts_with = "show_settings",
|
||||
// Unsupported default-command arguments.
|
||||
conflicts_with = "stdin_filename",
|
||||
conflicts_with = "watch",
|
||||
)]
|
||||
pub generate_shell_completion: Option<clap_complete_command::Shell>,
|
||||
/// Path to the cache directory.
|
||||
#[arg(long)]
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
/// See the files Ruff will be run against with the current settings.
|
||||
#[arg(
|
||||
long,
|
||||
// Fake subcommands.
|
||||
conflicts_with = "add_noqa",
|
||||
conflicts_with = "clean",
|
||||
conflicts_with = "explain",
|
||||
conflicts_with = "generate_shell_completion",
|
||||
// conflicts_with = "show_files",
|
||||
conflicts_with = "show_settings",
|
||||
// Unsupported default-command arguments.
|
||||
conflicts_with = "stdin_filename",
|
||||
conflicts_with = "watch",
|
||||
)]
|
||||
pub show_files: bool,
|
||||
/// See the settings Ruff will use to check a given Python file.
|
||||
#[arg(
|
||||
long,
|
||||
// Fake subcommands.
|
||||
conflicts_with = "add_noqa",
|
||||
conflicts_with = "clean",
|
||||
conflicts_with = "explain",
|
||||
conflicts_with = "generate_shell_completion",
|
||||
conflicts_with = "show_files",
|
||||
// conflicts_with = "show_settings",
|
||||
// Unsupported default-command arguments.
|
||||
conflicts_with = "stdin_filename",
|
||||
conflicts_with = "watch",
|
||||
)]
|
||||
pub show_settings: bool,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
@@ -156,7 +234,7 @@ impl Cli {
|
||||
(
|
||||
Arguments {
|
||||
add_noqa: self.add_noqa,
|
||||
autoformat: self.autoformat,
|
||||
clean: self.clean,
|
||||
config: self.config,
|
||||
diff: self.diff,
|
||||
exit_zero: self.exit_zero,
|
||||
@@ -192,11 +270,12 @@ impl Cli {
|
||||
target_version: self.target_version,
|
||||
unfixable: self.unfixable,
|
||||
// TODO(charlie): Included in `pyproject.toml`, but not inherited.
|
||||
cache_dir: self.cache_dir,
|
||||
fix: resolve_bool_arg(self.fix, self.no_fix),
|
||||
fix_only: resolve_bool_arg(self.fix_only, self.no_fix_only),
|
||||
format: self.format,
|
||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||
cache_dir: self.cache_dir,
|
||||
format: self.format,
|
||||
update_check: resolve_bool_arg(self.update_check, self.no_update_check),
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -216,7 +295,7 @@ fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Arguments {
|
||||
pub add_noqa: bool,
|
||||
pub autoformat: bool,
|
||||
pub clean: bool,
|
||||
pub config: Option<PathBuf>,
|
||||
pub diff: bool,
|
||||
pub exit_zero: bool,
|
||||
@@ -253,11 +332,12 @@ pub struct Overrides {
|
||||
pub target_version: Option<PythonVersion>,
|
||||
pub unfixable: Option<Vec<CheckCodePrefix>>,
|
||||
// TODO(charlie): Captured in pyproject.toml as a default, but not part of `Settings`.
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
pub fix: Option<bool>,
|
||||
pub fix_only: Option<bool>,
|
||||
pub format: Option<SerializationFormat>,
|
||||
pub force_exclude: Option<bool>,
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
pub format: Option<SerializationFormat>,
|
||||
pub update_check: Option<bool>,
|
||||
}
|
||||
|
||||
/// Map the CLI settings to a `LogLevel`.
|
||||
|
||||
@@ -1,26 +1,32 @@
|
||||
use std::fs::remove_dir_all;
|
||||
use std::io::{self, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use colored::Colorize;
|
||||
use ignore::Error;
|
||||
use itertools::Itertools;
|
||||
use log::{debug, error};
|
||||
use path_absolutize::path_dedot;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rayon::prelude::*;
|
||||
use rustpython_ast::Location;
|
||||
use serde::Serialize;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::cache::DEFAULT_CACHE_DIR_NAME;
|
||||
use crate::checks::{CheckCode, CheckKind};
|
||||
use crate::cli::Overrides;
|
||||
use crate::iterators::par_iter;
|
||||
use crate::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin, Diagnostics};
|
||||
use crate::linter::{add_noqa_to_path, lint_path, lint_stdin, Diagnostics};
|
||||
use crate::logging::LogLevel;
|
||||
use crate::message::Message;
|
||||
use crate::resolver::{FileDiscovery, PyprojectDiscovery};
|
||||
use crate::settings::flags;
|
||||
use crate::settings::types::SerializationFormat;
|
||||
use crate::{cache, packages, resolver};
|
||||
use crate::{cache, fs, one_time_warning, packages, resolver};
|
||||
|
||||
/// Run the linter over a collection of files.
|
||||
pub fn run(
|
||||
@@ -38,6 +44,15 @@ pub fn run(
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
if paths.is_empty() {
|
||||
one_time_warning!(
|
||||
"{} {}",
|
||||
"warning:".yellow().bold(),
|
||||
"No Python files found under the given path(s)"
|
||||
);
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
|
||||
// Validate the `Settings` and return any errors.
|
||||
resolver.validate(pyproject_strategy)?;
|
||||
|
||||
@@ -179,6 +194,15 @@ pub fn add_noqa(
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
if paths.is_empty() {
|
||||
one_time_warning!(
|
||||
"{} {}",
|
||||
"warning:".yellow().bold(),
|
||||
"No Python files found under the given path(s)"
|
||||
);
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Validate the `Settings` and return any errors.
|
||||
resolver.validate(pyproject_strategy)?;
|
||||
|
||||
@@ -204,45 +228,6 @@ pub fn add_noqa(
|
||||
Ok(modifications)
|
||||
}
|
||||
|
||||
/// Automatically format a collection of files.
|
||||
pub fn autoformat(
|
||||
files: &[PathBuf],
|
||||
pyproject_strategy: &PyprojectDiscovery,
|
||||
file_strategy: &FileDiscovery,
|
||||
overrides: &Overrides,
|
||||
) -> Result<usize> {
|
||||
// Collect all the files to format.
|
||||
let start = Instant::now();
|
||||
let (paths, resolver) =
|
||||
resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?;
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
// Validate the `Settings` and return any errors.
|
||||
resolver.validate(pyproject_strategy)?;
|
||||
|
||||
let start = Instant::now();
|
||||
let modifications = par_iter(&paths)
|
||||
.flatten()
|
||||
.filter_map(|entry| {
|
||||
let path = entry.path();
|
||||
let settings = resolver.resolve(path, pyproject_strategy);
|
||||
match autoformat_path(path, settings) {
|
||||
Ok(()) => Some(()),
|
||||
Err(e) => {
|
||||
error!("Failed to autoformat {}: {e}", path.to_string_lossy());
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.count();
|
||||
|
||||
let duration = start.elapsed();
|
||||
debug!("Auto-formatted files in: {:?}", duration);
|
||||
|
||||
Ok(modifications)
|
||||
}
|
||||
|
||||
/// Print the user-facing configuration settings.
|
||||
pub fn show_settings(
|
||||
files: &[PathBuf],
|
||||
@@ -283,6 +268,15 @@ pub fn show_files(
|
||||
let (paths, resolver) =
|
||||
resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?;
|
||||
|
||||
if paths.is_empty() {
|
||||
one_time_warning!(
|
||||
"{} {}",
|
||||
"warning:".yellow().bold(),
|
||||
"No Python files found under the given path(s)"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Validate the `Settings` and return any errors.
|
||||
resolver.validate(pyproject_strategy)?;
|
||||
|
||||
@@ -338,3 +332,21 @@ pub fn explain(code: &CheckCode, format: &SerializationFormat) -> Result<()> {
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clear any caches in the current directory or any subdirectories.
|
||||
pub fn clean(level: &LogLevel) -> Result<()> {
|
||||
for entry in WalkDir::new(&*path_dedot::CWD)
|
||||
.into_iter()
|
||||
.filter_map(std::result::Result::ok)
|
||||
.filter(|entry| entry.file_type().is_dir())
|
||||
{
|
||||
let cache = entry.path().join(DEFAULT_CACHE_DIR_NAME);
|
||||
if cache.is_dir() {
|
||||
if level >= &LogLevel::Default {
|
||||
eprintln!("Removing cache at: {}", fs::relativize_path(&cache).bold());
|
||||
}
|
||||
remove_dir_all(&cache)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::{bail, Result};
|
||||
use libcst_native::{Expr, Module, SmallStatement, Statement};
|
||||
use libcst_native::{Expr, Import, ImportFrom, Module, SmallStatement, Statement};
|
||||
|
||||
pub fn match_module(module_text: &str) -> Result<Module> {
|
||||
match libcst_native::parse_module(module_text, None) {
|
||||
@@ -19,3 +19,27 @@ pub fn match_expr<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Expr<'b>
|
||||
bail!("Expected Statement::Simple")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_import<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Import<'b>> {
|
||||
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
|
||||
if let Some(SmallStatement::Import(expr)) = expr.body.first_mut() {
|
||||
Ok(expr)
|
||||
} else {
|
||||
bail!("Expected SmallStatement::Expr")
|
||||
}
|
||||
} else {
|
||||
bail!("Expected Statement::Simple")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_import_from<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut ImportFrom<'b>> {
|
||||
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
|
||||
if let Some(SmallStatement::ImportFrom(expr)) = expr.body.first_mut() {
|
||||
Ok(expr)
|
||||
} else {
|
||||
bail!("Expected SmallStatement::Expr")
|
||||
}
|
||||
} else {
|
||||
bail!("Expected Statement::Simple")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,10 +110,7 @@ pub fn extract_isort_directives(lxr: &[LexResult], locator: &SourceCodeLocator)
|
||||
}
|
||||
|
||||
// TODO(charlie): Modify RustPython to include the comment text in the token.
|
||||
let comment_text = locator.slice_source_code_range(&Range {
|
||||
location: start,
|
||||
end_location: end,
|
||||
});
|
||||
let comment_text = locator.slice_source_code_range(&Range::new(start, end));
|
||||
|
||||
if comment_text == "# isort: split" {
|
||||
splits.push(start.row());
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/// See: <https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals>
|
||||
|
||||
pub const TRIPLE_QUOTE_PREFIXES: &[&str] = &[
|
||||
"ur\"\"\"", "ur'''", "u\"\"\"", "u'''", "r\"\"\"", "r'''", "UR\"\"\"", "UR'''", "Ur\"\"\"",
|
||||
"Ur'''", "U\"\"\"", "U'''", "uR\"\"\"", "uR'''", "R\"\"\"", "R'''", "\"\"\"", "'''",
|
||||
"u\"\"\"", "u'''", "r\"\"\"", "r'''", "U\"\"\"", "U'''", "R\"\"\"", "R'''", "\"\"\"", "'''",
|
||||
];
|
||||
|
||||
pub const SINGLE_QUOTE_PREFIXES: &[&str] = &[
|
||||
"ur\"", "ur'", "u\"", "u'", "r\"", "r'", "ur\"", "ur'", "u\"", "u'", "r\"", "r'", "UR\"",
|
||||
"UR'", "Ur\"", "Ur'", "U\"", "U'", "uR\"", "uR'", "R\"", "R'", "\"", "'",
|
||||
"u\"", "u'", "r\"", "r'", "u\"", "u'", "r\"", "r'", "U\"", "U'", "R\"", "R'", "\"", "'",
|
||||
];
|
||||
|
||||
@@ -5,21 +5,21 @@ use crate::docstrings::google::{GOOGLE_SECTION_NAMES, LOWERCASE_GOOGLE_SECTION_N
|
||||
use crate::docstrings::numpy::{LOWERCASE_NUMPY_SECTION_NAMES, NUMPY_SECTION_NAMES};
|
||||
|
||||
pub(crate) enum SectionStyle {
|
||||
NumPy,
|
||||
Numpy,
|
||||
Google,
|
||||
}
|
||||
|
||||
impl SectionStyle {
|
||||
pub(crate) fn section_names(&self) -> &Lazy<FxHashSet<&'static str>> {
|
||||
match self {
|
||||
SectionStyle::NumPy => &NUMPY_SECTION_NAMES,
|
||||
SectionStyle::Numpy => &NUMPY_SECTION_NAMES,
|
||||
SectionStyle::Google => &GOOGLE_SECTION_NAMES,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lowercase_section_names(&self) -> &Lazy<FxHashSet<&'static str>> {
|
||||
match self {
|
||||
SectionStyle::NumPy => &LOWERCASE_NUMPY_SECTION_NAMES,
|
||||
SectionStyle::Numpy => &LOWERCASE_NUMPY_SECTION_NAMES,
|
||||
SectionStyle::Google => &LOWERCASE_GOOGLE_SECTION_NAMES,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,20 +28,11 @@ pub fn commented_out_code(
|
||||
) -> Option<Check> {
|
||||
let location = Location::new(start.row(), 0);
|
||||
let end_location = Location::new(end.row() + 1, 0);
|
||||
let line = locator.slice_source_code_range(&Range {
|
||||
location,
|
||||
end_location,
|
||||
});
|
||||
let line = locator.slice_source_code_range(&Range::new(location, end_location));
|
||||
|
||||
// Verify that the comment is on its own line, and that it contains code.
|
||||
if is_standalone_comment(&line) && comment_contains_code(&line) {
|
||||
let mut check = Check::new(
|
||||
CheckKind::CommentedOutCode,
|
||||
Range {
|
||||
location: start,
|
||||
end_location: end,
|
||||
},
|
||||
);
|
||||
let mut check = Check::new(CheckKind::CommentedOutCode, Range::new(start, end));
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.fixable.contains(&CheckCode::ERA001)
|
||||
{
|
||||
|
||||
@@ -16,13 +16,12 @@ mod tests {
|
||||
#[test_case(CheckCode::ERA001, Path::new("ERA001.py"); "ERA001")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let mut checks = test_path(
|
||||
let checks = test_path(
|
||||
Path::new("./resources/test/fixtures/eradicate")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ expression: checks
|
||||
end_location:
|
||||
row: 2
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind: CommentedOutCode
|
||||
location:
|
||||
row: 2
|
||||
@@ -32,6 +33,7 @@ expression: checks
|
||||
end_location:
|
||||
row: 3
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind: CommentedOutCode
|
||||
location:
|
||||
row: 3
|
||||
@@ -47,6 +49,7 @@ expression: checks
|
||||
end_location:
|
||||
row: 4
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind: CommentedOutCode
|
||||
location:
|
||||
row: 5
|
||||
@@ -62,6 +65,7 @@ expression: checks
|
||||
end_location:
|
||||
row: 6
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind: CommentedOutCode
|
||||
location:
|
||||
row: 12
|
||||
@@ -77,4 +81,5 @@ expression: checks
|
||||
end_location:
|
||||
row: 13
|
||||
column: 0
|
||||
parent: ~
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user