Compare commits

...

59 Commits

Author SHA1 Message Date
Charlie Marsh
0e8c237167 Bump version to 0.0.205 2022-12-31 13:44:39 -05:00
Harutaka Kawamura
960c5e2006 Use more precise error ranges for names (#1513) 2022-12-31 13:42:39 -05:00
Charlie Marsh
9ba17fbf92 Avoid flagging nested f-strings (#1516) 2022-12-31 13:41:21 -05:00
Charlie Marsh
bfdf972a5d Add code kind to Quick Fix action 2022-12-31 10:26:47 -05:00
Charlie Marsh
0c215365ae Bump version to 0.0.204 2022-12-31 08:20:09 -05:00
Maksudul Haque
815284f890 Check for Unsupported Files and Display Errors and Warnings (#1509) 2022-12-31 08:12:55 -05:00
Charlie Marsh
6880338a9a Restore pyproject.toml 2022-12-31 08:06:08 -05:00
Charlie Marsh
68b749c67d Remove foo directory 2022-12-31 08:05:04 -05:00
Reiner Gerecke
c0fc55b812 Generate source code with detected line ending (#1487) 2022-12-31 08:02:29 -05:00
Reiner Gerecke
ba9cf70917 Adjust test_path helper to detect round-trip autofix issues (#1501) 2022-12-31 08:02:13 -05:00
Harutaka Kawamura
f73dfbbfd3 Fix E722 and F707 ranges (#1508) 2022-12-31 07:58:46 -05:00
Reiner Gerecke
62c273cd22 Include fix commit message when showing violations together with source (#1505) 2022-12-31 07:54:41 -05:00
Harutaka Kawamura
938ad9a39e Fix N818 range (#1503) 2022-12-31 07:43:03 -05:00
Harutaka Kawamura
14248cb8cb Improve PLW0120 range (#1500) 2022-12-31 07:42:49 -05:00
Harutaka Kawamura
3a280039e1 Improve F811 range for function and class definitions (#1499) 2022-12-31 07:42:18 -05:00
Harutaka Kawamura
4e9e58bdc0 Improve T20X ranges (#1502) 2022-12-31 07:41:53 -05:00
Harutaka Kawamura
926b5494ad Remove unused snapshots (#1497) 2022-12-31 07:40:38 -05:00
Reiner Gerecke
6717b48ca5 Fix detection of changed imports in isort plugin (#1504) 2022-12-31 07:37:27 -05:00
Harutaka Kawamura
f7bb5bc858 Remove F831 (#1495) 2022-12-30 23:57:51 -05:00
Harutaka Kawamura
3e23fd1487 Stop overriding locations for expressions within f-strings (#1494) 2022-12-30 23:43:59 -05:00
Charlie Marsh
01c74e0629 Add a "fix message" to every autofix-able check (#1489) 2022-12-30 23:16:03 -05:00
Charlie Marsh
1e3cf87f67 Escape strings when formatting check messages (#1493) 2022-12-30 22:11:01 -05:00
Charlie Marsh
248447e139 Trim CLI help during generation (#1492) 2022-12-30 22:03:58 -05:00
Charlie Marsh
95f139583a Modify pyproject.toml to meet schema compliance 2022-12-30 15:51:35 -05:00
Charlie Marsh
74903f23d6 Bump version to 0.0.203 2022-12-30 15:33:30 -05:00
Charlie Marsh
3ee20a70d3 Remove lingering ruff_options.ts references 2022-12-30 15:33:09 -05:00
Charlie Marsh
4c2fbb7ac0 Remove hidden autoformat command (#1486) 2022-12-30 15:32:05 -05:00
Charlie Marsh
7a66f98590 Move some argument validation into Clap (#1485) 2022-12-30 15:29:41 -05:00
Charlie Marsh
a2bf3916f3 Make clean a standalone command 2022-12-30 15:20:18 -05:00
Reiner Gerecke
c9aa7b9308 Generate the README's --help output automatically via cargo +nightly dev generate-all (#1483) 2022-12-30 15:06:32 -05:00
Charlie Marsh
d880ca6cc6 Add a command to clear the Ruff cache (#1484) 2022-12-30 13:52:16 -05:00
Reiner Gerecke
080f99b908 Detect line endings and use them during code generation (#1482) 2022-12-30 12:59:40 -05:00
Charlie Marsh
a7dc491ff1 Fix clippy 2022-12-30 12:34:43 -05:00
Charlie Marsh
cdc8f8c91a Remove support for ur prefixes (#1481) 2022-12-30 11:21:05 -05:00
Colin Delahunty
138c46e793 Simplified code for unicode fix (#1475) 2022-12-30 11:18:52 -05:00
Charlie Marsh
a86c57a832 Support multi-line noqa directives for 'import from' (#1479) 2022-12-30 11:16:50 -05:00
Charlie Marsh
818582fe8a Bump version to 0.0.202 2022-12-30 08:16:32 -05:00
Charlie Marsh
90574c1088 Set editor background on top-level component (#1478) 2022-12-30 07:55:54 -05:00
Charlie Marsh
3061a35e7c Use more precise ranges for class and function checks (#1476) 2022-12-30 07:39:20 -05:00
Martin Fischer
87681697ae Improve CLI help for --select (#1471) 2022-12-30 07:16:44 -05:00
Martin Fischer
e9ec2a7b36 Add use test_case::test_case; to CONTRIBUTING.md
I previously tried adding the #[test_case()] attribute macro and got
confused because the Rust compilation suddenly failed with:

    error[E0658]: use of unstable library feature 'custom_test_frameworks': custom test frameworks are an unstable feature

which is a quite confusing error message.  The solution is to just add
`use test_case::test_case;`, so this commit adds that line to the
example in CONTRIBUTING.md to spare others this source of confusion.
2022-12-30 07:13:48 -05:00
Martin Fischer
b0bb75dc1c Add --select to command suggested in CONTRIBUTING.md
By default only E* and F* lints are enabled. I previously followed the
CONTRIBUTING.md instructions to implement a TID* lint and was confused
why my lint wasn't being run.
2022-12-30 07:13:48 -05:00
Martin Fischer
ebca5c2df8 Make banned-api config setting optional (#1465) 2022-12-30 07:09:56 -05:00
Charlie Marsh
16b10c42f0 Fix lint issues 2022-12-29 23:12:28 -05:00
Charlie Marsh
4a6e5d1549 Bump version to 0.0.201 2022-12-29 23:01:35 -05:00
Charlie Marsh
b078050732 Implicit flake8-implicit-str-concat (#1463) 2022-12-29 23:00:55 -05:00
Charlie Marsh
5f796b39b4 Run cargo fmt 2022-12-29 22:25:14 -05:00
Martin Fischer
9d34da23bd Implement TID251 (banning modules & module members) (#1436) 2022-12-29 22:11:12 -05:00
Charlie Marsh
bde12c3bb3 Restore quick fixes for playground 2022-12-29 20:16:19 -05:00
Colin Delahunty
f735660801 Removed unicode literals (#1448) 2022-12-29 20:11:33 -05:00
Charlie Marsh
34cd22dfc1 Copy URL but don't update the hash (#1458) 2022-12-29 19:46:50 -05:00
Charlie Marsh
9fafe16a55 Re-add GitHub badge to the bottom of the page 2022-12-29 19:38:33 -05:00
Charlie Marsh
e9a4cb1c1d Remove generated TypeScript options (#1456) 2022-12-29 19:37:49 -05:00
Charlie Marsh
9db825c731 Use trailingComma: 'all' (#1457) 2022-12-29 19:36:51 -05:00
Charlie Marsh
2c7464604a Implement dark mode (#1455) 2022-12-29 19:33:46 -05:00
Charlie Marsh
cd2099f772 Move default options into WASM interface (#1453) 2022-12-29 18:06:57 -05:00
Adam Turner
091d36cd30 Add Sphinx to user list (#1451) 2022-12-29 18:06:09 -05:00
Mathieu Kniewallner
02f156c6cb docs(README): add missing flake8-simplify (#1449) 2022-12-29 17:02:26 -05:00
Charlie Marsh
9f7350961e Rename config to settings in the playground (#1450) 2022-12-29 16:59:38 -05:00
568 changed files with 6337 additions and 2740 deletions

View File

@@ -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"
@@ -118,7 +117,17 @@ jobs:
${{ runner.os }}-build-
${{ runner.os }}-
- run: pip install black[d]==22.12.0
- run: cargo test --all
- name: Run tests
run: |
export INSTA_SNAPSHOT_REFERENCES_FILE="$(mktemp)"
cargo test --all
USED_SNAPSHOTS=$(cat $INSTA_SNAPSHOT_REFERENCES_FILE | xargs realpath --relative-to $PWD)
UNUSED_SNAPSHOTS=$(cat <(git ls-files '*.snap') <(echo "$USED_SNAPSHOTS") | sort | uniq -u)
if [ ! -z "$UNUSED_SNAPSHOTS" ]; then
echo "Found unused snapshots:"
echo "$UNUSED_SNAPSHOTS"
exit 1
fi
- run: cargo test --package ruff --test black_compatibility_test -- --ignored
# TODO(charlie): Re-enable the `wasm-pack` tests.

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.200
rev: v0.0.205
hooks:
- id: ruff

View File

@@ -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
View File

@@ -750,7 +750,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.200-dev.0"
version = "0.0.205-dev.0"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -1878,7 +1878,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.200"
version = "0.0.205"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1946,7 +1946,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.200"
version = "0.0.205"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -1967,7 +1967,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.200"
version = "0.0.205"
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=8cb2b8292062adf13bde1b863a9b02c9f0bda3dd#8cb2b8292062adf13bde1b863a9b02c9f0bda3dd"
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=8cb2b8292062adf13bde1b863a9b02c9f0bda3dd#8cb2b8292062adf13bde1b863a9b02c9f0bda3dd"
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=8cb2b8292062adf13bde1b863a9b02c9f0bda3dd#8cb2b8292062adf13bde1b863a9b02c9f0bda3dd"
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=8cb2b8292062adf13bde1b863a9b02c9f0bda3dd#8cb2b8292062adf13bde1b863a9b02c9f0bda3dd"
dependencies = [
"ahash",
"anyhow",

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.200"
version = "0.0.205"
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.200", path = "ruff_macros" }
ruff_macros = { version = "0.0.205", 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 = "8cb2b8292062adf13bde1b863a9b02c9f0bda3dd" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "8cb2b8292062adf13bde1b863a9b02c9f0bda3dd" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "8cb2b8292062adf13bde1b863a9b02c9f0bda3dd" }
schemars = { version = "0.8.11" }
semver = { version = "1.0.16" }
serde = { version = "1.0.147", features = ["derive"] }

25
LICENSE
View File

@@ -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

View File

@@ -44,6 +44,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 +95,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 +169,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.200'
rev: 'v0.0.205'
hooks:
- id: ruff
# Respect `exclude` and `extend-exclude` settings.
@@ -298,6 +300,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 +331,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,6 +350,10 @@ 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
@@ -355,12 +362,6 @@ Options:
Enforce exclusions, even for paths passed to Ruff directly on the command-line
--update-check
Enable or disable automatic update checks
--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
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
Regular expression matching the name of dummy variables
--target-version <TARGET_VERSION>
@@ -368,18 +369,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
@@ -539,7 +545,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 | |
@@ -552,7 +558,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` | 🛠 |
@@ -572,7 +577,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: `...` | |
@@ -657,7 +662,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 | 🛠 |
@@ -671,12 +676,13 @@ 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` | 🛠 |
| UP025 | RewriteUnicodeLiteral | Remove unicode literals from strings | 🛠 |
### pep8-naming (N)
@@ -852,6 +858,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 |
@@ -907,6 +923,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)
@@ -1000,7 +1017,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 | 🛠 |
@@ -1271,10 +1288,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/)
@@ -1326,10 +1345,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/)
@@ -1651,7 +1672,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
@@ -2477,6 +2497,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)

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.200"
version = "0.0.205"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.200"
version = "0.0.205"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.200-dev.0"
version = "0.0.205-dev.0"
edition = "2021"
[lib]

View File

@@ -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()) {

View File

@@ -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,

View File

@@ -1 +0,0 @@
src/ruff_options.ts

View File

@@ -0,0 +1,3 @@
{
"trailingComma": "all"
}

View File

@@ -17,6 +17,14 @@
</head>
<body>
<div id="root"></div>
<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"
alt="GitHub stars"
style="width: 120px"
/></a>
</div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -1,12 +1,13 @@
import { useCallback, useEffect, useState } from "react";
import { persist, restore } from "./config";
import { DEFAULT_CONFIG_SOURCE, DEFAULT_PYTHON_SOURCE } from "../constants";
import { DEFAULT_PYTHON_SOURCE } from "../constants";
import init, { check, Check, currentVersion, defaultSettings } from "../pkg";
import { ErrorMessage } from "./ErrorMessage";
import Header from "./Header";
import init, { check, current_version, Check } from "../pkg";
import { useTheme } from "./theme";
import { persist, restore, stringify } from "./settings";
import SettingsEditor from "./SettingsEditor";
import SourceEditor from "./SourceEditor";
import Themes from "./Themes";
import MonacoThemes from "./MonacoThemes";
type Tab = "Source" | "Settings";
@@ -15,17 +16,18 @@ export default function Editor() {
const [version, setVersion] = useState<string | null>(null);
const [tab, setTab] = useState<Tab>("Source");
const [edit, setEdit] = useState<number>(0);
const [configSource, setConfigSource] = useState<string | null>(null);
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 || configSource == null || pythonSource == null) {
if (!initialized || settingsSource == null || pythonSource == null) {
return;
}
@@ -33,7 +35,7 @@ export default function Editor() {
let checks: Check[];
try {
config = JSON.parse(configSource);
config = JSON.parse(settingsSource);
} catch (e) {
setChecks([]);
setError((e as Error).message);
@@ -49,73 +51,85 @@ export default function Editor() {
setError(null);
setChecks(checks);
}, [initialized, configSource, pythonSource]);
useEffect(() => {
if (configSource == null || pythonSource == null) {
const payload = restore();
if (payload) {
const [configSource, pythonSource] = payload;
setConfigSource(configSource);
setPythonSource(pythonSource);
} else {
setConfigSource(DEFAULT_CONFIG_SOURCE);
setPythonSource(DEFAULT_PYTHON_SOURCE);
}
}
}, [configSource, pythonSource]);
}, [initialized, settingsSource, pythonSource]);
useEffect(() => {
if (!initialized) {
return;
}
setVersion(current_version());
}, [initialized]);
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]);
const handleShare = useCallback(() => {
if (!initialized || configSource == null || pythonSource == null) {
useEffect(() => {
if (!initialized) {
return;
}
persist(configSource, pythonSource);
}, [initialized, configSource, pythonSource]);
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 handleConfigSourceChange = useCallback((configSource: string) => {
const handleSettingsSourceChange = useCallback((settingsSource: string) => {
setEdit((edit) => edit + 1);
setConfigSource(configSource);
setSettingsSource(settingsSource);
}, []);
return (
<main className={"h-full w-full flex flex-auto"}>
<main
className={
"h-full w-full flex flex-auto bg-ayu-background dark:bg-ayu-background-dark"
}
>
<Header
edit={edit}
version={version}
tab={tab}
onChange={setTab}
theme={theme}
version={version}
onChangeTab={setTab}
onChangeTheme={setTheme}
onShare={initialized ? handleShare : undefined}
/>
<Themes />
<MonacoThemes />
<div className={"mt-12 relative flex-auto"}>
{initialized && configSource != null && pythonSource != null ? (
{initialized && settingsSource != null && pythonSource != null ? (
<>
<SourceEditor
visible={tab === "Source"}
source={pythonSource}
theme={theme}
checks={checks}
onChange={handlePythonSourceChange}
/>
<SettingsEditor
visible={tab === "Settings"}
source={configSource}
onChange={handleConfigSourceChange}
source={settingsSource}
theme={theme}
onChange={handleSettingsSourceChange}
/>
</>
) : null}

View File

@@ -18,7 +18,7 @@ export function ErrorMessage({ children }: { children: string }) {
children.startsWith("Error: ")
? children.slice("Error: ".length)
: children,
120
120,
)}
</p>
</div>

View File

@@ -1,26 +1,50 @@
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,
version,
tab,
onChange,
theme,
version,
onChangeTab,
onChangeTheme,
onShare,
}: {
edit: number;
version: string | null;
tab: Tab;
onChange: (tab: Tab) => void;
theme: Theme;
version: string | null;
onChangeTab: (tab: Tab) => void;
onChangeTheme: (theme: Theme) => void;
onShare?: () => void;
}) {
return (
<div
className="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"
style={{ background: "#f8f9fa" }}
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
@@ -28,15 +52,15 @@ export default function Header({
className={classNames(
"relative flex py-3 text-sm leading-6 font-semibold focus:outline-none",
tab === "Source"
? "text-ayu"
: "text-gray-700 hover:text-gray-900 focus:text-gray-900 dark:text-gray-300 dark:hover:text-white"
? "text-ayu-accent"
: "text-gray-700 hover:text-gray-900 focus:text-gray-900 dark:text-gray-300 dark:hover:text-white",
)}
onClick={() => onChange("Source")}
onClick={() => onChangeTab("Source")}
>
<span
className={classNames(
"absolute bottom-0 inset-x-0 bg-ayu h-0.5 rounded-full transition-opacity duration-150",
tab === "Source" ? "opacity-100" : "opacity-0"
"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
@@ -46,15 +70,15 @@ export default function Header({
className={classNames(
"relative flex py-3 text-sm leading-6 font-semibold focus:outline-none",
tab === "Settings"
? "text-ayu"
: "text-gray-700 hover:text-gray-900 focus:text-gray-900 dark:text-gray-300 dark:hover:text-white"
? "text-ayu-accent"
: "text-gray-700 hover:text-gray-900 focus:text-gray-900 dark:text-gray-300 dark:hover:text-white",
)}
onClick={() => onChange("Settings")}
onClick={() => onChangeTab("Settings")}
>
<span
className={classNames(
"absolute bottom-0 inset-x-0 bg-ayu h-0.5 rounded-full transition-opacity duration-150",
tab === "Settings" ? "opacity-100" : "opacity-0"
"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
@@ -67,6 +91,8 @@ export default function Header({
</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>
);

View File

@@ -1,7 +1,11 @@
/**
* Non-rendering component that loads the Monaco editor themes.
*/
import { useMonaco } from "@monaco-editor/react";
import { useEffect } from "react";
export default function Themes() {
export default function MonacoThemes() {
const monaco = useMonaco();
useEffect(() => {
@@ -631,7 +635,642 @@ export default function Themes() {
foreground: "#787b8099",
token: "punctuation.definition.markdown",
},
// Edits.
// Manual changes.
{
foreground: "#fa8d3e",
token: "number",
},
],
encodedTokensColors: [],
});
// Generated via `monaco-vscode-textmate-theme-converter`.
// See: https://github.com/ayu-theme/vscode-ayu/blob/91839e8a9dfa78d61e58dbcf9b52272a01fee66a/ayu-dark.json.
monaco?.editor.defineTheme("Ayu-Dark", {
inherit: false,
base: "vs-dark",
colors: {
focusBorder: "#e6b450b3",
foreground: "#565b66",
"widget.shadow": "#00000080",
"selection.background": "#409fff4d",
"icon.foreground": "#565b66",
errorForeground: "#d95757",
descriptionForeground: "#565b66",
"textBlockQuote.background": "#0f131a",
"textLink.foreground": "#e6b450",
"textLink.activeForeground": "#e6b450",
"textPreformat.foreground": "#bfbdb6",
"button.background": "#e6b450",
"button.foreground": "#0b0e14",
"button.hoverBackground": "#e1af4b",
"button.secondaryBackground": "#565b6633",
"button.secondaryForeground": "#bfbdb6",
"button.secondaryHoverBackground": "#565b6680",
"dropdown.background": "#0d1017",
"dropdown.foreground": "#565b66",
"dropdown.border": "#565b6645",
"input.background": "#0d1017",
"input.border": "#565b6645",
"input.foreground": "#bfbdb6",
"input.placeholderForeground": "#565b6680",
"inputOption.activeBorder": "#e6b4504d",
"inputOption.activeBackground": "#e6b45033",
"inputOption.activeForeground": "#e6b450",
"inputValidation.errorBackground": "#0d1017",
"inputValidation.errorBorder": "#d95757",
"inputValidation.infoBackground": "#0b0e14",
"inputValidation.infoBorder": "#39bae6",
"inputValidation.warningBackground": "#0b0e14",
"inputValidation.warningBorder": "#ffb454",
"scrollbar.shadow": "#11151c00",
"scrollbarSlider.background": "#565b6666",
"scrollbarSlider.hoverBackground": "#565b6699",
"scrollbarSlider.activeBackground": "#565b66b3",
"badge.background": "#e6b45033",
"badge.foreground": "#e6b450",
"progressBar.background": "#e6b450",
"list.activeSelectionBackground": "#47526640",
"list.activeSelectionForeground": "#bfbdb6",
"list.focusBackground": "#47526640",
"list.focusForeground": "#bfbdb6",
"list.focusOutline": "#47526640",
"list.highlightForeground": "#e6b450",
"list.deemphasizedForeground": "#d95757",
"list.hoverBackground": "#47526640",
"list.inactiveSelectionBackground": "#47526633",
"list.inactiveSelectionForeground": "#565b66",
"list.invalidItemForeground": "#565b664d",
"list.errorForeground": "#d95757",
"tree.indentGuidesStroke": "#6c738080",
"listFilterWidget.background": "#0f131a",
"listFilterWidget.outline": "#e6b450",
"listFilterWidget.noMatchesOutline": "#d95757",
"list.filterMatchBackground": "#5f4c7266",
"list.filterMatchBorder": "#6c598066",
"activityBar.background": "#0b0e14",
"activityBar.foreground": "#565b66cc",
"activityBar.inactiveForeground": "#565b6699",
"activityBar.border": "#0b0e14",
"activityBar.activeBorder": "#e6b450b3",
"activityBarBadge.background": "#e6b450",
"activityBarBadge.foreground": "#0b0e14",
"sideBar.background": "#0b0e14",
"sideBar.border": "#0b0e14",
"sideBarTitle.foreground": "#565b66",
"sideBarSectionHeader.background": "#0b0e14",
"sideBarSectionHeader.foreground": "#565b66",
"sideBarSectionHeader.border": "#0b0e14",
"minimap.background": "#0b0e14",
"minimap.selectionHighlight": "#409fff4d",
"minimap.errorHighlight": "#d95757",
"minimap.findMatchHighlight": "#6c5980",
"minimapGutter.addedBackground": "#7fd962",
"minimapGutter.modifiedBackground": "#73b8ff",
"minimapGutter.deletedBackground": "#f26d78",
"editorGroup.border": "#11151c",
"editorGroup.background": "#0f131a",
"editorGroupHeader.noTabsBackground": "#0b0e14",
"editorGroupHeader.tabsBackground": "#0b0e14",
"editorGroupHeader.tabsBorder": "#0b0e14",
"tab.activeBackground": "#0b0e14",
"tab.activeForeground": "#bfbdb6",
"tab.border": "#0b0e14",
"tab.activeBorder": "#e6b450",
"tab.unfocusedActiveBorder": "#565b66",
"tab.inactiveBackground": "#0b0e14",
"tab.inactiveForeground": "#565b66",
"tab.unfocusedActiveForeground": "#565b66",
"tab.unfocusedInactiveForeground": "#565b66",
"editor.background": "#0b0e14",
"editor.foreground": "#bfbdb6",
"editorLineNumber.foreground": "#6c738099",
"editorLineNumber.activeForeground": "#6c7380e6",
"editorCursor.foreground": "#e6b450",
"editor.inactiveSelectionBackground": "#409fff21",
"editor.selectionBackground": "#409fff4d",
"editor.selectionHighlightBackground": "#7fd96226",
"editor.selectionHighlightBorder": "#7fd96200",
"editor.wordHighlightBackground": "#73b8ff14",
"editor.wordHighlightStrongBackground": "#7fd96214",
"editor.wordHighlightBorder": "#73b8ff80",
"editor.wordHighlightStrongBorder": "#7fd96280",
"editor.findMatchBackground": "#6c5980",
"editor.findMatchBorder": "#6c5980",
"editor.findMatchHighlightBackground": "#6c598066",
"editor.findMatchHighlightBorder": "#5f4c7266",
"editor.findRangeHighlightBackground": "#6c598040",
"editor.rangeHighlightBackground": "#6c598033",
"editor.lineHighlightBackground": "#131721",
"editorLink.activeForeground": "#e6b450",
"editorWhitespace.foreground": "#6c738099",
"editorIndentGuide.background": "#6c738033",
"editorIndentGuide.activeBackground": "#6c738080",
"editorRuler.foreground": "#6c738033",
"editorCodeLens.foreground": "#acb6bf8c",
"editorBracketMatch.background": "#6c73804d",
"editorBracketMatch.border": "#6c73804d",
"editor.snippetTabstopHighlightBackground": "#7fd96233",
"editorOverviewRuler.border": "#11151c",
"editorOverviewRuler.modifiedForeground": "#73b8ff",
"editorOverviewRuler.addedForeground": "#7fd962",
"editorOverviewRuler.deletedForeground": "#f26d78",
"editorOverviewRuler.errorForeground": "#d95757",
"editorOverviewRuler.warningForeground": "#e6b450",
"editorOverviewRuler.bracketMatchForeground": "#6c7380b3",
"editorOverviewRuler.wordHighlightForeground": "#73b8ff66",
"editorOverviewRuler.wordHighlightStrongForeground": "#7fd96266",
"editorOverviewRuler.findMatchForeground": "#6c5980",
"editorError.foreground": "#d95757",
"editorWarning.foreground": "#e6b450",
"editorGutter.modifiedBackground": "#73b8ffcc",
"editorGutter.addedBackground": "#7fd962cc",
"editorGutter.deletedBackground": "#f26d78cc",
"diffEditor.insertedTextBackground": "#7fd9621f",
"diffEditor.removedTextBackground": "#f26d781f",
"diffEditor.diagonalFill": "#11151c",
"editorWidget.background": "#0f131a",
"editorWidget.border": "#11151c",
"editorHoverWidget.background": "#0f131a",
"editorHoverWidget.border": "#11151c",
"editorSuggestWidget.background": "#0f131a",
"editorSuggestWidget.border": "#11151c",
"editorSuggestWidget.highlightForeground": "#e6b450",
"editorSuggestWidget.selectedBackground": "#47526640",
"debugExceptionWidget.border": "#11151c",
"debugExceptionWidget.background": "#0f131a",
"editorMarkerNavigation.background": "#0f131a",
"peekView.border": "#47526640",
"peekViewTitle.background": "#47526640",
"peekViewTitleDescription.foreground": "#565b66",
"peekViewTitleLabel.foreground": "#bfbdb6",
"peekViewEditor.background": "#0f131a",
"peekViewEditor.matchHighlightBackground": "#6c598066",
"peekViewEditor.matchHighlightBorder": "#5f4c7266",
"peekViewResult.background": "#0f131a",
"peekViewResult.fileForeground": "#bfbdb6",
"peekViewResult.lineForeground": "#565b66",
"peekViewResult.matchHighlightBackground": "#6c598066",
"peekViewResult.selectionBackground": "#47526640",
"panel.background": "#0b0e14",
"panel.border": "#11151c",
"panelTitle.activeBorder": "#e6b450",
"panelTitle.activeForeground": "#bfbdb6",
"panelTitle.inactiveForeground": "#565b66",
"statusBar.background": "#0b0e14",
"statusBar.foreground": "#565b66",
"statusBar.border": "#0b0e14",
"statusBar.debuggingBackground": "#f29668",
"statusBar.debuggingForeground": "#0d1017",
"statusBar.noFolderBackground": "#0f131a",
"statusBarItem.activeBackground": "#565b6633",
"statusBarItem.hoverBackground": "#565b6633",
"statusBarItem.prominentBackground": "#11151c",
"statusBarItem.prominentHoverBackground": "#00000030",
"statusBarItem.remoteBackground": "#e6b450",
"statusBarItem.remoteForeground": "#0d1017",
"titleBar.activeBackground": "#0b0e14",
"titleBar.activeForeground": "#bfbdb6",
"titleBar.inactiveBackground": "#0b0e14",
"titleBar.inactiveForeground": "#565b66",
"titleBar.border": "#0b0e14",
"extensionButton.prominentForeground": "#0d1017",
"extensionButton.prominentBackground": "#e6b450",
"extensionButton.prominentHoverBackground": "#e1af4b",
"pickerGroup.border": "#11151c",
"pickerGroup.foreground": "#565b6680",
"debugToolBar.background": "#0f131a",
"debugIcon.breakpointForeground": "#f29668",
"debugIcon.breakpointDisabledForeground": "#f2966880",
"debugConsoleInputIcon.foreground": "#e6b450",
"welcomePage.tileBackground": "#0b0e14",
"welcomePage.tileShadow": "#00000080",
"welcomePage.progress.background": "#131721",
"welcomePage.buttonBackground": "#e6b45066",
"walkThrough.embeddedEditorBackground": "#0f131a",
"gitDecoration.modifiedResourceForeground": "#73b8ffb3",
"gitDecoration.deletedResourceForeground": "#f26d78b3",
"gitDecoration.untrackedResourceForeground": "#7fd962b3",
"gitDecoration.ignoredResourceForeground": "#565b6680",
"gitDecoration.conflictingResourceForeground": "",
"gitDecoration.submoduleResourceForeground": "#d2a6ffb3",
"settings.headerForeground": "#bfbdb6",
"settings.modifiedItemIndicator": "#73b8ff",
"keybindingLabel.background": "#565b661a",
"keybindingLabel.foreground": "#bfbdb6",
"keybindingLabel.border": "#bfbdb61a",
"keybindingLabel.bottomBorder": "#bfbdb61a",
"terminal.background": "#0b0e14",
"terminal.foreground": "#bfbdb6",
"terminal.ansiBlack": "#11151c",
"terminal.ansiRed": "#ea6c73",
"terminal.ansiGreen": "#7fd962",
"terminal.ansiYellow": "#f9af4f",
"terminal.ansiBlue": "#53bdfa",
"terminal.ansiMagenta": "#cda1fa",
"terminal.ansiCyan": "#90e1c6",
"terminal.ansiWhite": "#c7c7c7",
"terminal.ansiBrightBlack": "#686868",
"terminal.ansiBrightRed": "#f07178",
"terminal.ansiBrightGreen": "#aad94c",
"terminal.ansiBrightYellow": "#ffb454",
"terminal.ansiBrightBlue": "#59c2ff",
"terminal.ansiBrightMagenta": "#d2a6ff",
"terminal.ansiBrightCyan": "#95e6cb",
"terminal.ansiBrightWhite": "#ffffff",
},
rules: [
{
fontStyle: "italic",
foreground: "#acb6bf8c",
token: "comment",
},
{
foreground: "#aad94c",
token: "string",
},
{
foreground: "#aad94c",
token: "constant.other.symbol",
},
{
foreground: "#95e6cb",
token: "string.regexp",
},
{
foreground: "#95e6cb",
token: "constant.character",
},
{
foreground: "#95e6cb",
token: "constant.other",
},
{
foreground: "#d2a6ff",
token: "constant.numeric",
},
{
foreground: "#d2a6ff",
token: "constant.language",
},
{
foreground: "#bfbdb6",
token: "variable",
},
{
foreground: "#bfbdb6",
token: "variable.parameter.function-call",
},
{
foreground: "#f07178",
token: "variable.member",
},
{
fontStyle: "italic",
foreground: "#39bae6",
token: "variable.language",
},
{
foreground: "#ff8f40",
token: "storage",
},
{
foreground: "#ff8f40",
token: "keyword",
},
{
foreground: "#f29668",
token: "keyword.operator",
},
{
foreground: "#bfbdb6b3",
token: "punctuation.separator",
},
{
foreground: "#bfbdb6b3",
token: "punctuation.terminator",
},
{
foreground: "#bfbdb6",
token: "punctuation.section",
},
{
foreground: "#f29668",
token: "punctuation.accessor",
},
{
foreground: "#ff8f40",
token: "punctuation.definition.template-expression",
},
{
foreground: "#ff8f40",
token: "punctuation.section.embedded",
},
{
foreground: "#bfbdb6",
token: "meta.embedded",
},
{
foreground: "#59c2ff",
token: "source.java storage.type",
},
{
foreground: "#59c2ff",
token: "source.haskell storage.type",
},
{
foreground: "#59c2ff",
token: "source.c storage.type",
},
{
foreground: "#39bae6",
token: "entity.other.inherited-class",
},
{
foreground: "#ff8f40",
token: "storage.type.function",
},
{
foreground: "#39bae6",
token: "source.java storage.type.primitive",
},
{
foreground: "#ffb454",
token: "entity.name.function",
},
{
foreground: "#d2a6ff",
token: "variable.parameter",
},
{
foreground: "#d2a6ff",
token: "meta.parameter",
},
{
foreground: "#ffb454",
token: "variable.function",
},
{
foreground: "#ffb454",
token: "variable.annotation",
},
{
foreground: "#ffb454",
token: "meta.function-call.generic",
},
{
foreground: "#ffb454",
token: "support.function.go",
},
{
foreground: "#f07178",
token: "support.function",
},
{
foreground: "#f07178",
token: "support.macro",
},
{
foreground: "#aad94c",
token: "entity.name.import",
},
{
foreground: "#aad94c",
token: "entity.name.package",
},
{
foreground: "#59c2ff",
token: "entity.name",
},
{
foreground: "#39bae6",
token: "entity.name.tag",
},
{
foreground: "#39bae6",
token: "meta.tag.sgml",
},
{
foreground: "#59c2ff",
token: "support.class.component",
},
{
foreground: "#39bae680",
token: "punctuation.definition.tag.end",
},
{
foreground: "#39bae680",
token: "punctuation.definition.tag.begin",
},
{
foreground: "#39bae680",
token: "punctuation.definition.tag",
},
{
foreground: "#ffb454",
token: "entity.other.attribute-name",
},
{
fontStyle: "italic",
foreground: "#f29668",
token: "support.constant",
},
{
foreground: "#39bae6",
token: "support.type",
},
{
foreground: "#39bae6",
token: "support.class",
},
{
foreground: "#39bae6",
token: "source.go storage.type",
},
{
foreground: "#e6b673",
token: "meta.decorator variable.other",
},
{
foreground: "#e6b673",
token: "meta.decorator punctuation.decorator",
},
{
foreground: "#e6b673",
token: "storage.type.annotation",
},
{
foreground: "#d95757",
token: "invalid",
},
{
foreground: "#c594c5",
token: "meta.diff",
},
{
foreground: "#c594c5",
token: "meta.diff.header",
},
{
foreground: "#ffb454",
token: "source.ruby variable.other.readwrite",
},
{
foreground: "#59c2ff",
token: "source.css entity.name.tag",
},
{
foreground: "#59c2ff",
token: "source.sass entity.name.tag",
},
{
foreground: "#59c2ff",
token: "source.scss entity.name.tag",
},
{
foreground: "#59c2ff",
token: "source.less entity.name.tag",
},
{
foreground: "#59c2ff",
token: "source.stylus entity.name.tag",
},
{
foreground: "#acb6bf8c",
token: "source.css support.type",
},
{
foreground: "#acb6bf8c",
token: "source.sass support.type",
},
{
foreground: "#acb6bf8c",
token: "source.scss support.type",
},
{
foreground: "#acb6bf8c",
token: "source.less support.type",
},
{
foreground: "#acb6bf8c",
token: "source.stylus support.type",
},
{
fontStyle: "normal",
foreground: "#39bae6",
token: "support.type.property-name",
},
{
foreground: "#acb6bf8c",
token: "constant.numeric.line-number.find-in-files - match",
},
{
foreground: "#ff8f40",
token: "constant.numeric.line-number.match",
},
{
foreground: "#aad94c",
token: "entity.name.filename.find-in-files",
},
{
foreground: "#d95757",
token: "message.error",
},
{
fontStyle: "bold",
foreground: "#aad94c",
token: "markup.heading",
},
{
fontStyle: "bold",
foreground: "#aad94c",
token: "markup.heading entity.name",
},
{
foreground: "#39bae6",
token: "markup.underline.link",
},
{
foreground: "#39bae6",
token: "string.other.link",
},
{
fontStyle: "italic",
foreground: "#f07178",
token: "markup.italic",
},
{
fontStyle: "bold",
foreground: "#f07178",
token: "markup.bold",
},
{
fontStyle: "bold italic",
token: "markup.italic markup.bold",
},
{
fontStyle: "bold italic",
token: "markup.bold markup.italic",
},
{
background: "#bfbdb605",
token: "markup.raw",
},
{
background: "#bfbdb60f",
token: "markup.raw.inline",
},
{
fontStyle: "bold",
background: "#bfbdb60f",
foreground: "#acb6bf8c",
token: "meta.separator",
},
{
foreground: "#95e6cb",
fontStyle: "italic",
token: "markup.quote",
},
{
foreground: "#ffb454",
token: "markup.list punctuation.definition.list.begin",
},
{
foreground: "#7fd962",
token: "markup.inserted",
},
{
foreground: "#73b8ff",
token: "markup.changed",
},
{
foreground: "#f26d78",
token: "markup.deleted",
},
{
foreground: "#e6b673",
token: "markup.strike",
},
{
background: "#bfbdb60f",
foreground: "#39bae6",
token: "markup.table",
},
{
foreground: "#f29668",
token: "text.html.markdown markup.inline.raw",
},
{
background: "#acb6bf8c",
foreground: "#acb6bf8c",
token: "text.html.markdown meta.dummy.line-break",
},
{
background: "#bfbdb6",
foreground: "#acb6bf8c",
token: "punctuation.definition.markdown",
},
// Manual changes.
{
foreground: "#fa8d3e",
token: "number",

View File

@@ -5,14 +5,17 @@
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();
@@ -33,7 +36,7 @@ export default function SettingsEditor({
(value: string | undefined) => {
onChange(value ?? "");
},
[onChange]
[onChange],
);
return (
<Editor
@@ -47,7 +50,7 @@ export default function SettingsEditor({
wrapperProps={visible ? {} : { style: { display: "none" } }}
language={"json"}
value={source}
theme={"Ayu-Light"}
theme={theme === "light" ? "Ayu-Light" : "Ayu-Dark"}
onChange={handleChange}
/>
);

View File

@@ -13,7 +13,7 @@ export default function ShareButton({ onShare }: { onShare?: () => void }) {
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 shadow-copied dark:bg-ayu/10"
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"
@@ -28,7 +28,7 @@ export default function ShareButton({ onShare }: { onShare?: () => void }) {
) : (
<button
type="button"
className="relative flex-none rounded-md text-sm font-semibold leading-6 py-1.5 px-3 enabled:hover:bg-ayu/80 bg-ayu text-white shadow-sm dark:shadow-highlight/20 disabled:opacity-50"
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

View File

@@ -6,18 +6,19 @@ import Editor, { useMonaco } from "@monaco-editor/react";
import { MarkerSeverity, MarkerTag } from "monaco-editor";
import { useCallback, useEffect } from "react";
import { Check } from "../pkg";
export type Mode = "JSON" | "Python";
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();
@@ -43,7 +44,7 @@ export default function SourceEditor({
check.code === "F401" || check.code === "F841"
? [MarkerTag.Unnecessary]
: [],
}))
})),
);
const codeActionProvider = monaco?.languages.registerCodeActionProvider(
@@ -55,7 +56,9 @@ export default function SourceEditor({
.filter((check) => position.startLineNumber === check.location.row)
.filter((check) => check.fix)
.map((check) => ({
title: `Fix ${check.code}`,
title: check.fix
? `${check.code}: ${check.fix.message}` ?? `Fix ${check.code}`
: "Autofix",
id: `fix-${check.code}`,
kind: "quickfix",
edit: check.fix
@@ -80,7 +83,7 @@ export default function SourceEditor({
}));
return { actions, dispose: () => {} };
},
}
},
);
return () => {
@@ -92,7 +95,7 @@ export default function SourceEditor({
(value: string | undefined) => {
onChange(value ?? "");
},
[onChange]
[onChange],
);
return (
@@ -104,9 +107,9 @@ export default function SourceEditor({
roundedSelection: false,
scrollBeyondLastLine: false,
}}
wrapperProps={visible ? {} : { style: { display: "none" } }}
theme={"Ayu-Light"}
language={"python"}
wrapperProps={visible ? {} : { style: { display: "none" } }}
theme={theme === "light" ? "Ayu-Light" : "Ayu-Dark"}
value={source}
onChange={handleChange}
/>

View 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>
);
}

View File

@@ -17,7 +17,7 @@ export default function VersionTag({ children }: { children: ReactNode }) {
"items-center",
"dark:bg-gray-800",
"dark:text-gray-400",
"dark:shadow-highlight/4"
"dark:shadow-highlight/4",
)}
>
{children}

View File

@@ -1,63 +0,0 @@
import lzstring from "lz-string";
import { OptionGroup } from "../ruff_options";
export type Config = { [K: string]: any };
/**
* Parse an encoded value from the options export.
*
* TODO(charlie): Use JSON for the default values.
*/
function parse(value: any): any {
if (value == "None") {
return null;
}
return JSON.parse(value);
}
/**
* The default configuration for the playground.
*/
export function defaultConfig(availableOptions: OptionGroup[]): Config {
const config: Config = {};
for (const group of availableOptions) {
if (group.name == "globals") {
for (const field of group.fields) {
config[field.name] = parse(field.default);
}
} else {
config[group.name] = {};
for (const field of group.fields) {
config[group.name][field.name] = parse(field.default);
}
}
}
return config;
}
/**
* Persist the configuration to a URL.
*/
export function persist(configSource: string, pythonSource: string) {
window.location.hash = lzstring.compressToEncodedURIComponent(
configSource + "$$$" + pythonSource
);
}
/**
* 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 configSource = parts[0];
const pythonSource = parts[1];
return [configSource, pythonSource];
} else {
return null;
}
}

View 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;
}
}

View 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];
}

View 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);
}
}

View File

@@ -1,6 +1,3 @@
import { defaultConfig } from "./Editor/config";
import { AVAILABLE_OPTIONS } from "./ruff_options";
export const DEFAULT_PYTHON_SOURCE =
"import os\n" +
"\n" +
@@ -32,9 +29,3 @@ export const DEFAULT_PYTHON_SOURCE =
"# 13\n" +
"# 21\n" +
"# 34\n";
export const DEFAULT_CONFIG_SOURCE = JSON.stringify(
defaultConfig(AVAILABLE_OPTIONS),
null,
2
);

View File

@@ -6,5 +6,5 @@ import "./index.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<Editor />
</React.StrictMode>
</React.StrictMode>,
);

View File

@@ -1,244 +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": "update-check",
"default": 'true',
"type": 'bool',
},
]},
{"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": 'None',
"type": 'Convention',
},
]},
{"name": "pyupgrade", "fields": [
{
"name": "keep-runtime-typing",
"default": 'false',
"type": 'bool',
},
]},
];

View File

@@ -1,6 +1,6 @@
declare module "lz-string" {
function decompressFromEncodedURIComponent(
input: string | null
input: string | null,
): string | null;
function compressToEncodedURIComponent(input: string | null): string;
}

View File

@@ -2,11 +2,16 @@
const defaultTheme = require("tailwindcss/defaultTheme");
module.exports = {
darkMode: "class",
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
colors: {
ayu: "#f07171",
"ayu-accent": "#f07171",
"ayu-background": {
DEFAULT: "#f8f9fa",
dark: "#0b0e14",
},
},
fontFamily: {
sans: ["Inter var", ...defaultTheme.fontFamily.sans],

View File

@@ -1,5 +1,19 @@
[build-system]
requires = ["maturin>=0.14,<0.15"]
build-backend = "maturin"
[project]
name = "ruff"
version = "0.0.205"
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,24 +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]
update-check = true
line-length = 88
[tool.ruff.isort]
force-wrap-aliases = true

View File

@@ -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

View 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"
)

View 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

View 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()

View File

@@ -0,0 +1 @@
from long_module_name import member_one, member_two, member_three, member_four, member_five

View File

@@ -0,0 +1,2 @@
from long_module_name import member_one, member_two, member_three, member_four, member_five

View File

@@ -0,0 +1,2 @@
from long_module_name import member_one, member_two, member_three, member_four, member_five

View File

@@ -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

View File

@@ -1,6 +1,8 @@
# OK
a = "abc"
b = f"ghi{'jkl'}"
# Errors
c = f"def"
d = f"def" + "ghi"
e = (
@@ -8,5 +10,17 @@ e = (
"ghi"
)
# OK
g = f"ghi{123:{45}}"
# Error
h = "x" "y" f"z"
v = 23.234234
# OK
f"{v:0.2f}"
# OK (false negatives)
f"{v:{f'0.2f'}}"
f"{f''}"

View File

@@ -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

View File

@@ -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

View 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"

View 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

View File

@@ -375,6 +375,19 @@
},
"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": [
@@ -639,8 +652,6 @@
"F821",
"F822",
"F823",
"F83",
"F831",
"F84",
"F841",
"F842",
@@ -670,6 +681,12 @@
"ICN0",
"ICN00",
"ICN001",
"ISC",
"ISC0",
"ISC00",
"ISC001",
"ISC002",
"ISC003",
"M",
"M0",
"M001",
@@ -842,6 +859,7 @@
"TID",
"TID2",
"TID25",
"TID251",
"TID252",
"U",
"U0",
@@ -891,6 +909,7 @@
"UP021",
"UP022",
"UP023",
"UP025",
"W",
"W2",
"W29",
@@ -1085,6 +1104,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

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.200"
version = "0.0.205"
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 = "8cb2b8292062adf13bde1b863a9b02c9f0bda3dd" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "8cb2b8292062adf13bde1b863a9b02c9f0bda3dd" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "8cb2b8292062adf13bde1b863a9b02c9f0bda3dd" }
schemars = { version = "0.8.11" }
serde_json = {version="1.0.91"}
strum = { version = "0.24.1", features = ["strum_macros"] }

View File

@@ -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(())

View 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(())
}

View File

@@ -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(())

View File

@@ -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(())
}

View File

@@ -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(())
}

View File

@@ -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;

View File

@@ -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)?,

View File

@@ -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
View 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(())
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_macros"
version = "0.0.200"
version = "0.0.205"
edition = "2021"
[lib]

View File

@@ -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;
@@ -395,6 +395,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,
@@ -426,6 +439,64 @@ pub fn excepthandler_name_range(
}
}
/// 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,
});
range
}
_ => None,
}
}
/// Return `true` if a `Stmt` appears to be part of a multi-statement line, with
/// other statements preceding it.
pub fn preceded_by_continuation(stmt: &Stmt, locator: &SourceCodeLocator) -> bool {
@@ -466,7 +537,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;
@@ -738,4 +811,24 @@ class Class():
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(())
}
}

View File

@@ -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 {

View File

@@ -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,11 +87,11 @@ 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,
in_deferred_type_definition: bool,
in_f_string: bool,
in_literal: bool,
in_subscript: bool,
seen_import_boundary: bool,
@@ -144,11 +144,11 @@ 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,
in_deferred_type_definition: false,
in_f_string: false,
in_literal: false,
in_subscript: false,
seen_import_boundary: false,
@@ -161,14 +161,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 +177,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}`.
@@ -282,16 +266,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 +287,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 +297,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 +315,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 +329,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 +340,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 +385,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 +538,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 +572,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 +588,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);
}
}
@@ -723,6 +713,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);
@@ -854,6 +855,27 @@ where
}
}
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() {
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
@@ -1155,7 +1177,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);
}
}
@@ -1399,7 +1423,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()),
},
);
@@ -1523,9 +1547,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);
}
}
@@ -1586,6 +1611,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,
@@ -1687,7 +1721,7 @@ where
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
@@ -2125,11 +2159,12 @@ 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 { .. }))
// Conversion flags are parsed as f-strings without placeholders, so skip
// nested f-strings, which would lead to false positives.
if !self.in_f_string && self.settings.enabled.contains(&CheckCode::F541) {
if !values
.iter()
.any(|value| matches!(value.node, ExprKind::FormattedValue { .. }))
{
self.add_check(Check::new(
CheckKind::FStringMissingPlaceholders,
@@ -2137,7 +2172,6 @@ where
));
}
}
self.in_f_string = Some(Range::from_located(expr));
}
ExprKind::BinOp {
left,
@@ -2230,6 +2264,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 +2372,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 +2390,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 +2664,11 @@ where
}
self.in_subscript = prev_in_subscript;
}
ExprKind::JoinedStr { .. } => {
self.in_f_string = true;
visitor::walk_expr(self, expr);
self.in_f_string = prev_in_f_string;
}
_ => visitor::walk_expr(self, expr),
}
@@ -2649,7 +2700,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 +2720,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);
}
}
@@ -2737,10 +2791,6 @@ where
}
fn visit_arguments(&mut self, arguments: &'b Arguments) {
if self.settings.enabled.contains(&CheckCode::F831) {
self.checks
.extend(pyflakes::checks::duplicate_arguments(arguments));
}
if self.settings.enabled.contains(&CheckCode::B006) {
flake8_bugbear::plugins::mutable_argument_default(self, arguments);
}
@@ -2791,7 +2841,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 +3674,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 +3736,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);
}
}
}

View File

@@ -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)
}

View File

@@ -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,20 +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)
{
if check.kind == CheckKind::BlanketNOQA {
continue;
}
// 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![])
});
@@ -65,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).

View File

@@ -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
}

View File

@@ -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,7 @@ pub enum CheckCode {
UP021,
UP022,
UP023,
UP025,
// pydocstyle
D100,
D101,
@@ -375,6 +381,7 @@ pub enum CheckCategory {
Flake8Comprehensions,
Flake8Debugger,
Flake8ErrMsg,
Flake8ImplicitStrConcat,
Flake8ImportConventions,
Flake8Print,
Flake8Quotes,
@@ -418,6 +425,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",
@@ -451,19 +459,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],
@@ -475,7 +485,6 @@ impl CheckCategory {
CheckCodePrefix::PLW,
],
CheckCategory::Pyupgrade => vec![CheckCodePrefix::UP],
CheckCategory::Flake8ImportConventions => vec![CheckCodePrefix::ICN],
CheckCategory::Ruff => vec![CheckCodePrefix::RUF],
}
}
@@ -525,6 +534,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/",
@@ -605,11 +618,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,
@@ -642,6 +681,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>,
@@ -655,17 +709,17 @@ pub enum CheckKind {
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,
@@ -675,7 +729,6 @@ pub enum CheckKind {
BreakOutsideLoop,
ContinueOutsideLoop,
DefaultExceptNotLast,
DuplicateArgumentName,
ExpressionsInStarAssignment,
FStringMissingPlaceholders,
ForwardAnnotationSyntaxError(String),
@@ -686,7 +739,7 @@ pub enum CheckKind {
ImportStarUsage(String, Vec<String>),
ImportStarUsed(String),
InvalidPrintSyntax,
IsLiteral,
IsLiteral(IsCmpop),
LateFutureImport,
MultiValueRepeatedKeyLiteral,
MultiValueRepeatedKeyVariable(String),
@@ -712,7 +765,7 @@ pub enum CheckKind {
UndefinedLocal(String),
UnusedAnnotation(String),
UndefinedName(String),
UnusedImport(String, bool),
UnusedImport(String, bool, bool),
UnusedVariable(String),
YieldOutsideFunction(DeferralKeyword),
// pylint
@@ -783,6 +836,7 @@ pub enum CheckKind {
// flake8-debugger
Debugger(DebuggerUsingType),
// flake8-tidy-imports
BannedApi { name: String, message: String },
BannedRelativeImport(Strictness),
// flake8-return
UnnecessaryReturnNone,
@@ -793,6 +847,10 @@ pub enum CheckKind {
SuperfluousElseRaise(Branch),
SuperfluousElseContinue(Branch),
SuperfluousElseBreak(Branch),
// flake8-implicit-str-concat
SingleLineImplicitStringConcatenation,
MultiLineImplicitStringConcatenation,
ExplicitStringConcatenation,
// flake8-print
PrintFound,
PPrintFound,
@@ -841,14 +899,15 @@ pub enum CheckKind {
UnnecessaryEncodeUTF8,
ConvertTypedDictFunctionalToClass(String),
ConvertNamedTupleFunctionalToClass(String),
RedundantOpenModes,
RedundantOpenModes(Option<String>),
RemoveSixCompat,
DatetimeTimezoneUTC,
NativeLiterals,
NativeLiterals(LiteralType),
OpenAlias,
ReplaceUniversalNewlines,
ReplaceStdoutStderr,
RewriteCElementTree,
RewriteUnicodeLiteral,
// pydocstyle
BlankLineAfterLastSection(String),
BlankLineAfterSection(String),
@@ -988,6 +1047,8 @@ impl CheckCode {
| CheckCode::PGH003
| CheckCode::PGH004 => &LintSource::Lines,
CheckCode::ERA001
| CheckCode::ISC001
| CheckCode::ISC002
| CheckCode::Q000
| CheckCode::Q001
| CheckCode::Q002
@@ -1009,13 +1070,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()),
@@ -1025,7 +1086,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,
@@ -1058,7 +1119,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,
@@ -1071,7 +1132,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,
@@ -1162,6 +1222,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,
@@ -1172,6 +1236,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,
@@ -1224,15 +1292,16 @@ 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::UP025 => CheckKind::RewriteUnicodeLiteral,
// pydocstyle
CheckCode::D100 => CheckKind::PublicModule,
CheckCode::D101 => CheckKind::PublicClass,
@@ -1559,7 +1628,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,
@@ -1567,8 +1635,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,
@@ -1639,6 +1709,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,
@@ -1661,6 +1733,7 @@ impl CheckCode {
CheckCode::UP021 => CheckCategory::Pyupgrade,
CheckCode::UP022 => CheckCategory::Pyupgrade,
CheckCode::UP023 => CheckCategory::Pyupgrade,
CheckCode::UP025 => CheckCategory::Pyupgrade,
CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::W605 => CheckCategory::Pycodestyle,
CheckCode::YTT101 => CheckCategory::Flake82020,
@@ -1689,9 +1762,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,
@@ -1702,7 +1774,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,
@@ -1812,6 +1884,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,
@@ -1822,6 +1895,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,
@@ -1869,15 +1946,16 @@ 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::RewriteUnicodeLiteral => &CheckCode::UP025,
// pydocstyle
CheckKind::BlankLineAfterLastSection(..) => &CheckCode::D413,
CheckKind::BlankLineAfterSection(..) => &CheckCode::D410,
@@ -2027,13 +2105,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}`")
}
@@ -2064,7 +2139,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()
}
@@ -2082,10 +2160,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(),
@@ -2149,16 +2225,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(),
@@ -2175,7 +2251,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 \
@@ -2439,6 +2515,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()
@@ -2470,6 +2547,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(),
@@ -2575,7 +2662,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!(
@@ -2601,14 +2688,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()
}
@@ -2618,9 +2715,7 @@ impl CheckKind {
CheckKind::RewriteCElementTree => {
"`cElementTree` is deprecated, use `ElementTree`".to_string()
}
CheckKind::ConvertNamedTupleFunctionalToClass(name) => {
format!("Convert `{name}` from `NamedTuple` functional to class syntax")
}
CheckKind::RewriteUnicodeLiteral => "Remove unicode literals from strings".to_string(),
// pydocstyle
CheckKind::FitsOnOneLine => "One-line docstring should fit on one line".to_string(),
CheckKind::BlankLineAfterSummary => {
@@ -2797,14 +2892,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}`"),
@@ -3036,6 +3130,7 @@ impl CheckKind {
self,
CheckKind::AmbiguousUnicodeCharacterString(..)
| CheckKind::AmbiguousUnicodeCharacterDocstring(..)
| CheckKind::AmbiguousUnicodeCharacterComment(..)
| CheckKind::BlankLineAfterLastSection(..)
| CheckKind::BlankLineAfterSection(..)
| CheckKind::BlankLineAfterSummary
@@ -3048,7 +3143,7 @@ impl CheckKind {
| CheckKind::DatetimeTimezoneUTC
| CheckKind::DeprecatedUnittestAlias(..)
| CheckKind::DoNotAssertFalse
| CheckKind::DoNotAssignLambda
| CheckKind::DoNotAssignLambda(..)
| CheckKind::DuplicateHandlerException(..)
| CheckKind::EndsInPeriod
| CheckKind::EndsInPunctuation
@@ -3056,16 +3151,17 @@ impl CheckKind {
| CheckKind::ImplicitReturn
| CheckKind::ImplicitReturnValue
| CheckKind::InvalidEscapeSequence(..)
| CheckKind::IsLiteral
| CheckKind::IsLiteral(..)
| CheckKind::KeyInDict(..)
| CheckKind::MisplacedComparisonConstant(..)
| CheckKind::MissingReturnTypeSpecialMethod(..)
| CheckKind::NativeLiterals
| CheckKind::NativeLiterals(..)
| CheckKind::OpenAlias
| CheckKind::NewLineAfterLastParagraph
| CheckKind::ReplaceUniversalNewlines
| CheckKind::ReplaceStdoutStderr
| CheckKind::RewriteCElementTree
| CheckKind::RewriteUnicodeLiteral
| CheckKind::NewLineAfterSectionName(..)
| CheckKind::NoBlankLineAfterFunction(..)
| CheckKind::NoBlankLineBeforeClass(..)
@@ -3082,10 +3178,10 @@ impl CheckKind {
| CheckKind::OneBlankLineBeforeClass(..)
| CheckKind::PEP3120UnnecessaryCodingComment
| CheckKind::PPrintFound
| CheckKind::PercentFormatExtraNamedArguments(..)
| CheckKind::PrintFound
| CheckKind::PercentFormatExtraNamedArguments(..)
| CheckKind::RaiseNotImplemented
| CheckKind::RedundantOpenModes
| CheckKind::RedundantOpenModes(..)
| CheckKind::RedundantTupleInExceptionHandler(..)
| CheckKind::RemoveSixCompat
| CheckKind::SectionNameEndsInColon(..)
@@ -3117,7 +3213,7 @@ impl CheckKind {
| CheckKind::UnnecessaryLiteralWithinTupleCall(..)
| CheckKind::UnnecessaryReturnNone
| CheckKind::UnsortedImports
| CheckKind::UnusedImport(_, false)
| CheckKind::UnusedImport(_, false, _)
| CheckKind::UnusedLoopControlVariable(..)
| CheckKind::UnusedNOQA(..)
| CheckKind::UsePEP585Annotation(..)
@@ -3128,6 +3224,235 @@ 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::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::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)]
@@ -3136,6 +3461,7 @@ pub struct Check {
pub location: Location,
pub end_location: Location,
pub fix: Option<Fix>,
pub parent: Option<Location>,
}
impl Check {
@@ -3145,11 +3471,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
}
}
@@ -3300,4 +3633,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."
);
}
}
}
}

View File

@@ -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,
@@ -486,6 +490,7 @@ pub enum CheckCodePrefix {
TID,
TID2,
TID25,
TID251,
TID252,
U,
U0,
@@ -535,6 +540,7 @@ pub enum CheckCodePrefix {
UP021,
UP022,
UP023,
UP025,
W,
W2,
W29,
@@ -639,7 +645,6 @@ impl CheckCodePrefix {
CheckCode::F821,
CheckCode::F822,
CheckCode::F823,
CheckCode::F831,
CheckCode::F841,
CheckCode::F842,
CheckCode::F901,
@@ -705,6 +710,7 @@ impl CheckCodePrefix {
CheckCode::C417,
CheckCode::T100,
CheckCode::C901,
CheckCode::TID251,
CheckCode::TID252,
CheckCode::RET501,
CheckCode::RET502,
@@ -714,6 +720,9 @@ impl CheckCodePrefix {
CheckCode::RET506,
CheckCode::RET507,
CheckCode::RET508,
CheckCode::ISC001,
CheckCode::ISC002,
CheckCode::ISC003,
CheckCode::T201,
CheckCode::T203,
CheckCode::Q000,
@@ -764,6 +773,7 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP025,
CheckCode::D100,
CheckCode::D101,
CheckCode::D102,
@@ -1493,7 +1503,6 @@ impl CheckCodePrefix {
CheckCode::F821,
CheckCode::F822,
CheckCode::F823,
CheckCode::F831,
CheckCode::F841,
CheckCode::F842,
CheckCode::F901,
@@ -1627,7 +1636,6 @@ impl CheckCodePrefix {
CheckCode::F821,
CheckCode::F822,
CheckCode::F823,
CheckCode::F831,
CheckCode::F841,
CheckCode::F842,
],
@@ -1637,8 +1645,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],
@@ -1662,7 +1668,7 @@ impl CheckCodePrefix {
":".bold(),
"`I2` has been remapped to `TID2`".bold()
);
vec![CheckCode::TID252]
vec![CheckCode::TID251, CheckCode::TID252]
}
CheckCodePrefix::I25 => {
one_time_warning!(
@@ -1671,7 +1677,7 @@ impl CheckCodePrefix {
":".bold(),
"`I25` has been remapped to `TID25`".bold()
);
vec![CheckCode::TID252]
vec![CheckCode::TID251, CheckCode::TID252]
}
CheckCodePrefix::I252 => {
one_time_warning!(
@@ -1740,6 +1746,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!(
"{}{} {}",
@@ -2405,9 +2417,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!(
@@ -2439,6 +2452,7 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP025,
]
}
CheckCodePrefix::U0 => {
@@ -2471,6 +2485,7 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP025,
]
}
CheckCodePrefix::U00 => {
@@ -2687,6 +2702,7 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP025,
],
CheckCodePrefix::UP0 => vec![
CheckCode::UP001,
@@ -2711,6 +2727,7 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP025,
],
CheckCodePrefix::UP00 => vec![
CheckCode::UP001,
@@ -2757,11 +2774,13 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP025,
],
CheckCodePrefix::UP020 => vec![CheckCode::UP020],
CheckCodePrefix::UP021 => vec![CheckCode::UP021],
CheckCodePrefix::UP022 => vec![CheckCode::UP022],
CheckCodePrefix::UP023 => vec![CheckCode::UP023],
CheckCodePrefix::UP025 => vec![CheckCode::UP025],
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
CheckCodePrefix::W2 => vec![CheckCode::W292],
CheckCodePrefix::W29 => vec![CheckCode::W292],
@@ -3076,8 +3095,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,
@@ -3107,6 +3124,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,
@@ -3279,6 +3302,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,
@@ -3328,6 +3352,7 @@ impl CheckCodePrefix {
CheckCodePrefix::UP021 => SuffixLength::Three,
CheckCodePrefix::UP022 => SuffixLength::Three,
CheckCodePrefix::UP023 => SuffixLength::Three,
CheckCodePrefix::UP025 => SuffixLength::Three,
CheckCodePrefix::W => SuffixLength::Zero,
CheckCodePrefix::W2 => SuffixLength::One,
CheckCodePrefix::W29 => SuffixLength::Two,
@@ -3373,6 +3398,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
CheckCodePrefix::FBT,
CheckCodePrefix::I,
CheckCodePrefix::ICN,
CheckCodePrefix::ISC,
CheckCodePrefix::N,
CheckCodePrefix::PD,
CheckCodePrefix::PGH,

View File

@@ -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,
@@ -114,15 +121,6 @@ pub struct Cli {
update_check: bool,
#[clap(long, overrides_with("update_check"), hide = true)]
no_update_check: 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,
/// Regular expression matching the name of dummy variables.
#[arg(long)]
pub dummy_variable_rgx: Option<Regex>,
@@ -133,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 {
@@ -161,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,
@@ -222,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,

View File

@@ -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(())
}

View File

@@ -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'", "\"", "'",
];

View File

@@ -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(())
}

View File

@@ -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: ~

View File

@@ -24,13 +24,12 @@ mod tests {
#[test_case(CheckCode::YTT303, Path::new("YTT303.py"); "YTT303")]
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/flake8_2020")
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}

View File

@@ -10,6 +10,7 @@ expression: checks
row: 6
column: 17
fix: ~
parent: ~
- kind: SysVersionSlice3Referenced
location:
row: 7
@@ -18,6 +19,7 @@ expression: checks
row: 7
column: 13
fix: ~
parent: ~
- kind: SysVersionSlice3Referenced
location:
row: 8
@@ -26,4 +28,5 @@ expression: checks
row: 8
column: 7
fix: ~
parent: ~

View File

@@ -10,6 +10,7 @@ expression: checks
row: 4
column: 22
fix: ~
parent: ~
- kind: SysVersion2Referenced
location:
row: 5
@@ -18,4 +19,5 @@ expression: checks
row: 5
column: 18
fix: ~
parent: ~

View File

@@ -10,6 +10,7 @@ expression: checks
row: 4
column: 7
fix: ~
parent: ~
- kind: SysVersionCmpStr3
location:
row: 5
@@ -18,6 +19,7 @@ expression: checks
row: 5
column: 11
fix: ~
parent: ~
- kind: SysVersionCmpStr3
location:
row: 6
@@ -26,6 +28,7 @@ expression: checks
row: 6
column: 11
fix: ~
parent: ~
- kind: SysVersionCmpStr3
location:
row: 7
@@ -34,6 +37,7 @@ expression: checks
row: 7
column: 11
fix: ~
parent: ~
- kind: SysVersionCmpStr3
location:
row: 8
@@ -42,4 +46,5 @@ expression: checks
row: 8
column: 11
fix: ~
parent: ~

View File

@@ -10,6 +10,7 @@ expression: checks
row: 7
column: 25
fix: ~
parent: ~
- kind: SysVersionInfo0Eq3Referenced
location:
row: 8
@@ -18,6 +19,7 @@ expression: checks
row: 8
column: 21
fix: ~
parent: ~
- kind: SysVersionInfo0Eq3Referenced
location:
row: 9
@@ -26,6 +28,7 @@ expression: checks
row: 9
column: 25
fix: ~
parent: ~
- kind: SysVersionInfo0Eq3Referenced
location:
row: 10
@@ -34,4 +37,5 @@ expression: checks
row: 10
column: 21
fix: ~
parent: ~

View File

@@ -10,6 +10,7 @@ expression: checks
row: 4
column: 10
fix: ~
parent: ~
- kind: SixPY3Referenced
location:
row: 6
@@ -18,4 +19,5 @@ expression: checks
row: 6
column: 6
fix: ~
parent: ~

View File

@@ -10,6 +10,7 @@ expression: checks
row: 4
column: 19
fix: ~
parent: ~
- kind: SysVersionInfo1CmpInt
location:
row: 5
@@ -18,4 +19,5 @@ expression: checks
row: 5
column: 15
fix: ~
parent: ~

View File

@@ -10,6 +10,7 @@ expression: checks
row: 4
column: 22
fix: ~
parent: ~
- kind: SysVersionInfoMinorCmpInt
location:
row: 5
@@ -18,4 +19,5 @@ expression: checks
row: 5
column: 18
fix: ~
parent: ~

View File

@@ -10,6 +10,7 @@ expression: checks
row: 4
column: 22
fix: ~
parent: ~
- kind: SysVersion0Referenced
location:
row: 5
@@ -18,4 +19,5 @@ expression: checks
row: 5
column: 18
fix: ~
parent: ~

View File

@@ -10,6 +10,7 @@ expression: checks
row: 4
column: 7
fix: ~
parent: ~
- kind: SysVersionCmpStr10
location:
row: 5
@@ -18,6 +19,7 @@ expression: checks
row: 5
column: 11
fix: ~
parent: ~
- kind: SysVersionCmpStr10
location:
row: 6
@@ -26,6 +28,7 @@ expression: checks
row: 6
column: 11
fix: ~
parent: ~
- kind: SysVersionCmpStr10
location:
row: 7
@@ -34,6 +37,7 @@ expression: checks
row: 7
column: 11
fix: ~
parent: ~
- kind: SysVersionCmpStr10
location:
row: 8
@@ -42,4 +46,5 @@ expression: checks
row: 8
column: 11
fix: ~
parent: ~

View File

@@ -10,6 +10,7 @@ expression: checks
row: 4
column: 17
fix: ~
parent: ~
- kind: SysVersionSlice1Referenced
location:
row: 5
@@ -18,4 +19,5 @@ expression: checks
row: 5
column: 13
fix: ~
parent: ~

View File

@@ -15,7 +15,7 @@ mod tests {
#[test]
fn defaults() -> Result<()> {
let mut checks = test_path(
let checks = test_path(
Path::new("./resources/test/fixtures/flake8_annotations/annotation_presence.py"),
&Settings {
..Settings::for_rules(vec![
@@ -33,14 +33,13 @@ mod tests {
])
},
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn suppress_dummy_args() -> Result<()> {
let mut checks = test_path(
let checks = test_path(
Path::new("./resources/test/fixtures/flake8_annotations/suppress_dummy_args.py"),
&Settings {
flake8_annotations: flake8_annotations::settings::Settings {
@@ -58,14 +57,13 @@ mod tests {
])
},
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn mypy_init_return() -> Result<()> {
let mut checks = test_path(
let checks = test_path(
Path::new("./resources/test/fixtures/flake8_annotations/mypy_init_return.py"),
&Settings {
flake8_annotations: flake8_annotations::settings::Settings {
@@ -83,14 +81,13 @@ mod tests {
])
},
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn suppress_none_returning() -> Result<()> {
let mut checks = test_path(
let checks = test_path(
Path::new("./resources/test/fixtures/flake8_annotations/suppress_none_returning.py"),
&Settings {
flake8_annotations: flake8_annotations::settings::Settings {
@@ -108,14 +105,13 @@ mod tests {
])
},
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn allow_star_arg_any() -> Result<()> {
let mut checks = test_path(
let checks = test_path(
Path::new("./resources/test/fixtures/flake8_annotations/allow_star_arg_any.py"),
&Settings {
flake8_annotations: flake8_annotations::settings::Settings {
@@ -127,14 +123,13 @@ mod tests {
..Settings::for_rules(vec![CheckCode::ANN401])
},
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn allow_overload() -> Result<()> {
let mut checks = test_path(
let checks = test_path(
Path::new("./resources/test/fixtures/flake8_annotations/allow_overload.py"),
&Settings {
..Settings::for_rules(vec![
@@ -146,7 +141,6 @@ mod tests {
])
},
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}

View File

@@ -3,7 +3,7 @@ use rustpython_ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::visitor::Visitor;
use crate::ast::{cast, visitor};
use crate::ast::{cast, helpers, visitor};
use crate::checkers::ast::Checker;
use crate::checks::{CheckCode, CheckKind};
use crate::docstrings::definition::{Definition, DefinitionKind};
@@ -167,7 +167,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
if checker.settings.enabled.contains(&CheckCode::ANN201) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePublicFunction(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
));
}
}
@@ -175,7 +175,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
if checker.settings.enabled.contains(&CheckCode::ANN202) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePrivateFunction(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
));
}
}
@@ -309,14 +309,14 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
if checker.settings.enabled.contains(&CheckCode::ANN206) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeClassMethod(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
));
}
} else if visibility::is_staticmethod(checker, cast::decorator_list(stmt)) {
if checker.settings.enabled.contains(&CheckCode::ANN205) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeStaticMethod(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
));
}
} else if visibility::is_init(stmt) {
@@ -328,11 +328,13 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
{
let mut check = Check::new(
CheckKind::MissingReturnTypeSpecialMethod(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
);
if checker.patch(check.kind.code()) {
match fixes::add_return_none_annotation(checker.locator, stmt) {
Ok(fix) => check.amend(fix),
Ok(fix) => {
check.amend(fix);
}
Err(e) => error!("Failed to generate fix: {e}"),
}
}
@@ -343,7 +345,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
if checker.settings.enabled.contains(&CheckCode::ANN204) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeSpecialMethod(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
));
}
} else {
@@ -352,7 +354,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
if checker.settings.enabled.contains(&CheckCode::ANN201) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePublicFunction(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
));
}
}
@@ -360,7 +362,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
if checker.settings.enabled.contains(&CheckCode::ANN202) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePrivateFunction(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
));
}
}

View File

@@ -5,7 +5,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
Debug, PartialEq, Eq, Default, Serialize, Deserialize, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
@@ -51,7 +51,7 @@ pub struct Options {
pub allow_star_arg_any: Option<bool>,
}
#[derive(Debug, Hash, Default)]
#[derive(Debug, Default, Hash)]
#[allow(clippy::struct_excessive_bools)]
pub struct Settings {
pub mypy_init_return: bool,
@@ -60,14 +60,24 @@ pub struct Settings {
pub allow_star_arg_any: bool,
}
impl Settings {
#[allow(clippy::needless_pass_by_value)]
pub fn from_options(options: Options) -> Self {
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
mypy_init_return: options.mypy_init_return.unwrap_or_default(),
suppress_dummy_args: options.suppress_dummy_args.unwrap_or_default(),
suppress_none_returning: options.suppress_none_returning.unwrap_or_default(),
allow_star_arg_any: options.allow_star_arg_any.unwrap_or_default(),
mypy_init_return: options.mypy_init_return.unwrap_or(false),
suppress_dummy_args: options.suppress_dummy_args.unwrap_or(false),
suppress_none_returning: options.suppress_none_returning.unwrap_or(false),
allow_star_arg_any: options.allow_star_arg_any.unwrap_or(false),
}
}
}
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
mypy_init_return: Some(settings.mypy_init_return),
suppress_dummy_args: Some(settings.suppress_dummy_args),
suppress_none_returning: Some(settings.suppress_none_returning),
allow_star_arg_any: Some(settings.allow_star_arg_any),
}
}
}

View File

@@ -6,9 +6,10 @@ expression: checks
MissingReturnTypePublicFunction: bar
location:
row: 29
column: 4
column: 8
end_location:
row: 30
column: 16
row: 29
column: 11
fix: ~
parent: ~

View File

@@ -11,6 +11,7 @@ expression: checks
row: 10
column: 14
fix: ~
parent: ~
- kind:
DynamicallyTypedExpression: foo
location:
@@ -20,6 +21,7 @@ expression: checks
row: 15
column: 49
fix: ~
parent: ~
- kind:
DynamicallyTypedExpression: a
location:
@@ -29,6 +31,7 @@ expression: checks
row: 40
column: 31
fix: ~
parent: ~
- kind:
DynamicallyTypedExpression: foo_method
location:
@@ -38,4 +41,5 @@ expression: checks
row: 44
column: 69
fix: ~
parent: ~

View File

@@ -6,11 +6,12 @@ expression: checks
MissingReturnTypePublicFunction: foo
location:
row: 4
column: 0
column: 4
end_location:
row: 5
column: 8
row: 4
column: 7
fix: ~
parent: ~
- kind:
MissingTypeFunctionArgument: a
location:
@@ -20,6 +21,7 @@ expression: checks
row: 4
column: 9
fix: ~
parent: ~
- kind:
MissingTypeFunctionArgument: b
location:
@@ -29,15 +31,17 @@ expression: checks
row: 4
column: 12
fix: ~
parent: ~
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 9
column: 0
column: 4
end_location:
row: 10
column: 8
row: 9
column: 7
fix: ~
parent: ~
- kind:
MissingTypeFunctionArgument: b
location:
@@ -47,6 +51,7 @@ expression: checks
row: 9
column: 17
fix: ~
parent: ~
- kind:
MissingTypeFunctionArgument: b
location:
@@ -56,24 +61,27 @@ expression: checks
row: 14
column: 17
fix: ~
parent: ~
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 19
column: 0
column: 4
end_location:
row: 20
column: 8
row: 19
column: 7
fix: ~
parent: ~
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 24
column: 0
column: 4
end_location:
row: 25
column: 8
row: 24
column: 7
fix: ~
parent: ~
- kind:
DynamicallyTypedExpression: a
location:
@@ -83,6 +91,7 @@ expression: checks
row: 44
column: 14
fix: ~
parent: ~
- kind:
DynamicallyTypedExpression: foo
location:
@@ -92,6 +101,7 @@ expression: checks
row: 49
column: 49
fix: ~
parent: ~
- kind:
DynamicallyTypedExpression: "*args"
location:
@@ -101,6 +111,7 @@ expression: checks
row: 54
column: 26
fix: ~
parent: ~
- kind:
DynamicallyTypedExpression: "**kwargs"
location:
@@ -110,6 +121,7 @@ expression: checks
row: 54
column: 41
fix: ~
parent: ~
- kind:
DynamicallyTypedExpression: "*args"
location:
@@ -119,6 +131,7 @@ expression: checks
row: 59
column: 26
fix: ~
parent: ~
- kind:
DynamicallyTypedExpression: "**kwargs"
location:
@@ -128,6 +141,7 @@ expression: checks
row: 64
column: 41
fix: ~
parent: ~
- kind:
MissingTypeSelf: self
location:
@@ -137,6 +151,7 @@ expression: checks
row: 74
column: 16
fix: ~
parent: ~
- kind:
DynamicallyTypedExpression: a
location:
@@ -146,6 +161,7 @@ expression: checks
row: 78
column: 31
fix: ~
parent: ~
- kind:
DynamicallyTypedExpression: foo
location:
@@ -155,6 +171,7 @@ expression: checks
row: 82
column: 69
fix: ~
parent: ~
- kind:
DynamicallyTypedExpression: "*params"
location:
@@ -164,6 +181,7 @@ expression: checks
row: 86
column: 45
fix: ~
parent: ~
- kind:
DynamicallyTypedExpression: "**options"
location:
@@ -173,6 +191,7 @@ expression: checks
row: 86
column: 61
fix: ~
parent: ~
- kind:
DynamicallyTypedExpression: "*params"
location:
@@ -182,6 +201,7 @@ expression: checks
row: 90
column: 45
fix: ~
parent: ~
- kind:
DynamicallyTypedExpression: "**options"
location:
@@ -191,6 +211,7 @@ expression: checks
row: 94
column: 61
fix: ~
parent: ~
- kind:
MissingTypeCls: cls
location:
@@ -200,4 +221,5 @@ expression: checks
row: 104
column: 15
fix: ~
parent: ~

View File

@@ -6,10 +6,10 @@ expression: checks
MissingReturnTypeSpecialMethod: __init__
location:
row: 5
column: 4
column: 8
end_location:
row: 6
column: 11
row: 5
column: 16
fix:
content: " -> None"
location:
@@ -18,14 +18,15 @@ expression: checks
end_location:
row: 5
column: 22
parent: ~
- kind:
MissingReturnTypeSpecialMethod: __init__
location:
row: 11
column: 4
column: 8
end_location:
row: 12
column: 11
row: 11
column: 16
fix:
content: " -> None"
location:
@@ -34,13 +35,15 @@ expression: checks
end_location:
row: 11
column: 27
parent: ~
- kind:
MissingReturnTypePrivateFunction: __init__
location:
row: 40
column: 0
column: 4
end_location:
row: 41
column: 7
row: 40
column: 12
fix: ~
parent: ~

View File

@@ -6,18 +6,20 @@ expression: checks
MissingReturnTypePublicFunction: foo
location:
row: 45
column: 0
column: 4
end_location:
row: 46
column: 15
row: 45
column: 7
fix: ~
parent: ~
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 50
column: 0
column: 4
end_location:
row: 55
column: 14
row: 50
column: 7
fix: ~
parent: ~

View File

@@ -21,13 +21,12 @@ mod tests {
#[test_case(CheckCode::S107, Path::new("S107.py"); "S107")]
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/flake8_bandit")
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}

View File

@@ -10,6 +10,7 @@ expression: checks
row: 2
column: 11
fix: ~
parent: ~
- kind: AssertUsed
location:
row: 8
@@ -18,6 +19,7 @@ expression: checks
row: 8
column: 17
fix: ~
parent: ~
- kind: AssertUsed
location:
row: 11
@@ -26,4 +28,5 @@ expression: checks
row: 11
column: 17
fix: ~
parent: ~

View File

@@ -10,6 +10,7 @@ expression: checks
row: 3
column: 17
fix: ~
parent: ~
- kind: ExecUsed
location:
row: 5
@@ -18,4 +19,5 @@ expression: checks
row: 5
column: 13
fix: ~
parent: ~

View File

@@ -10,6 +10,7 @@ expression: checks
row: 9
column: 9
fix: ~
parent: ~
- kind: HardcodedBindAllInterfaces
location:
row: 10
@@ -18,6 +19,7 @@ expression: checks
row: 10
column: 9
fix: ~
parent: ~
- kind: HardcodedBindAllInterfaces
location:
row: 14
@@ -26,6 +28,7 @@ expression: checks
row: 14
column: 14
fix: ~
parent: ~
- kind: HardcodedBindAllInterfaces
location:
row: 18
@@ -34,4 +37,5 @@ expression: checks
row: 18
column: 17
fix: ~
parent: ~

View File

@@ -11,6 +11,7 @@ expression: checks
row: 12
column: 19
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -20,6 +21,7 @@ expression: checks
row: 13
column: 16
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -29,6 +31,7 @@ expression: checks
row: 14
column: 17
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -38,6 +41,7 @@ expression: checks
row: 15
column: 14
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -47,6 +51,7 @@ expression: checks
row: 16
column: 17
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -56,6 +61,7 @@ expression: checks
row: 17
column: 16
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -65,6 +71,7 @@ expression: checks
row: 18
column: 18
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -74,6 +81,7 @@ expression: checks
row: 19
column: 26
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -83,6 +91,7 @@ expression: checks
row: 20
column: 26
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -92,6 +101,7 @@ expression: checks
row: 22
column: 24
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -101,6 +111,7 @@ expression: checks
row: 23
column: 20
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -110,6 +121,7 @@ expression: checks
row: 24
column: 22
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -119,6 +131,7 @@ expression: checks
row: 25
column: 19
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -128,6 +141,7 @@ expression: checks
row: 26
column: 22
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -137,6 +151,7 @@ expression: checks
row: 27
column: 21
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -146,6 +161,7 @@ expression: checks
row: 28
column: 23
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -155,6 +171,7 @@ expression: checks
row: 29
column: 31
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -164,6 +181,7 @@ expression: checks
row: 30
column: 31
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -173,6 +191,7 @@ expression: checks
row: 34
column: 23
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -182,6 +201,7 @@ expression: checks
row: 38
column: 27
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -191,6 +211,7 @@ expression: checks
row: 39
column: 24
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -200,6 +221,7 @@ expression: checks
row: 40
column: 25
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -209,6 +231,7 @@ expression: checks
row: 41
column: 22
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -218,6 +241,7 @@ expression: checks
row: 42
column: 25
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -227,6 +251,7 @@ expression: checks
row: 43
column: 24
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -236,6 +261,7 @@ expression: checks
row: 44
column: 26
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -245,6 +271,7 @@ expression: checks
row: 46
column: 20
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -254,6 +281,7 @@ expression: checks
row: 47
column: 17
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -263,6 +291,7 @@ expression: checks
row: 48
column: 18
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -272,6 +301,7 @@ expression: checks
row: 49
column: 15
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -281,6 +311,7 @@ expression: checks
row: 50
column: 18
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -290,6 +321,7 @@ expression: checks
row: 51
column: 17
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -299,6 +331,7 @@ expression: checks
row: 52
column: 19
fix: ~
parent: ~
- kind:
HardcodedPasswordString: s3cr3t
location:
@@ -308,4 +341,35 @@ expression: checks
row: 53
column: 28
fix: ~
parent: ~
- kind:
HardcodedPasswordString: "1\n2"
location:
row: 55
column: 12
end_location:
row: 55
column: 18
fix: ~
parent: ~
- kind:
HardcodedPasswordString: "3\t4"
location:
row: 58
column: 12
end_location:
row: 58
column: 18
fix: ~
parent: ~
- kind:
HardcodedPasswordString: "5\r6"
location:
row: 61
column: 12
end_location:
row: 61
column: 18
fix: ~
parent: ~

View File

@@ -11,4 +11,5 @@ expression: checks
row: 13
column: 25
fix: ~
parent: ~

View File

@@ -11,6 +11,7 @@ expression: checks
row: 5
column: 37
fix: ~
parent: ~
- kind:
HardcodedPasswordDefault: posonly
location:
@@ -20,6 +21,7 @@ expression: checks
row: 13
column: 53
fix: ~
parent: ~
- kind:
HardcodedPasswordDefault: kwonly
location:
@@ -29,6 +31,7 @@ expression: checks
row: 21
column: 46
fix: ~
parent: ~
- kind:
HardcodedPasswordDefault: posonly
location:
@@ -38,6 +41,7 @@ expression: checks
row: 29
column: 47
fix: ~
parent: ~
- kind:
HardcodedPasswordDefault: kwonly
location:
@@ -47,4 +51,5 @@ expression: checks
row: 29
column: 69
fix: ~
parent: ~

View File

@@ -15,13 +15,12 @@ mod tests {
#[test_case(CheckCode::BLE001, Path::new("BLE.py"); "BLE001")]
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/flake8_blind_except")
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}

View File

@@ -11,6 +11,7 @@ expression: checks
row: 25
column: 20
fix: ~
parent: ~
- kind:
BlindExcept: Exception
location:
@@ -20,6 +21,7 @@ expression: checks
row: 31
column: 16
fix: ~
parent: ~
- kind:
BlindExcept: Exception
location:
@@ -29,6 +31,7 @@ expression: checks
row: 42
column: 16
fix: ~
parent: ~
- kind:
BlindExcept: BaseException
location:
@@ -38,6 +41,7 @@ expression: checks
row: 45
column: 24
fix: ~
parent: ~
- kind:
BlindExcept: Exception
location:
@@ -47,6 +51,7 @@ expression: checks
row: 54
column: 16
fix: ~
parent: ~
- kind:
BlindExcept: Exception
location:
@@ -56,6 +61,7 @@ expression: checks
row: 60
column: 16
fix: ~
parent: ~
- kind:
BlindExcept: BaseException
location:
@@ -65,4 +71,5 @@ expression: checks
row: 62
column: 20
fix: ~
parent: ~

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