Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc2110449c | ||
|
|
f6ca49e05f | ||
|
|
9a7331b2e2 | ||
|
|
4888afd423 | ||
|
|
0dc523b081 | ||
|
|
63772e335d | ||
|
|
7f4ff1e38f | ||
|
|
32ebc1d227 | ||
|
|
4ded155dc0 | ||
|
|
201e1250de | ||
|
|
102b049a32 | ||
|
|
74f49eda64 | ||
|
|
9da3e2cca1 | ||
|
|
e290050821 | ||
|
|
bc9ed0a4ef | ||
|
|
20b9b44973 | ||
|
|
6e5a553235 | ||
|
|
2a08a63f17 | ||
|
|
d4290e6721 | ||
|
|
51bda28a7d | ||
|
|
cc26051b7a | ||
|
|
3ac5a9aa31 | ||
|
|
451047c30d | ||
|
|
6907df489b | ||
|
|
970f882b03 | ||
|
|
3eff9a2860 | ||
|
|
a4a24a0ef3 | ||
|
|
48e3c046b0 | ||
|
|
03e4f5be8a | ||
|
|
99657b7d92 | ||
|
|
40377aa1fc | ||
|
|
2a37017e8c | ||
|
|
ff66d08cef | ||
|
|
dad8035eef | ||
|
|
bf5fec342c | ||
|
|
66a6c81ebf | ||
|
|
5c70f5044b | ||
|
|
953d141ab2 | ||
|
|
07dba46039 | ||
|
|
3b02da9d7b | ||
|
|
20234c6156 | ||
|
|
de767cc026 | ||
|
|
ce1663d302 | ||
|
|
f40e4bcd14 | ||
|
|
e7d40d435f | ||
|
|
ef8fe31c0c | ||
|
|
226f682c99 | ||
|
|
468ffd29fb | ||
|
|
a61126ab23 | ||
|
|
54c7c25861 | ||
|
|
eff7700d92 | ||
|
|
8934f6938d | ||
|
|
8f0fc3033a | ||
|
|
4107bc828d | ||
|
|
706d28cabc | ||
|
|
4da2264722 | ||
|
|
bf88c815aa | ||
|
|
8a4831dd5b | ||
|
|
b5ab492a70 | ||
|
|
1fc09ebd5c | ||
|
|
6cf047976c | ||
|
|
87465daacc | ||
|
|
a52bed7101 | ||
|
|
20ac823778 | ||
|
|
1028ed3565 | ||
|
|
98897db6ac | ||
|
|
5ce4262112 | ||
|
|
6b2359384d | ||
|
|
d3443d7c19 | ||
|
|
04b1e1de6f | ||
|
|
c93c85300f | ||
|
|
73ed6f8654 | ||
|
|
eb183645f3 | ||
|
|
ef17aa93da | ||
|
|
f366b0147f | ||
|
|
fc88fa35ff |
7
.github/workflows/ci.yaml
vendored
7
.github/workflows/ci.yaml
vendored
@@ -39,9 +39,12 @@ jobs:
|
||||
- run: ./target/release/ruff_dev generate-rules-table
|
||||
- run: ./target/release/ruff_dev generate-options
|
||||
- run: git diff --quiet README.md || echo "::error file=README.md::This file is outdated. You may have to rerun 'cargo dev generate-options' and/or 'cargo dev generate-rules-table'."
|
||||
- run: ./target/release/ruff_dev generate-check-code-prefix && cargo fmt -- src/checks_gen.rs
|
||||
- run: ./target/release/ruff_dev generate-check-code-prefix
|
||||
- run: git diff --quiet src/checks_gen.rs || echo "::error file=src/checks_gen.rs::This file is outdated. You may have to rerun 'cargo dev generate-check-code-prefix'."
|
||||
- run: git diff --exit-code -- README.md src/checks_gen.rs
|
||||
- run: ./target/release/ruff_dev generate-json-schema
|
||||
- run: git diff --quiet ruff.schema.json || echo "::error file=ruff.schema.json::This file is outdated. You may have to rerun 'cargo dev generate-json-schema'."
|
||||
- run: git diff --exit-code -- ruff.schema.json
|
||||
|
||||
cargo_fmt:
|
||||
name: "cargo fmt"
|
||||
@@ -131,7 +134,7 @@ jobs:
|
||||
override: true
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
python-version: "3.11"
|
||||
- run: pip install maturin
|
||||
- uses: actions/cache@v3
|
||||
env:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.185
|
||||
rev: v0.0.193
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ prior to merging.
|
||||
There are four phases to adding a new lint rule:
|
||||
|
||||
1. Define the rule in `src/checks.rs`.
|
||||
2. Define the _logic_ for triggering the rule in `src/check_ast.rs` (for AST-based checks),
|
||||
`src/check_tokens.rs` (for token-based checks), or `src/check_lines.rs` (for text-based checks).
|
||||
2. Define the _logic_ for triggering the rule in `src/checkers/ast.rs` (for AST-based checks),
|
||||
`src/checkers/tokens.rs` (for token-based checks), or `src/checkers/lines.rs` (for text-based checks).
|
||||
3. Add a test fixture.
|
||||
4. Update the generated files (documentation and generated code).
|
||||
|
||||
@@ -105,7 +105,8 @@ You may also want to add the new configuration option to the `flake8-to-ruff` to
|
||||
responsible for converting `flake8` configuration files to Ruff's TOML format. This logic
|
||||
lives in `flake8_to_ruff/src/converter.rs`.
|
||||
|
||||
To update the documentation for supported configuration options, run `cargo dev generate-options`.
|
||||
Run `cargo dev generate-options` to update the documentation for supported configuration options,
|
||||
and `cargo dev generate-json-schema` to update the JSON schema for `tool.ruff` in `pyproject.toml`.
|
||||
|
||||
## Release process
|
||||
|
||||
|
||||
78
Cargo.lock
generated
78
Cargo.lock
generated
@@ -644,6 +644,12 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.0"
|
||||
@@ -724,7 +730,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.185-dev.0"
|
||||
version = "0.0.193-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.29",
|
||||
@@ -735,6 +741,8 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"toml",
|
||||
]
|
||||
|
||||
@@ -1845,7 +1853,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.185"
|
||||
version = "0.0.193"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1886,8 +1894,10 @@ dependencies = [
|
||||
"rustpython-ast",
|
||||
"rustpython-common",
|
||||
"rustpython-parser",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"test-case",
|
||||
@@ -1901,7 +1911,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.185"
|
||||
version = "0.0.193"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.29",
|
||||
@@ -1913,13 +1923,15 @@ dependencies = [
|
||||
"rustpython-ast",
|
||||
"rustpython-common",
|
||||
"rustpython-parser",
|
||||
"schemars",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.185"
|
||||
version = "0.0.193"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1962,7 +1974,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-common",
|
||||
@@ -1972,7 +1984,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-common"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -1995,7 +2007,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -2012,7 +2024,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
@@ -2055,6 +2067,30 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
@@ -2104,16 +2140,36 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.89"
|
||||
name = "serde_derive_internals"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
|
||||
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellexpand"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd1c7ddea665294d484c39fd0c0d2b7e35bbfe10035c5fe1854741a57f6880e1"
|
||||
dependencies = [
|
||||
"dirs 4.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "2.2.1"
|
||||
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.185"
|
||||
version = "0.0.193"
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
|
||||
@@ -43,13 +43,15 @@ quick-junit = { version = "0.3.2" }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
|
||||
ruff_macros = { version = "0.0.185", path = "ruff_macros" }
|
||||
ruff_macros = { version = "0.0.193", path = "ruff_macros" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "1b6cb170e925a43d605b3fed9f6b878e63e47744" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "1b6cb170e925a43d605b3fed9f6b878e63e47744" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "1b6cb170e925a43d605b3fed9f6b878e63e47744" }
|
||||
schemars = { version = "0.8.11" }
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_json = { version = "1.0.87" }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.24.3" }
|
||||
textwrap = { version = "0.16.0" }
|
||||
|
||||
517
README.md
517
README.md
@@ -17,7 +17,7 @@ An extremely fast Python linter, written in Rust.
|
||||
|
||||
- ⚡️ 10-100x faster than existing linters
|
||||
- 🐍 Installable via `pip`
|
||||
- 🤝 Python 3.10 compatibility
|
||||
- 🤝 Python 3.11 compatibility
|
||||
- 🛠️ `pyproject.toml` support
|
||||
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
||||
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
@@ -39,9 +39,11 @@ 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)
|
||||
- [Saleor](https://github.com/saleor/saleor)
|
||||
- [Hatch](https://github.com/pypa/hatch)
|
||||
- [Jupyter Server](https://github.com/jupyter-server/jupyter_server)
|
||||
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
|
||||
- [Synapse](https://github.com/matrix-org/synapse)
|
||||
- [Ibis](https://github.com/ibis-project/ibis)
|
||||
- [Saleor](https://github.com/saleor/saleor)
|
||||
|
||||
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
|
||||
@@ -94,8 +96,9 @@ of [Conda](https://docs.conda.io/en/latest/):
|
||||
1. [flake8-simplify (SIM)](#flake8-simplify-sim)
|
||||
1. [flake8-tidy-imports (TID)](#flake8-tidy-imports-tid)
|
||||
1. [flake8-unused-arguments (ARG)](#flake8-unused-arguments-arg)
|
||||
1. [flake8-datetimez (DTZ)](#flake8-datetimez-dtz)
|
||||
1. [eradicate (ERA)](#eradicate-era)
|
||||
1. [pandas-vet (PDV)](#pandas-vet-pdv)
|
||||
1. [pandas-vet (PD)](#pandas-vet-pd)
|
||||
1. [pygrep-hooks (PGH)](#pygrep-hooks-pgh)
|
||||
1. [Pylint (PLC, PLE, PLR, PLW)](#pylint-plc-ple-plr-plw)
|
||||
1. [Ruff-specific rules (RUF)](#ruff-specific-rules-ruf)<!-- End auto-generated table of contents. -->
|
||||
@@ -118,6 +121,8 @@ Ruff is available as [`ruff`](https://pypi.org/project/ruff/) on PyPI:
|
||||
pip install ruff
|
||||
```
|
||||
|
||||
[](https://repology.org/project/ruff-python-linter/versions)
|
||||
|
||||
For **macOS Homebrew** and **Linuxbrew** users, Ruff is also available as [`ruff`](https://formulae.brew.sh/formula/ruff) on Homebrew:
|
||||
|
||||
```shell
|
||||
@@ -155,11 +160,13 @@ ruff path/to/code/ --watch
|
||||
Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.185
|
||||
hooks:
|
||||
- id: ruff
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.193'
|
||||
hooks:
|
||||
- id: ruff
|
||||
# Respect `exclude` and `extend-exclude` settings.
|
||||
args: ["--force-exclude"]
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@@ -311,6 +318,8 @@ Options:
|
||||
Show violations with source code
|
||||
--respect-gitignore
|
||||
Respect file exclusions via `.gitignore` and other standard ignore files
|
||||
--force-exclude
|
||||
Enforce exclusions, even for paths passed to Ruff directly on the command-line
|
||||
--show-files
|
||||
See the files Ruff will be run against with the current settings
|
||||
--show-settings
|
||||
@@ -329,6 +338,8 @@ Options:
|
||||
The name of the file when passing it through stdin
|
||||
--explain <EXPLAIN>
|
||||
Explain a rule
|
||||
--cache-dir <CACHE_DIR>
|
||||
Path to the cache directory
|
||||
-h, --help
|
||||
Print help information
|
||||
-V, --version
|
||||
@@ -354,7 +365,7 @@ There are a few exceptions to these rules:
|
||||
a default configuration. If a user-specific configuration file exists
|
||||
at `${config_dir}/ruff/pyproject.toml`,
|
||||
that file will be used instead of the default configuration, with `${config_dir}` being
|
||||
determined via the [`dirs](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html) crate, and all
|
||||
determined via the [`dirs`](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html) crate, and all
|
||||
relative paths being again resolved relative to the _current working directory_.
|
||||
4. Any `pyproject.toml`-supported settings that are provided on the command-line (e.g., via
|
||||
`--select`) will override the settings in _every_ resolved configuration file.
|
||||
@@ -514,6 +525,7 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| E401 | MultipleImportsOnOneLine | Multiple imports on one line | |
|
||||
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file | |
|
||||
| E501 | LineTooLong | Line too long (89 > 88 characters) | |
|
||||
| E711 | NoneComparison | Comparison to `None` should be `cond is None` | 🛠 |
|
||||
@@ -528,8 +540,8 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI
|
||||
| E743 | AmbiguousFunctionName | Ambiguous function name: `...` | |
|
||||
| E902 | IOError | IOError: `...` | |
|
||||
| E999 | SyntaxError | SyntaxError: `...` | |
|
||||
| W292 | NoNewLineAtEndOfFile | No newline at end of file | |
|
||||
| W605 | InvalidEscapeSequence | Invalid escape sequence: '\c' | |
|
||||
| W292 | NoNewLineAtEndOfFile | No newline at end of file | 🛠 |
|
||||
| W605 | InvalidEscapeSequence | Invalid escape sequence: '\c' | 🛠 |
|
||||
|
||||
### mccabe (C90)
|
||||
|
||||
@@ -620,6 +632,8 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
|
||||
| UP014 | ConvertNamedTupleFunctionalToClass | Convert `...` from `NamedTuple` functional to class syntax | 🛠 |
|
||||
| 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` | 🛠 |
|
||||
|
||||
### pep8-naming (N)
|
||||
|
||||
@@ -864,6 +878,22 @@ For more, see [flake8-unused-arguments](https://pypi.org/project/flake8-unused-a
|
||||
| ARG004 | UnusedStaticMethodArgument | Unused static method argument: `...` | |
|
||||
| ARG005 | UnusedLambdaArgument | Unused lambda argument: `...` | |
|
||||
|
||||
### flake8-datetimez (DTZ)
|
||||
|
||||
For more, see [flake8-datetimez](https://pypi.org/project/flake8-datetimez/20.10.0/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| DTZ001 | CallDatetimeWithoutTzinfo | The use of `datetime.datetime()` without `tzinfo` argument is not allowed | |
|
||||
| DTZ002 | CallDatetimeToday | The use of `datetime.datetime.today()` is not allowed | |
|
||||
| DTZ003 | CallDatetimeUtcnow | The use of `datetime.datetime.utcnow()` is not allowed | |
|
||||
| DTZ004 | CallDatetimeUtcfromtimestamp | The use of `datetime.datetime.utcfromtimestamp()` is not allowed | |
|
||||
| DTZ005 | CallDatetimeNowWithoutTzinfo | The use of `datetime.datetime.now()` without `tz` argument is not allowed | |
|
||||
| DTZ006 | CallDatetimeFromtimestamp | The use of `datetime.datetime.fromtimestamp()` without `tz` argument is not allowed | |
|
||||
| DTZ007 | CallDatetimeStrptimeWithoutZone | The use of `datetime.datetime.strptime()` without %z must be followed by `.replace(tzinfo=)` | |
|
||||
| DTZ011 | CallDateToday | The use of `datetime.date.today()` is not allowed. | |
|
||||
| DTZ012 | CallDateFromtimestamp | The use of `datetime.date.fromtimestamp()` is not allowed | |
|
||||
|
||||
### eradicate (ERA)
|
||||
|
||||
For more, see [eradicate](https://pypi.org/project/eradicate/2.1.0/) on PyPI.
|
||||
@@ -872,24 +902,24 @@ For more, see [eradicate](https://pypi.org/project/eradicate/2.1.0/) on PyPI.
|
||||
| ---- | ---- | ------- | --- |
|
||||
| ERA001 | CommentedOutCode | Found commented-out code | 🛠 |
|
||||
|
||||
### pandas-vet (PDV)
|
||||
### pandas-vet (PD)
|
||||
|
||||
For more, see [pandas-vet](https://pypi.org/project/pandas-vet/0.2.3/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| PDV002 | UseOfInplaceArgument | `inplace=True` should be avoided; it has inconsistent behavior | |
|
||||
| PDV003 | UseOfDotIsNull | `.isna` is preferred to `.isnull`; functionality is equivalent | |
|
||||
| PDV004 | UseOfDotNotNull | `.notna` is preferred to `.notnull`; functionality is equivalent | |
|
||||
| PDV007 | UseOfDotIx | ``ix` i` deprecated; use more explicit `.loc` o` `.iloc` | |
|
||||
| PDV008 | UseOfDotAt | Use `.loc` instead of `.at`. If speed is important, use numpy. | |
|
||||
| PDV009 | UseOfDotIat | Use `.iloc` instea` of `.iat`. If speed is important, use numpy. | |
|
||||
| PDV010 | UseOfDotPivotOrUnstack | `.pivot_table` is preferred to `.pivot` or `.unstack`; provides same functionality | |
|
||||
| PDV011 | UseOfDotValues | Use `.to_numpy()` instead of `.values` | |
|
||||
| PDV012 | UseOfDotReadTable | `.read_csv` is preferred to `.read_table`; provides same functionality | |
|
||||
| PDV013 | UseOfDotStack | `.melt` is preferred to `.stack`; provides same functionality | |
|
||||
| PDV015 | UseOfPdMerge | Use `.merge` method instead of `pd.merge` function. They have equivalent functionality. | |
|
||||
| PDV901 | DfIsABadVariableName | `df` is a bad variable name. Be kinder to your future self. | |
|
||||
| PD002 | UseOfInplaceArgument | `inplace=True` should be avoided; it has inconsistent behavior | |
|
||||
| PD003 | UseOfDotIsNull | `.isna` is preferred to `.isnull`; functionality is equivalent | |
|
||||
| PD004 | UseOfDotNotNull | `.notna` is preferred to `.notnull`; functionality is equivalent | |
|
||||
| PD007 | UseOfDotIx | `.ix` is deprecated; use more explicit `.loc` or `.iloc` | |
|
||||
| PD008 | UseOfDotAt | Use `.loc` instead of `.at`. If speed is important, use numpy. | |
|
||||
| PD009 | UseOfDotIat | Use `.iloc` instead of `.iat`. If speed is important, use numpy. | |
|
||||
| PD010 | UseOfDotPivotOrUnstack | `.pivot_table` is preferred to `.pivot` or `.unstack`; provides same functionality | |
|
||||
| PD011 | UseOfDotValues | Use `.to_numpy()` instead of `.values` | |
|
||||
| PD012 | UseOfDotReadTable | `.read_csv` is preferred to `.read_table`; provides same functionality | |
|
||||
| PD013 | UseOfDotStack | `.melt` is preferred to `.stack`; provides same functionality | |
|
||||
| PD015 | UseOfPdMerge | Use `.merge` method instead of `pd.merge` function. They have equivalent functionality. | |
|
||||
| PD901 | DfIsABadVariableName | `df` is a bad variable name. Be kinder to your future self. | |
|
||||
|
||||
### pygrep-hooks (PGH)
|
||||
|
||||
@@ -898,6 +928,8 @@ For more, see [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks) on GitH
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| PGH001 | NoEval | No builtin `eval()` allowed | |
|
||||
| PGH002 | DeprecatedLogWarn | `warn` is deprecated in favor of `warning` | |
|
||||
| PGH003 | BlanketTypeIgnore | Use specific error codes when ignoring type issues | |
|
||||
|
||||
### Pylint (PLC, PLE, PLR, PLW)
|
||||
|
||||
@@ -925,7 +957,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'?) | |
|
||||
| RUF100 | UnusedNOQA | Unused `noqa` directive | 🛠 |
|
||||
| RUF100 | UnusedNOQA | Unused blanket `noqa` directive | 🛠 |
|
||||
|
||||
<!-- End auto-generated sections. -->
|
||||
|
||||
@@ -933,21 +965,100 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
|
||||
|
||||
### VS Code (Official)
|
||||
|
||||
Download the [Ruff VS Code extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff).
|
||||
Download the [Ruff VS Code extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff),
|
||||
which supports autofix actions, import sorting, and more.
|
||||
|
||||
### Language Server Protocol
|
||||

|
||||
|
||||
Ruff is available as a [Language Server Protocol (LSP)](https://microsoft.github.io/language-server-protocol/)
|
||||
server, distributed as the [`python-lsp-ruff`](https://github.com/python-lsp/python-lsp-ruff) plugin
|
||||
for [`python-lsp-server`](https://github.com/python-lsp/python-lsp-server), both of which are
|
||||
installable via [PyPI](https://pypi.org/project/python-lsp-ruff/):
|
||||
### Language Server Protocol (Official)
|
||||
|
||||
Ruff supports the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/)
|
||||
via the [`ruff-lsp`](https://github.com/charliermarsh/ruff-lsp) Python package, available on
|
||||
[PyPI](https://pypi.org/project/ruff-lsp/).
|
||||
|
||||
[`ruff-lsp`](https://github.com/charliermarsh/ruff-lsp) enables Ruff to be used with any editor that
|
||||
supports the Language Server Protocol, including [Neovim](https://github.com/charliermarsh/ruff-lsp#example-neovim),
|
||||
[Sublime Text](https://github.com/charliermarsh/ruff-lsp#example-sublime-text), Emacs, and more.
|
||||
|
||||
For example, to use `ruff-lsp` with Neovim, install `ruff-lsp` from PyPI along with
|
||||
[`nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig). Then, add something like the following
|
||||
to your `init.lua`:
|
||||
|
||||
```lua
|
||||
-- See: https://github.com/neovim/nvim-lspconfig/tree/54eb2a070a4f389b1be0f98070f81d23e2b1a715#suggested-configuration
|
||||
local opts = { noremap=true, silent=true }
|
||||
vim.keymap.set('n', '<space>e', vim.diagnostic.open_float, opts)
|
||||
vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, opts)
|
||||
vim.keymap.set('n', ']d', vim.diagnostic.goto_next, opts)
|
||||
vim.keymap.set('n', '<space>q', vim.diagnostic.setloclist, opts)
|
||||
|
||||
-- Use an on_attach function to only map the following keys
|
||||
-- after the language server attaches to the current buffer
|
||||
local on_attach = function(client, bufnr)
|
||||
-- Enable completion triggered by <c-x><c-o>
|
||||
vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')
|
||||
|
||||
-- Mappings.
|
||||
-- See `:help vim.lsp.*` for documentation on any of the below functions
|
||||
local bufopts = { noremap=true, silent=true, buffer=bufnr }
|
||||
vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, bufopts)
|
||||
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, bufopts)
|
||||
vim.keymap.set('n', 'K', vim.lsp.buf.hover, bufopts)
|
||||
vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, bufopts)
|
||||
vim.keymap.set('n', '<C-k>', vim.lsp.buf.signature_help, bufopts)
|
||||
vim.keymap.set('n', '<space>wa', vim.lsp.buf.add_workspace_folder, bufopts)
|
||||
vim.keymap.set('n', '<space>wr', vim.lsp.buf.remove_workspace_folder, bufopts)
|
||||
vim.keymap.set('n', '<space>wl', function()
|
||||
print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
|
||||
end, bufopts)
|
||||
vim.keymap.set('n', '<space>D', vim.lsp.buf.type_definition, bufopts)
|
||||
vim.keymap.set('n', '<space>rn', vim.lsp.buf.rename, bufopts)
|
||||
vim.keymap.set('n', '<space>ca', vim.lsp.buf.code_action, bufopts)
|
||||
vim.keymap.set('n', 'gr', vim.lsp.buf.references, bufopts)
|
||||
vim.keymap.set('n', '<space>f', function() vim.lsp.buf.format { async = true } end, bufopts)
|
||||
end
|
||||
|
||||
-- Configure `ruff-lsp`.
|
||||
local configs = require 'lspconfig.configs'
|
||||
if not configs.ruff_lsp then
|
||||
configs.ruff_lsp = {
|
||||
default_config = {
|
||||
cmd = { 'ruff-lsp' },
|
||||
filetypes = { 'python' },
|
||||
root_dir = require('lspconfig').util.find_git_ancestor,
|
||||
init_options = {
|
||||
settings = {
|
||||
args = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
require('lspconfig').ruff_lsp.setup {
|
||||
on_attach = on_attach,
|
||||
}
|
||||
```
|
||||
|
||||
Upon successful installation, you should see Ruff's diagnostics surfaced directly in your editor:
|
||||
|
||||

|
||||
|
||||
To use `ruff-lsp` with other editors, including Sublime Text and Helix, see the [`ruff-lsp` documentation](https://github.com/charliermarsh/ruff-lsp#installation-and-usage).
|
||||
|
||||
### Language Server Protocol (Unofficial)
|
||||
|
||||
Ruff is also available as the [`python-lsp-ruff`](https://github.com/python-lsp/python-lsp-ruff)
|
||||
plugin for [`python-lsp-server`](https://github.com/python-lsp/python-lsp-ruff), both of which are
|
||||
installable from PyPI:
|
||||
|
||||
```shell
|
||||
pip install python-lsp-server python-lsp-ruff
|
||||
```
|
||||
|
||||
The LSP server can be used with any editor that supports the Language Server Protocol. For example,
|
||||
to use it with Neovim, you would add something like the following to your `init.lua`:
|
||||
The LSP server can then be used with any editor that supports the Language Server Protocol.
|
||||
|
||||
For example, to use `python-lsp-ruff` with Neovim, add something like the following to your
|
||||
`init.lua`:
|
||||
|
||||
```lua
|
||||
require'lspconfig'.pylsp.setup {
|
||||
@@ -956,6 +1067,15 @@ require'lspconfig'.pylsp.setup {
|
||||
plugins = {
|
||||
ruff = {
|
||||
enabled = true
|
||||
},
|
||||
pycodestyle = {
|
||||
enabled = false
|
||||
},
|
||||
pyflakes = {
|
||||
enabled = false
|
||||
},
|
||||
mccabe = {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -963,27 +1083,15 @@ require'lspconfig'.pylsp.setup {
|
||||
}
|
||||
```
|
||||
|
||||
[`ruffd`](https://github.com/Seamooo/ruffd) is another implementation of the Language Server
|
||||
Protocol (LSP) for Ruff, written in Rust.
|
||||
|
||||
### PyCharm
|
||||
|
||||
Ruff can be installed as an [External Tool](https://www.jetbrains.com/help/pycharm/configuring-third-party-tools.html)
|
||||
in PyCharm. Open the Preferences pane, then navigate to "Tools", then "External Tools". From there,
|
||||
add a new tool with the following configuration:
|
||||
|
||||

|
||||
|
||||
Ruff should then appear as a runnable action:
|
||||
|
||||

|
||||
|
||||
### Vim & Neovim
|
||||
|
||||
Ruff can be integrated into any editor that supports the Language Server Protocol (LSP) (see:
|
||||
[Language Server Protocol](#language-server-protocol)).
|
||||
Ruff can be integrated into any editor that supports the Language Server Protocol via [`ruff-lsp`](https://github.com/charliermarsh/ruff-lsp)
|
||||
(see: [Language Server Protocol](#language-server-protocol-official)), including Vim and Neovim.
|
||||
|
||||
Ruff is also available as part of the [coc-pyright](https://github.com/fannheyward/coc-pyright)
|
||||
It's recommended that you use [`ruff-lsp`](https://github.com/charliermarsh/ruff-lsp), the
|
||||
officially supported LSP server for Ruff.
|
||||
|
||||
However, Ruff is also available as part of the [coc-pyright](https://github.com/fannheyward/coc-pyright)
|
||||
extension for `coc.nvim`.
|
||||
|
||||
<details>
|
||||
@@ -1000,7 +1108,6 @@ tools:
|
||||
format-command: 'ruff --stdin-filename ${INPUT} --config ~/myconfigs/linters/ruff.toml --fix --exit-zero --quiet -'
|
||||
format-stdin: true
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
@@ -1037,9 +1144,26 @@ null_ls.setup({
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
### PyCharm (External Tool)
|
||||
|
||||
Ruff can be installed as an [External Tool](https://www.jetbrains.com/help/pycharm/configuring-third-party-tools.html)
|
||||
in PyCharm. Open the Preferences pane, then navigate to "Tools", then "External Tools". From there,
|
||||
add a new tool with the following configuration:
|
||||
|
||||

|
||||
|
||||
Ruff should then appear as a runnable action:
|
||||
|
||||

|
||||
|
||||
### PyCharm (Unofficial)
|
||||
|
||||
Ruff is also available as the [Ruff](https://plugins.jetbrains.com/plugin/20574-ruff) plugin on the
|
||||
IntelliJ Marketplace (maintained by @koxudaxi).
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
GitHub Actions has everything you need to run Ruff out-of-the-box:
|
||||
@@ -1055,7 +1179,7 @@ jobs:
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
python-version: "3.11"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
@@ -1097,6 +1221,7 @@ natively, including:
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
|
||||
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-datetimez`](https://pypi.org/project/flake8-datetimez/)
|
||||
- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/)
|
||||
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
|
||||
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
|
||||
@@ -1111,8 +1236,8 @@ natively, including:
|
||||
- [`mccabe`](https://pypi.org/project/mccabe/)
|
||||
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
|
||||
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
|
||||
- [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (1/10)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33)
|
||||
- [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (18/33)
|
||||
- [`yesqa`](https://github.com/asottile/yesqa)
|
||||
|
||||
Note that, in some cases, Ruff uses different error code prefixes than would be found in the
|
||||
@@ -1151,6 +1276,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
|
||||
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-datetimez`](https://pypi.org/project/flake8-datetimez/)
|
||||
- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/)
|
||||
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
|
||||
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
|
||||
@@ -1167,8 +1293,8 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
|
||||
Ruff can also replace [`isort`](https://pypi.org/project/isort/),
|
||||
[`yesqa`](https://github.com/asottile/yesqa), [`eradicate`](https://pypi.org/project/eradicate/),
|
||||
[`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (1/10), and a subset of the rules
|
||||
implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33).
|
||||
[`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10), and a subset of the rules
|
||||
implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (17/33).
|
||||
|
||||
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
|
||||
|
||||
@@ -1455,8 +1581,8 @@ Summary
|
||||
|
||||
#### [`allowed-confusables`](#allowed-confusables)
|
||||
|
||||
A list of allowed "confusable" Unicode characters to ignore when enforcing `RUF001`,
|
||||
`RUF002`, and `RUF003`.
|
||||
A list of allowed "confusable" Unicode characters to ignore when
|
||||
enforcing `RUF001`, `RUF002`, and `RUF003`.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
@@ -1473,11 +1599,37 @@ allowed-confusables = ["−", "ρ", "∗"]
|
||||
|
||||
---
|
||||
|
||||
#### [`cache-dir`](#cache-dir)
|
||||
|
||||
A path to the cache directory.
|
||||
|
||||
By default, Ruff stores cache results in a `.ruff_cache` directory in
|
||||
the current project root.
|
||||
|
||||
However, Ruff will also respect the `RUFF_CACHE_DIR` environment
|
||||
variable, which takes precedence over that default.
|
||||
|
||||
This setting will override even the `RUFF_CACHE_DIR` environment
|
||||
variable, if set.
|
||||
|
||||
**Default value**: `.ruff_cache`
|
||||
|
||||
**Type**: `PathBuf`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
cache-dir = "~/.cache/ruff"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`dummy-variable-rgx`](#dummy-variable-rgx)
|
||||
|
||||
A regular expression used to identify "dummy" variables, or those which should be
|
||||
ignored when evaluating (e.g.) unused-variable checks. The default expression matches
|
||||
`_`, `__`, and `_var`, but not `_var_`.
|
||||
A regular expression used to identify "dummy" variables, or those which
|
||||
should be ignored when evaluating (e.g.) unused-variable checks. The
|
||||
default expression matches `_`, `__`, and `_var`, but not `_var_`.
|
||||
|
||||
**Default value**: `"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"`
|
||||
|
||||
@@ -1488,7 +1640,7 @@ ignored when evaluating (e.g.) unused-variable checks. The default expression ma
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Only ignore variables named "_".
|
||||
dummy_variable_rgx = "^_$"
|
||||
dummy-variable-rgx = "^_$"
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1499,15 +1651,16 @@ A list of file patterns to exclude from linting.
|
||||
|
||||
Exclusions are based on globs, and can be either:
|
||||
|
||||
- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the
|
||||
tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching
|
||||
`foo_*.py` ).
|
||||
- Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py`
|
||||
(to exclude any Python files in `directory`). Note that these paths are relative to the
|
||||
project root (e.g., the directory containing your `pyproject.toml`).
|
||||
- Single-path patterns, like `.mypy_cache` (to exclude any directory
|
||||
named `.mypy_cache` in the tree), `foo.py` (to exclude any file named
|
||||
`foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ).
|
||||
- Relative patterns, like `directory/foo.py` (to exclude that specific
|
||||
file) or `directory/*.py` (to exclude any Python files in
|
||||
`directory`). Note that these paths are relative to the project root
|
||||
(e.g., the directory containing your `pyproject.toml`).
|
||||
|
||||
Note that you'll typically want to use [`extend-exclude`](#extend-exclude) to modify
|
||||
the excluded paths.
|
||||
Note that you'll typically want to use
|
||||
[`extend-exclude`](#extend-exclude) to modify the excluded paths.
|
||||
|
||||
**Default value**: `[".bzr", ".direnv", ".eggs", ".git", ".hg", ".mypy_cache", ".nox", ".pants.d", ".ruff_cache", ".svn", ".tox", ".venv", "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "venv"]`
|
||||
|
||||
@@ -1524,11 +1677,13 @@ exclude = [".venv"]
|
||||
|
||||
#### [`extend`](#extend)
|
||||
|
||||
A path to a local `pyproject.toml` file to merge into this configuration.
|
||||
A path to a local `pyproject.toml` file to merge into this
|
||||
configuration. User home directory and environment variables will be
|
||||
expanded.
|
||||
|
||||
To resolve the current `pyproject.toml` file, Ruff will first resolve this base
|
||||
configuration file, then merge in any properties defined in the current configuration
|
||||
file.
|
||||
To resolve the current `pyproject.toml` file, Ruff will first resolve
|
||||
this base configuration file, then merge in any properties defined
|
||||
in the current configuration file.
|
||||
|
||||
**Default value**: `None`
|
||||
|
||||
@@ -1548,7 +1703,8 @@ line-length = 100
|
||||
|
||||
#### [`extend-exclude`](#extend-exclude)
|
||||
|
||||
A list of file patterns to omit from linting, in addition to those specified by `exclude`.
|
||||
A list of file patterns to omit from linting, in addition to those
|
||||
specified by `exclude`.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
@@ -1566,7 +1722,8 @@ extend-exclude = ["tests", "src/bad.py"]
|
||||
|
||||
#### [`extend-ignore`](#extend-ignore)
|
||||
|
||||
A list of check code prefixes to ignore, in addition to those specified by `ignore`.
|
||||
A list of check code prefixes to ignore, in addition to those specified
|
||||
by `ignore`.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
@@ -1584,7 +1741,8 @@ extend-ignore = ["F841"]
|
||||
|
||||
#### [`extend-select`](#extend-select)
|
||||
|
||||
A list of check code prefixes to enable, in addition to those specified by `select`.
|
||||
A list of check code prefixes to enable, in addition to those specified
|
||||
by `select`.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
@@ -1602,9 +1760,10 @@ extend-select = ["B", "Q"]
|
||||
|
||||
#### [`external`](#external)
|
||||
|
||||
A list of check codes that are unsupported by Ruff, but should be preserved when (e.g.)
|
||||
validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not
|
||||
yet implemented in Ruff.
|
||||
A list of check codes that are unsupported by Ruff, but should be
|
||||
preserved when (e.g.) validating `# noqa` directives. Useful for
|
||||
retaining `# noqa` directives that cover plugins not yet implemented
|
||||
in Ruff.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
@@ -1657,11 +1816,38 @@ fixable = ["E", "F"]
|
||||
|
||||
---
|
||||
|
||||
#### [`force-exclude`](#force-exclude)
|
||||
|
||||
Whether to enforce `exclude` and `extend-exclude` patterns, even for
|
||||
paths that are passed to Ruff explicitly. Typically, Ruff will lint
|
||||
any paths passed in directly, even if they would typically be
|
||||
excluded. Setting `force-exclude = true` will cause Ruff to
|
||||
respect these exclusions unequivocally.
|
||||
|
||||
This is useful for [`pre-commit`](https://pre-commit.com/), which explicitly passes all
|
||||
changed files to the [`ruff-pre-commit`](https://github.com/charliermarsh/ruff-pre-commit)
|
||||
plugin, regardless of whether they're marked as excluded by Ruff's own
|
||||
settings.
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
force-exclude = true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`format`](#format)
|
||||
|
||||
The style in which violation messages should be formatted: `"text"` (default),
|
||||
`"grouped"` (group messages by file), `"json"` (machine-readable), `"junit"`
|
||||
(machine-readable XML), or `"github"` (GitHub Actions annotations).
|
||||
The style in which violation messages should be formatted: `"text"`
|
||||
(default), `"grouped"` (group messages by file), `"json"`
|
||||
(machine-readable), `"junit"` (machine-readable XML), or `"github"`
|
||||
(GitHub Actions annotations).
|
||||
|
||||
**Default value**: `"text"`
|
||||
|
||||
@@ -1679,11 +1865,13 @@ format = "grouped"
|
||||
|
||||
#### [`ignore`](#ignore)
|
||||
|
||||
A list of check code prefixes to ignore. Prefixes can specify exact checks (like
|
||||
`F841`), entire categories (like `F`), or anything in between.
|
||||
A list of check code prefixes to ignore. Prefixes can specify exact
|
||||
checks (like `F841`), entire categories (like `F`), or anything in
|
||||
between.
|
||||
|
||||
When breaking ties between enabled and disabled checks (via `select` and `ignore`,
|
||||
respectively), more specific prefixes override less specific prefixes.
|
||||
When breaking ties between enabled and disabled checks (via `select` and
|
||||
`ignore`, respectively), more specific prefixes override less
|
||||
specific prefixes.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
@@ -1701,10 +1889,11 @@ ignore = ["F841"]
|
||||
|
||||
#### [`ignore-init-module-imports`](#ignore-init-module-imports)
|
||||
|
||||
Avoid automatically removing unused imports in `__init__.py` files. Such imports will
|
||||
still be +flagged, but with a dedicated message suggesting that the import is either
|
||||
added to the module' +`__all__` symbol, or re-exported with a redundant alias (e.g.,
|
||||
`import os as os`).
|
||||
Avoid automatically removing unused imports in `__init__.py` files. Such
|
||||
imports will still be +flagged, but with a dedicated message
|
||||
suggesting that the import is either added to the module' +`__all__`
|
||||
symbol, or re-exported with a redundant alias (e.g., `import os as
|
||||
os`).
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
@@ -1721,7 +1910,8 @@ ignore-init-module-imports = true
|
||||
|
||||
#### [`line-length`](#line-length)
|
||||
|
||||
The line length to use when enforcing long-lines violations (like E501).
|
||||
The line length to use when enforcing long-lines violations (like
|
||||
`E501`).
|
||||
|
||||
**Default value**: `88`
|
||||
|
||||
@@ -1739,8 +1929,8 @@ line-length = 120
|
||||
|
||||
#### [`per-file-ignores`](#per-file-ignores)
|
||||
|
||||
A list of mappings from file pattern to check code prefixes to exclude, when considering
|
||||
any matching files.
|
||||
A list of mappings from file pattern to check code prefixes to exclude,
|
||||
when considering any matching files.
|
||||
|
||||
**Default value**: `{}`
|
||||
|
||||
@@ -1760,8 +1950,9 @@ any matching files.
|
||||
|
||||
#### [`respect-gitignore`](#respect-gitignore)
|
||||
|
||||
Whether to automatically exclude files that are ignored by `.ignore`, `.gitignore`,
|
||||
`.git/info/exclude`, and global `gitignore` files. Enabled by default.
|
||||
Whether to automatically exclude files that are ignored by `.ignore`,
|
||||
`.gitignore`, `.git/info/exclude`, and global `gitignore` files.
|
||||
Enabled by default.
|
||||
|
||||
**Default value**: `true`
|
||||
|
||||
@@ -1778,11 +1969,13 @@ respect_gitignore = false
|
||||
|
||||
#### [`select`](#select)
|
||||
|
||||
A list of check code prefixes to enable. Prefixes can specify exact checks (like
|
||||
`F841`), entire categories (like `F`), or anything in between.
|
||||
A list of check code prefixes to enable. Prefixes can specify exact
|
||||
checks (like `F841`), entire categories (like `F`), or anything in
|
||||
between.
|
||||
|
||||
When breaking ties between enabled and disabled checks (via `select` and `ignore`,
|
||||
respectively), more specific prefixes override less specific prefixes.
|
||||
When breaking ties between enabled and disabled checks (via `select` and
|
||||
`ignore`, respectively), more specific prefixes override less
|
||||
specific prefixes.
|
||||
|
||||
**Default value**: `["E", "F"]`
|
||||
|
||||
@@ -1800,8 +1993,8 @@ select = ["E", "F", "B", "Q"]
|
||||
|
||||
#### [`show-source`](#show-source)
|
||||
|
||||
Whether to show source code snippets when reporting lint error violations (overridden by
|
||||
the `--show-source` command-line flag).
|
||||
Whether to show source code snippets when reporting lint error
|
||||
violations (overridden by the `--show-source` command-line flag).
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
@@ -1819,7 +2012,8 @@ show-source = true
|
||||
|
||||
#### [`src`](#src)
|
||||
|
||||
The source code paths to consider, e.g., when resolving first- vs. third-party imports.
|
||||
The source code paths to consider, e.g., when resolving first- vs.
|
||||
third-party imports.
|
||||
|
||||
As an example: given a Python package structure like:
|
||||
|
||||
@@ -1833,12 +2027,15 @@ my_package/
|
||||
bar.py
|
||||
```
|
||||
|
||||
The `src` directory should be included in `source` (e.g., `source = ["src"]`), such that
|
||||
when resolving imports, `my_package.foo` is considered a first-party import.
|
||||
The `src` directory should be included in `source` (e.g., `source =
|
||||
["src"]`), such that when resolving imports, `my_package.foo` is
|
||||
considered a first-party import.
|
||||
|
||||
This field supports globs. For example, if you have a series of Python packages in
|
||||
a `python_modules` directory, `src = ["python_modules/*"]` would expand to incorporate
|
||||
all of the packages in that directory.
|
||||
This field supports globs. For example, if you have a series of Python
|
||||
packages in a `python_modules` directory, `src =
|
||||
["python_modules/*"]` would expand to incorporate all of the
|
||||
packages in that directory. User home directory and environment
|
||||
variables will also be expanded.
|
||||
|
||||
**Default value**: `["."]`
|
||||
|
||||
@@ -1856,9 +2053,10 @@ src = ["src", "test"]
|
||||
|
||||
#### [`target-version`](#target-version)
|
||||
|
||||
The Python version to target, e.g., when considering automatic code upgrades, like
|
||||
rewriting type annotations. Note that the target version will _not_ be inferred from the
|
||||
_current_ Python version, and instead must be specified explicitly (as seen below).
|
||||
The Python version to target, e.g., when considering automatic code
|
||||
upgrades, like rewriting type annotations. Note that the target
|
||||
version will _not_ be inferred from the _current_ Python version,
|
||||
and instead must be specified explicitly (as seen below).
|
||||
|
||||
**Default value**: `"py310"`
|
||||
|
||||
@@ -1896,7 +2094,8 @@ unfixable = ["F401"]
|
||||
|
||||
#### [`allow-star-arg-any`](#allow-star-arg-any)
|
||||
|
||||
Whether to suppress `ANN401` for dynamically typed `*args` and `**kwargs` arguments.
|
||||
Whether to suppress `ANN401` for dynamically typed `*args` and
|
||||
`**kwargs` arguments.
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
@@ -1913,8 +2112,8 @@ allow-star-arg-any = true
|
||||
|
||||
#### [`mypy-init-return`](#mypy-init-return)
|
||||
|
||||
Whether to allow the omission of a return type hint for `__init__` if at least one
|
||||
argument is annotated.
|
||||
Whether to allow the omission of a return type hint for `__init__` if at
|
||||
least one argument is annotated.
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
@@ -1931,8 +2130,8 @@ mypy-init-return = true
|
||||
|
||||
#### [`suppress-dummy-args`](#suppress-dummy-args)
|
||||
|
||||
Whether to suppress `ANN000`-level errors for arguments matching the "dummy" variable
|
||||
regex (like `_`).
|
||||
Whether to suppress `ANN000`-level errors for arguments matching the
|
||||
"dummy" variable regex (like `_`).
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
@@ -1949,11 +2148,12 @@ suppress-dummy-args = true
|
||||
|
||||
#### [`suppress-none-returning`](#suppress-none-returning)
|
||||
|
||||
Whether to suppress `ANN200`-level errors for functions that meet either of the
|
||||
following criteria:
|
||||
Whether to suppress `ANN200`-level errors for functions that meet either
|
||||
of the following criteria:
|
||||
|
||||
- Contain no `return` statement.
|
||||
- Explicit `return` statement(s) all return `None` (explicitly or implicitly).
|
||||
- Explicit `return` statement(s) all return `None` (explicitly or
|
||||
implicitly).
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
@@ -1972,8 +2172,8 @@ suppress-none-returning = true
|
||||
|
||||
#### [`extend-immutable-calls`](#extend-immutable-calls)
|
||||
|
||||
Additional callable functions to consider "immutable" when evaluating, e.g.,
|
||||
`no-mutable-default-argument` checks (`B006`).
|
||||
Additional callable functions to consider "immutable" when evaluating,
|
||||
e.g., `no-mutable-default-argument` checks (`B006`).
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
@@ -2012,7 +2212,8 @@ max-string-length = 20
|
||||
|
||||
#### [`aliases`](#aliases)
|
||||
|
||||
The conventional aliases for imports. These aliases can be extended by the `extend_aliases` option.
|
||||
The conventional aliases for imports. These aliases can be extended by
|
||||
the `extend_aliases` option.
|
||||
|
||||
**Default value**: `{"altair": "alt", "matplotlib.pyplot": "plt", "numpy": "np", "pandas": "pd", "seaborn": "sns"}`
|
||||
|
||||
@@ -2034,7 +2235,8 @@ seaborn = "sns"
|
||||
|
||||
#### [`extend-aliases`](#extend-aliases)
|
||||
|
||||
A mapping of modules to their conventional import aliases. These aliases will be added to the `aliases` mapping.
|
||||
A mapping of modules to their conventional import aliases. These aliases
|
||||
will be added to the `aliases` mapping.
|
||||
|
||||
**Default value**: `{}`
|
||||
|
||||
@@ -2054,8 +2256,8 @@ A mapping of modules to their conventional import aliases. These aliases will be
|
||||
|
||||
#### [`avoid-escape`](#avoid-escape)
|
||||
|
||||
Whether to avoid using single quotes if a string contains single quotes, or vice-versa
|
||||
with double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes).
|
||||
Whether to avoid using single quotes if a string contains single quotes,
|
||||
or vice-versa with double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes).
|
||||
This minimizes the need to escape quotation marks within strings.
|
||||
|
||||
**Default value**: `true`
|
||||
@@ -2074,7 +2276,8 @@ avoid-escape = false
|
||||
|
||||
#### [`docstring-quotes`](#docstring-quotes)
|
||||
|
||||
Quote style to prefer for docstrings (either "single" (`'`) or "double" (`"`)).
|
||||
Quote style to prefer for docstrings (either "single" (`'`) or "double"
|
||||
(`"`)).
|
||||
|
||||
**Default value**: `"double"`
|
||||
|
||||
@@ -2091,7 +2294,8 @@ docstring-quotes = "single"
|
||||
|
||||
#### [`inline-quotes`](#inline-quotes)
|
||||
|
||||
Quote style to prefer for inline strings (either "single" (`'`) or "double" (`"`)).
|
||||
Quote style to prefer for inline strings (either "single" (`'`) or
|
||||
"double" (`"`)).
|
||||
|
||||
**Default value**: `"double"`
|
||||
|
||||
@@ -2108,7 +2312,8 @@ inline-quotes = "single"
|
||||
|
||||
#### [`multiline-quotes`](#multiline-quotes)
|
||||
|
||||
Quote style to prefer for multiline strings (either "single" (`'`) or "double" (`"`)).
|
||||
Quote style to prefer for multiline strings (either "single" (`'`) or
|
||||
"double" (`"`)).
|
||||
|
||||
**Default value**: `"double"`
|
||||
|
||||
@@ -2127,8 +2332,8 @@ multiline-quotes = "single"
|
||||
|
||||
#### [`ban-relative-imports`](#ban-relative-imports)
|
||||
|
||||
Whether to ban all relative imports (`"all"`), or only those imports that extend into
|
||||
the parent module and beyond (`"parents"`).
|
||||
Whether to ban all relative imports (`"all"`), or only those imports
|
||||
that extend into the parent module and beyond (`"parents"`).
|
||||
|
||||
**Default value**: `"parents"`
|
||||
|
||||
@@ -2185,8 +2390,8 @@ combine-as-imports = true
|
||||
|
||||
#### [`extra-standard-library`](#extra-standard-library)
|
||||
|
||||
A list of modules to consider standard-library, in addition to those known to Ruff in
|
||||
advance.
|
||||
A list of modules to consider standard-library, in addition to those
|
||||
known to Ruff in advance.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
@@ -2203,9 +2408,10 @@ extra-standard-library = ["path"]
|
||||
|
||||
#### [`force-wrap-aliases`](#force-wrap-aliases)
|
||||
|
||||
Force `import from` statements with multiple members and at least one alias (e.g.,
|
||||
`import A as B`) to wrap such that every line contains exactly one member. For example,
|
||||
this formatting would be retained, rather than condensing to a single line:
|
||||
Force `import from` statements with multiple members and at least one
|
||||
alias (e.g., `import A as B`) to wrap such that every line contains
|
||||
exactly one member. For example, this formatting would be retained,
|
||||
rather than condensing to a single line:
|
||||
|
||||
```py
|
||||
from .utils import (
|
||||
@@ -2214,9 +2420,10 @@ from .utils import (
|
||||
)
|
||||
```
|
||||
|
||||
Note that this setting is only effective when combined with `combine-as-imports = true`.
|
||||
When `combine-as-imports` isn't enabled, every aliased `import from` will be given its
|
||||
own line, in which case, wrapping is not necessary.
|
||||
Note that this setting is only effective when combined with
|
||||
`combine-as-imports = true`. When `combine-as-imports` isn't
|
||||
enabled, every aliased `import from` will be given its own line, in
|
||||
which case, wrapping is not necessary.
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
@@ -2234,8 +2441,8 @@ combine-as-imports = true
|
||||
|
||||
#### [`known-first-party`](#known-first-party)
|
||||
|
||||
A list of modules to consider first-party, regardless of whether they can be identified
|
||||
as such via introspection of the local filesystem.
|
||||
A list of modules to consider first-party, regardless of whether they
|
||||
can be identified as such via introspection of the local filesystem.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
@@ -2252,8 +2459,8 @@ known-first-party = ["src"]
|
||||
|
||||
#### [`known-third-party`](#known-third-party)
|
||||
|
||||
A list of modules to consider third-party, regardless of whether they can be identified
|
||||
as such via introspection of the local filesystem.
|
||||
A list of modules to consider third-party, regardless of whether they
|
||||
can be identified as such via introspection of the local filesystem.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
@@ -2292,9 +2499,10 @@ max-complexity = 5
|
||||
|
||||
#### [`classmethod-decorators`](#classmethod-decorators)
|
||||
|
||||
A list of decorators that, when applied to a method, indicate that the method should be
|
||||
treated as a class method. For example, Ruff will expect that any method decorated by a
|
||||
decorator in this list takes a `cls` argument as its first argument.
|
||||
A list of decorators that, when applied to a method, indicate that the
|
||||
method should be treated as a class method. For example, Ruff will
|
||||
expect that any method decorated by a decorator in this list takes a
|
||||
`cls` argument as its first argument.
|
||||
|
||||
**Default value**: `["classmethod"]`
|
||||
|
||||
@@ -2329,9 +2537,10 @@ ignore-names = ["callMethod"]
|
||||
|
||||
#### [`staticmethod-decorators`](#staticmethod-decorators)
|
||||
|
||||
A list of decorators that, when applied to a method, indicate that the method should be
|
||||
treated as a static method. For example, Ruff will expect that any method decorated by a
|
||||
decorator in this list has no `self` or `cls` argument.
|
||||
A list of decorators that, when applied to a method, indicate that the
|
||||
method should be treated as a static method. For example, Ruff will
|
||||
expect that any method decorated by a decorator in this list has no
|
||||
`self` or `cls` argument.
|
||||
|
||||
**Default value**: `["staticmethod"]`
|
||||
|
||||
@@ -2351,7 +2560,11 @@ staticmethod-decorators = ["staticmethod", "stcmthd"]
|
||||
|
||||
#### [`keep-runtime-typing`](#keep-runtime-typing)
|
||||
|
||||
Whether to avoid PEP 585 (`List[int]` -> `list[int]`) and PEP 604 (`Optional[str]` -> `str | None`) rewrites even if a file imports `from __future__ import annotations`. Note that this setting is only applicable when the target Python version is below 3.9 and 3.10 respectively.
|
||||
Whether to avoid PEP 585 (`List[int]` -> `list[int]`) and PEP 604
|
||||
(`Optional[str]` -> `str | None`) rewrites even if a file imports `from
|
||||
__future__ import annotations`. Note that this setting is only
|
||||
applicable when the target Python version is below 3.9 and 3.10
|
||||
respectively.
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
|
||||
12
flake8_to_ruff/Cargo.lock
generated
12
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.185"
|
||||
version = "0.0.193"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.185"
|
||||
version = "0.0.193"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -2028,7 +2028,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-common",
|
||||
@@ -2038,7 +2038,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-common"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -2061,7 +2061,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -2078,7 +2078,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.185-dev.0"
|
||||
version = "0.0.193-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -16,6 +16,8 @@ ruff = { path = "..", default-features = false }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_json = { version = "1.0.87" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.24.3" }
|
||||
toml = { version = "0.5.9" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
65
flake8_to_ruff/examples/cryptography/pyproject.toml
Normal file
65
flake8_to_ruff/examples/cryptography/pyproject.toml
Normal file
@@ -0,0 +1,65 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
# The minimum setuptools version is specific to the PEP 517 backend,
|
||||
# and may be stricter than the version required in `setup.cfg`
|
||||
"setuptools>=40.6.0,!=60.9.0",
|
||||
"wheel",
|
||||
# Must be kept in sync with the `install_requirements` in `setup.cfg`
|
||||
"cffi>=1.12; platform_python_implementation != 'PyPy'",
|
||||
"setuptools-rust>=0.11.4",
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.black]
|
||||
line-length = 79
|
||||
target-version = ["py36"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "-r s --capture=no --strict-markers --benchmark-disable"
|
||||
markers = [
|
||||
"skip_fips: this test is not executed in FIPS mode",
|
||||
"supported: parametrized test requiring only_if and skip_message",
|
||||
]
|
||||
|
||||
[tool.mypy]
|
||||
show_error_codes = true
|
||||
check_untyped_defs = true
|
||||
no_implicit_reexport = true
|
||||
warn_redundant_casts = true
|
||||
warn_unused_ignores = true
|
||||
warn_unused_configs = true
|
||||
strict_equality = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = [
|
||||
"pretend"
|
||||
]
|
||||
ignore_missing_imports = true
|
||||
|
||||
[tool.coverage.run]
|
||||
branch = true
|
||||
relative_files = true
|
||||
source = [
|
||||
"cryptography",
|
||||
"tests/",
|
||||
]
|
||||
|
||||
[tool.coverage.paths]
|
||||
source = [
|
||||
"src/cryptography",
|
||||
"*.tox/*/lib*/python*/site-packages/cryptography",
|
||||
"*.tox\\*\\Lib\\site-packages\\cryptography",
|
||||
"*.tox/pypy/site-packages/cryptography",
|
||||
]
|
||||
tests =[
|
||||
"tests/",
|
||||
"*tests\\",
|
||||
]
|
||||
|
||||
[tool.coverage.report]
|
||||
exclude_lines = [
|
||||
"@abc.abstractmethod",
|
||||
"@abc.abstractproperty",
|
||||
"@typing.overload",
|
||||
"if typing.TYPE_CHECKING",
|
||||
]
|
||||
91
flake8_to_ruff/examples/cryptography/setup.cfg
Normal file
91
flake8_to_ruff/examples/cryptography/setup.cfg
Normal file
@@ -0,0 +1,91 @@
|
||||
[metadata]
|
||||
name = cryptography
|
||||
version = attr: cryptography.__version__
|
||||
description = cryptography is a package which provides cryptographic recipes and primitives to Python developers.
|
||||
long_description = file: README.rst
|
||||
long_description_content_type = text/x-rst
|
||||
license = BSD-3-Clause OR Apache-2.0
|
||||
url = https://github.com/pyca/cryptography
|
||||
author = The Python Cryptographic Authority and individual contributors
|
||||
author_email = cryptography-dev@python.org
|
||||
project_urls =
|
||||
Documentation=https://cryptography.io/
|
||||
Source=https://github.com/pyca/cryptography/
|
||||
Issues=https://github.com/pyca/cryptography/issues
|
||||
Changelog=https://cryptography.io/en/latest/changelog/
|
||||
classifiers =
|
||||
Development Status :: 5 - Production/Stable
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: Apache Software License
|
||||
License :: OSI Approved :: BSD License
|
||||
Natural Language :: English
|
||||
Operating System :: MacOS :: MacOS X
|
||||
Operating System :: POSIX
|
||||
Operating System :: POSIX :: BSD
|
||||
Operating System :: POSIX :: Linux
|
||||
Operating System :: Microsoft :: Windows
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
Programming Language :: Python :: 3.11
|
||||
Programming Language :: Python :: Implementation :: CPython
|
||||
Programming Language :: Python :: Implementation :: PyPy
|
||||
Topic :: Security :: Cryptography
|
||||
|
||||
[options]
|
||||
python_requires = >=3.6
|
||||
include_package_data = True
|
||||
zip_safe = False
|
||||
package_dir =
|
||||
=src
|
||||
packages = find:
|
||||
# `install_requires` must be kept in sync with `pyproject.toml`
|
||||
install_requires =
|
||||
cffi >=1.12
|
||||
|
||||
[options.packages.find]
|
||||
where = src
|
||||
exclude =
|
||||
_cffi_src
|
||||
_cffi_src.*
|
||||
|
||||
[options.extras_require]
|
||||
test =
|
||||
pytest>=6.2.0
|
||||
pytest-benchmark
|
||||
pytest-cov
|
||||
pytest-subtests
|
||||
pytest-xdist
|
||||
pretend
|
||||
iso8601
|
||||
pytz
|
||||
hypothesis>=1.11.4,!=3.79.2
|
||||
docs =
|
||||
sphinx >= 1.6.5,!=1.8.0,!=3.1.0,!=3.1.1,!=5.2.0,!=5.2.0.post0
|
||||
sphinx_rtd_theme
|
||||
docstest =
|
||||
pyenchant >= 1.6.11
|
||||
twine >= 1.12.0
|
||||
sphinxcontrib-spelling >= 4.0.1
|
||||
sdist =
|
||||
setuptools_rust >= 0.11.4
|
||||
pep8test =
|
||||
black
|
||||
flake8
|
||||
flake8-import-order
|
||||
pep8-naming
|
||||
# This extra is for OpenSSH private keys that use bcrypt KDF
|
||||
# Versions: v3.1.3 - ignore_few_rounds, v3.1.5 - abi3
|
||||
ssh =
|
||||
bcrypt >= 3.1.5
|
||||
|
||||
[flake8]
|
||||
ignore = E203,E211,W503,W504,N818
|
||||
exclude = .tox,*.egg,.git,_build,.hypothesis
|
||||
select = E,W,F,N,I
|
||||
application-import-names = cryptography,cryptography_vectors,tests
|
||||
@@ -12,6 +12,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Topic :: Software Development :: Quality Assurance",
|
||||
|
||||
32
flake8_to_ruff/src/black.rs
Normal file
32
flake8_to_ruff/src/black.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! Extract Black configuration settings from a pyproject.toml.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use ruff::settings::types::PythonVersion;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub struct Black {
|
||||
#[serde(alias = "line-length", alias = "line_length")]
|
||||
pub line_length: Option<usize>,
|
||||
#[serde(alias = "target-version", alias = "target_version")]
|
||||
pub target_version: Option<Vec<PythonVersion>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct Tools {
|
||||
black: Option<Black>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct Pyproject {
|
||||
tool: Option<Tools>,
|
||||
}
|
||||
|
||||
pub fn parse_black_options<P: AsRef<Path>>(path: P) -> Result<Option<Black>> {
|
||||
let contents = std::fs::read_to_string(path)?;
|
||||
Ok(toml::from_str::<Pyproject>(&contents)?
|
||||
.tool
|
||||
.and_then(|tool| tool.black))
|
||||
}
|
||||
@@ -11,13 +11,20 @@ use ruff::{
|
||||
pep8_naming,
|
||||
};
|
||||
|
||||
use crate::black::Black;
|
||||
use crate::plugin::Plugin;
|
||||
use crate::{parser, plugin};
|
||||
|
||||
pub fn convert(
|
||||
flake8: &HashMap<String, Option<String>>,
|
||||
config: &HashMap<String, HashMap<String, Option<String>>>,
|
||||
black: Option<&Black>,
|
||||
plugins: Option<Vec<Plugin>>,
|
||||
) -> Result<Pyproject> {
|
||||
// Extract the Flake8 section.
|
||||
let flake8 = config
|
||||
.get("flake8")
|
||||
.expect("Unable to find flake8 section in INI file");
|
||||
|
||||
// Extract all referenced check code prefixes, to power plugin inference.
|
||||
let mut referenced_codes: BTreeSet<CheckCodePrefix> = BTreeSet::default();
|
||||
for (key, value) in flake8 {
|
||||
@@ -54,10 +61,15 @@ pub fn convert(
|
||||
plugin::resolve_select(
|
||||
flake8,
|
||||
&plugins.unwrap_or_else(|| {
|
||||
plugin::infer_plugins_from_options(flake8)
|
||||
.into_iter()
|
||||
.chain(plugin::infer_plugins_from_codes(&referenced_codes))
|
||||
.collect()
|
||||
let from_options = plugin::infer_plugins_from_options(flake8);
|
||||
if !from_options.is_empty() {
|
||||
eprintln!("Inferred plugins from settings: {from_options:#?}");
|
||||
}
|
||||
let from_codes = plugin::infer_plugins_from_codes(&referenced_codes);
|
||||
if !from_codes.is_empty() {
|
||||
eprintln!("Inferred plugins from referenced check codes: {from_codes:#?}");
|
||||
}
|
||||
from_options.into_iter().chain(from_codes).collect()
|
||||
}),
|
||||
)
|
||||
});
|
||||
@@ -236,6 +248,19 @@ pub fn convert(
|
||||
options.pep8_naming = Some(pep8_naming);
|
||||
}
|
||||
|
||||
// Extract any settings from the existing `pyproject.toml`.
|
||||
if let Some(black) = black {
|
||||
if let Some(line_length) = &black.line_length {
|
||||
options.line_length = Some(*line_length);
|
||||
}
|
||||
|
||||
if let Some(target_version) = &black.target_version {
|
||||
if let Some(target_version) = target_version.iter().min() {
|
||||
options.target_version = Some(*target_version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the pyproject.toml.
|
||||
Ok(Pyproject::new(options))
|
||||
}
|
||||
@@ -255,7 +280,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn it_converts_empty() -> Result<()> {
|
||||
let actual = convert(&HashMap::from([]), None)?;
|
||||
let actual = convert(
|
||||
&HashMap::from([("flake8".to_string(), HashMap::default())]),
|
||||
None,
|
||||
None,
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
dummy_variable_rgx: None,
|
||||
@@ -268,6 +297,7 @@ mod tests {
|
||||
fix: None,
|
||||
fixable: None,
|
||||
format: None,
|
||||
force_exclude: None,
|
||||
ignore: Some(vec![]),
|
||||
ignore_init_module_imports: None,
|
||||
line_length: None,
|
||||
@@ -282,6 +312,7 @@ mod tests {
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
cache_dir: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
@@ -302,7 +333,11 @@ mod tests {
|
||||
#[test]
|
||||
fn it_converts_dashes() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]),
|
||||
&HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]),
|
||||
)]),
|
||||
None,
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
@@ -317,6 +352,7 @@ mod tests {
|
||||
fix: None,
|
||||
fixable: None,
|
||||
format: None,
|
||||
force_exclude: None,
|
||||
ignore: Some(vec![]),
|
||||
ignore_init_module_imports: None,
|
||||
line_length: Some(100),
|
||||
@@ -331,6 +367,7 @@ mod tests {
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
cache_dir: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
@@ -351,7 +388,11 @@ mod tests {
|
||||
#[test]
|
||||
fn it_converts_underscores() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]),
|
||||
&HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]),
|
||||
)]),
|
||||
None,
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
@@ -366,6 +407,7 @@ mod tests {
|
||||
fix: None,
|
||||
fixable: None,
|
||||
format: None,
|
||||
force_exclude: None,
|
||||
ignore: Some(vec![]),
|
||||
ignore_init_module_imports: None,
|
||||
line_length: Some(100),
|
||||
@@ -380,6 +422,7 @@ mod tests {
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
cache_dir: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
@@ -400,7 +443,11 @@ mod tests {
|
||||
#[test]
|
||||
fn it_ignores_parse_errors() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]),
|
||||
&HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]),
|
||||
)]),
|
||||
None,
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
@@ -415,6 +462,7 @@ mod tests {
|
||||
fix: None,
|
||||
fixable: None,
|
||||
format: None,
|
||||
force_exclude: None,
|
||||
ignore: Some(vec![]),
|
||||
ignore_init_module_imports: None,
|
||||
line_length: None,
|
||||
@@ -429,6 +477,7 @@ mod tests {
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
cache_dir: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
@@ -449,7 +498,11 @@ mod tests {
|
||||
#[test]
|
||||
fn it_converts_plugin_options() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||
&HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||
)]),
|
||||
None,
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
@@ -464,6 +517,7 @@ mod tests {
|
||||
fix: None,
|
||||
fixable: None,
|
||||
format: None,
|
||||
force_exclude: None,
|
||||
ignore: Some(vec![]),
|
||||
ignore_init_module_imports: None,
|
||||
line_length: None,
|
||||
@@ -478,6 +532,7 @@ mod tests {
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
cache_dir: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
@@ -504,9 +559,13 @@ mod tests {
|
||||
fn it_converts_docstring_conventions() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([(
|
||||
"docstring-convention".to_string(),
|
||||
Some("numpy".to_string()),
|
||||
"flake8".to_string(),
|
||||
HashMap::from([(
|
||||
"docstring-convention".to_string(),
|
||||
Some("numpy".to_string()),
|
||||
)]),
|
||||
)]),
|
||||
None,
|
||||
Some(vec![Plugin::Flake8Docstrings]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
@@ -521,6 +580,7 @@ mod tests {
|
||||
fix: None,
|
||||
fixable: None,
|
||||
format: None,
|
||||
force_exclude: None,
|
||||
ignore: Some(vec![]),
|
||||
ignore_init_module_imports: None,
|
||||
line_length: None,
|
||||
@@ -571,6 +631,7 @@ mod tests {
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
cache_dir: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
@@ -591,7 +652,11 @@ mod tests {
|
||||
#[test]
|
||||
fn it_infers_plugins_if_omitted() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||
&HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||
)]),
|
||||
None,
|
||||
None,
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
@@ -606,6 +671,7 @@ mod tests {
|
||||
fix: None,
|
||||
fixable: None,
|
||||
format: None,
|
||||
force_exclude: None,
|
||||
ignore: Some(vec![]),
|
||||
ignore_init_module_imports: None,
|
||||
line_length: None,
|
||||
@@ -621,6 +687,7 @@ mod tests {
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
cache_dir: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_errmsg: None,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
clippy::too_many_lines
|
||||
)]
|
||||
|
||||
pub mod black;
|
||||
pub mod converter;
|
||||
mod parser;
|
||||
pub mod plugin;
|
||||
|
||||
@@ -17,6 +17,7 @@ use std::path::PathBuf;
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use configparser::ini::Ini;
|
||||
use flake8_to_ruff::black::parse_black_options;
|
||||
use flake8_to_ruff::converter;
|
||||
use flake8_to_ruff::plugin::Plugin;
|
||||
|
||||
@@ -26,10 +27,14 @@ use flake8_to_ruff::plugin::Plugin;
|
||||
long_about = None
|
||||
)]
|
||||
struct Cli {
|
||||
/// Path to the Flake8 configuration file (e.g., 'setup.cfg', 'tox.ini', or
|
||||
/// '.flake8').
|
||||
/// Path to the Flake8 configuration file (e.g., `setup.cfg`, `tox.ini`, or
|
||||
/// `.flake8`).
|
||||
#[arg(required = true)]
|
||||
file: PathBuf,
|
||||
/// Optional path to a `pyproject.toml` file, used to ensure compatibility
|
||||
/// with Black.
|
||||
#[arg(long)]
|
||||
pyproject: Option<PathBuf>,
|
||||
/// List of plugins to enable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
plugin: Option<Vec<Plugin>>,
|
||||
@@ -43,13 +48,15 @@ fn main() -> Result<()> {
|
||||
ini.set_multiline(true);
|
||||
let config = ini.load(cli.file).map_err(|msg| anyhow::anyhow!(msg))?;
|
||||
|
||||
// Extract the Flake8 section.
|
||||
let flake8 = config
|
||||
.get("flake8")
|
||||
.expect("Unable to find flake8 section in INI file");
|
||||
// Read the pyproject.toml file.
|
||||
let black = cli
|
||||
.pyproject
|
||||
.map(parse_black_options)
|
||||
.transpose()?
|
||||
.flatten();
|
||||
|
||||
// Create the pyproject.toml.
|
||||
let pyproject = converter::convert(flake8, cli.plugin)?;
|
||||
// Create Ruff's pyproject.toml section.
|
||||
let pyproject = converter::convert(&config, black.as_ref(), cli.plugin)?;
|
||||
println!("{}", toml::to_string_pretty(&pyproject)?);
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::str::FromStr;
|
||||
use anyhow::{bail, Result};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use ruff::checks::PREFIX_REDIRECTS;
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
use ruff::settings::types::PatternPrefixPair;
|
||||
use rustc_hash::FxHashMap;
|
||||
@@ -18,7 +19,9 @@ pub fn parse_prefix_codes(value: &str) -> Vec<CheckCodePrefix> {
|
||||
if code.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Ok(code) = CheckCodePrefix::from_str(code) {
|
||||
if let Some(code) = PREFIX_REDIRECTS.get(code) {
|
||||
codes.push(code.clone());
|
||||
} else if let Ok(code) = CheckCodePrefix::from_str(code) {
|
||||
codes.push(code);
|
||||
} else {
|
||||
eprintln!("Unsupported prefix code: {code}");
|
||||
@@ -83,16 +86,22 @@ impl State {
|
||||
fn parse(&self) -> Vec<PatternPrefixPair> {
|
||||
let mut codes: Vec<PatternPrefixPair> = vec![];
|
||||
for code in &self.codes {
|
||||
match CheckCodePrefix::from_str(code) {
|
||||
Ok(code) => {
|
||||
for filename in &self.filenames {
|
||||
codes.push(PatternPrefixPair {
|
||||
pattern: filename.clone(),
|
||||
prefix: code.clone(),
|
||||
});
|
||||
}
|
||||
if let Some(code) = PREFIX_REDIRECTS.get(code.as_str()) {
|
||||
for filename in &self.filenames {
|
||||
codes.push(PatternPrefixPair {
|
||||
pattern: filename.clone(),
|
||||
prefix: code.clone(),
|
||||
});
|
||||
}
|
||||
Err(_) => eprintln!("Skipping unrecognized prefix: {code}"),
|
||||
} else if let Ok(code) = CheckCodePrefix::from_str(code) {
|
||||
for filename in &self.filenames {
|
||||
codes.push(PatternPrefixPair {
|
||||
pattern: filename.clone(),
|
||||
prefix: code.clone(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
eprintln!("Unsupported prefix code: {code}");
|
||||
}
|
||||
}
|
||||
codes
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub enum Plugin {
|
||||
Flake8Annotations,
|
||||
Flake8Bandit,
|
||||
@@ -12,6 +13,7 @@ pub enum Plugin {
|
||||
Flake8Bugbear,
|
||||
Flake8Builtins,
|
||||
Flake8Comprehensions,
|
||||
Flake8Datetimez,
|
||||
Flake8Debugger,
|
||||
Flake8Docstrings,
|
||||
Flake8ErrMsg,
|
||||
@@ -38,9 +40,10 @@ impl FromStr for Plugin {
|
||||
"flake8-bugbear" => Ok(Plugin::Flake8Bugbear),
|
||||
"flake8-builtins" => Ok(Plugin::Flake8Builtins),
|
||||
"flake8-comprehensions" => Ok(Plugin::Flake8Comprehensions),
|
||||
"flake8-datetimez" => Ok(Plugin::Flake8Datetimez),
|
||||
"flake8-debugger" => Ok(Plugin::Flake8Debugger),
|
||||
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
|
||||
"flake8-eradicate" => Ok(Plugin::Flake8BlindExcept),
|
||||
"flake8-eradicate" => Ok(Plugin::Flake8Eradicate),
|
||||
"flake8-errmsg" => Ok(Plugin::Flake8ErrMsg),
|
||||
"flake8-print" => Ok(Plugin::Flake8Print),
|
||||
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
|
||||
@@ -56,6 +59,37 @@ impl FromStr for Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Plugin {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Plugin::Flake8Annotations => "flake8-annotations",
|
||||
Plugin::Flake8Bandit => "flake8-bandit",
|
||||
Plugin::Flake8BlindExcept => "flake8-blind-except",
|
||||
Plugin::Flake8Bugbear => "flake8-bugbear",
|
||||
Plugin::Flake8Builtins => "flake8-builtins",
|
||||
Plugin::Flake8Comprehensions => "flake8-comprehensions",
|
||||
Plugin::Flake8Datetimez => "flake8-datetimez",
|
||||
Plugin::Flake8Debugger => "flake8-debugger",
|
||||
Plugin::Flake8Docstrings => "flake8-docstrings",
|
||||
Plugin::Flake8Eradicate => "flake8-eradicate",
|
||||
Plugin::Flake8ErrMsg => "flake8-errmsg",
|
||||
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::Pyupgrade => "pyupgrade",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin {
|
||||
pub fn default(&self) -> CheckCodePrefix {
|
||||
match self {
|
||||
@@ -66,6 +100,7 @@ impl Plugin {
|
||||
Plugin::Flake8Bugbear => CheckCodePrefix::B,
|
||||
Plugin::Flake8Builtins => CheckCodePrefix::A,
|
||||
Plugin::Flake8Comprehensions => CheckCodePrefix::C4,
|
||||
Plugin::Flake8Datetimez => CheckCodePrefix::DTZ,
|
||||
Plugin::Flake8Debugger => CheckCodePrefix::T1,
|
||||
Plugin::Flake8Docstrings => CheckCodePrefix::D,
|
||||
// TODO(charlie): Handle rename of `E` to `ERA`.
|
||||
@@ -75,12 +110,11 @@ impl Plugin {
|
||||
Plugin::Flake8Quotes => CheckCodePrefix::Q,
|
||||
Plugin::Flake8Return => CheckCodePrefix::RET,
|
||||
Plugin::Flake8Simplify => CheckCodePrefix::SIM,
|
||||
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
|
||||
Plugin::Flake8TidyImports => CheckCodePrefix::TID25,
|
||||
Plugin::McCabe => CheckCodePrefix::C9,
|
||||
// TODO(charlie): Handle rename of `PD` to `PDV`.
|
||||
Plugin::PandasVet => CheckCodePrefix::PDV,
|
||||
Plugin::PandasVet => CheckCodePrefix::PD,
|
||||
Plugin::PEP8Naming => CheckCodePrefix::N,
|
||||
Plugin::Pyupgrade => CheckCodePrefix::U,
|
||||
Plugin::Pyupgrade => CheckCodePrefix::UP,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +126,7 @@ impl Plugin {
|
||||
Plugin::Flake8Bugbear => vec![CheckCodePrefix::B],
|
||||
Plugin::Flake8Builtins => vec![CheckCodePrefix::A],
|
||||
Plugin::Flake8Comprehensions => vec![CheckCodePrefix::C4],
|
||||
Plugin::Flake8Datetimez => vec![CheckCodePrefix::DTZ],
|
||||
Plugin::Flake8Debugger => vec![CheckCodePrefix::T1],
|
||||
Plugin::Flake8Docstrings => {
|
||||
// Use the user-provided docstring.
|
||||
@@ -117,7 +152,7 @@ impl Plugin {
|
||||
Plugin::Flake8Simplify => vec![CheckCodePrefix::SIM],
|
||||
Plugin::Flake8TidyImports => vec![CheckCodePrefix::TID],
|
||||
Plugin::McCabe => vec![CheckCodePrefix::C9],
|
||||
Plugin::PandasVet => vec![CheckCodePrefix::PDV],
|
||||
Plugin::PandasVet => vec![CheckCodePrefix::PD],
|
||||
Plugin::PEP8Naming => vec![CheckCodePrefix::N],
|
||||
Plugin::Pyupgrade => vec![CheckCodePrefix::UP],
|
||||
}
|
||||
@@ -409,6 +444,7 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin
|
||||
Plugin::Flake8Bugbear,
|
||||
Plugin::Flake8Builtins,
|
||||
Plugin::Flake8Comprehensions,
|
||||
Plugin::Flake8Datetimez,
|
||||
Plugin::Flake8Debugger,
|
||||
Plugin::Flake8Docstrings,
|
||||
Plugin::Flake8Eradicate,
|
||||
@@ -420,7 +456,6 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin
|
||||
Plugin::Flake8TidyImports,
|
||||
Plugin::PandasVet,
|
||||
Plugin::PEP8Naming,
|
||||
Plugin::Pyupgrade,
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|plugin| {
|
||||
|
||||
@@ -12,6 +12,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Topic :: Software Development :: Quality Assurance",
|
||||
@@ -32,6 +33,8 @@ build-backend = "maturin"
|
||||
bindings = "bin"
|
||||
strip = true
|
||||
|
||||
[tool.ruff]
|
||||
|
||||
[tool.ruff.isort]
|
||||
force-wrap-aliases = true
|
||||
combine-as-imports = true
|
||||
|
||||
@@ -55,3 +55,5 @@ a.get("hello", False)
|
||||
{}.pop(True, False)
|
||||
dict.fromkeys(("world",), True)
|
||||
{}.deploy(True, False)
|
||||
getattr(someobj, attrname, False)
|
||||
mylist.index(True)
|
||||
|
||||
21
resources/test/fixtures/flake8_datetimez/DTZ001.py
vendored
Normal file
21
resources/test/fixtures/flake8_datetimez/DTZ001.py
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
import datetime
|
||||
|
||||
# no args
|
||||
datetime.datetime(2000, 1, 1, 0, 0, 0)
|
||||
|
||||
# none args
|
||||
datetime.datetime(2000, 1, 1, 0, 0, 0, 0, None)
|
||||
|
||||
# not none arg
|
||||
datetime.datetime(2000, 1, 1, 0, 0, 0, 0, datetime.timezone.utc)
|
||||
|
||||
# no kwargs
|
||||
datetime.datetime(2000, 1, 1, fold=1)
|
||||
|
||||
# none kwargs
|
||||
datetime.datetime(2000, 1, 1, tzinfo=None)
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
# no args unqualified
|
||||
datetime(2000, 1, 1, 0, 0, 0)
|
||||
9
resources/test/fixtures/flake8_datetimez/DTZ002.py
vendored
Normal file
9
resources/test/fixtures/flake8_datetimez/DTZ002.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import datetime
|
||||
|
||||
# qualified
|
||||
datetime.datetime.today()
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
# unqualified
|
||||
datetime.today()
|
||||
9
resources/test/fixtures/flake8_datetimez/DTZ003.py
vendored
Normal file
9
resources/test/fixtures/flake8_datetimez/DTZ003.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import datetime
|
||||
|
||||
# qualified
|
||||
datetime.datetime.utcnow()
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
# unqualified
|
||||
datetime.utcnow()
|
||||
9
resources/test/fixtures/flake8_datetimez/DTZ004.py
vendored
Normal file
9
resources/test/fixtures/flake8_datetimez/DTZ004.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import datetime
|
||||
|
||||
# qualified
|
||||
datetime.datetime.utcfromtimestamp(1234)
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
# unqualified
|
||||
datetime.utcfromtimestamp(1234)
|
||||
18
resources/test/fixtures/flake8_datetimez/DTZ005.py
vendored
Normal file
18
resources/test/fixtures/flake8_datetimez/DTZ005.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import datetime
|
||||
|
||||
# no args
|
||||
datetime.datetime.now()
|
||||
|
||||
# wrong keywords
|
||||
datetime.datetime.now(bad=datetime.timezone.utc)
|
||||
|
||||
# none args
|
||||
datetime.datetime.now(None)
|
||||
|
||||
# none keywords
|
||||
datetime.datetime.now(tz=None)
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
# no args unqualified
|
||||
datetime.now()
|
||||
18
resources/test/fixtures/flake8_datetimez/DTZ006.py
vendored
Normal file
18
resources/test/fixtures/flake8_datetimez/DTZ006.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import datetime
|
||||
|
||||
# no args
|
||||
datetime.datetime.fromtimestamp(1234)
|
||||
|
||||
# wrong keywords
|
||||
datetime.datetime.fromtimestamp(1234, bad=datetime.timezone.utc)
|
||||
|
||||
# none args
|
||||
datetime.datetime.fromtimestamp(1234, None)
|
||||
|
||||
# none keywords
|
||||
datetime.datetime.fromtimestamp(1234, tz=None)
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
# no args unqualified
|
||||
datetime.fromtimestamp(1234)
|
||||
35
resources/test/fixtures/flake8_datetimez/DTZ007.py
vendored
Normal file
35
resources/test/fixtures/flake8_datetimez/DTZ007.py
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
import datetime
|
||||
|
||||
# bad format
|
||||
datetime.datetime.strptime("something", "%H:%M:%S%Z")
|
||||
|
||||
# no replace or astimezone
|
||||
datetime.datetime.strptime("something", "something")
|
||||
|
||||
# wrong replace
|
||||
datetime.datetime.strptime("something", "something").replace(hour=1)
|
||||
|
||||
# none replace
|
||||
datetime.datetime.strptime("something", "something").replace(tzinfo=None)
|
||||
|
||||
# OK
|
||||
datetime.datetime.strptime("something", "something").replace(
|
||||
tzinfo=datetime.timezone.utc
|
||||
)
|
||||
|
||||
# OK
|
||||
datetime.datetime.strptime("something", "something").astimezone()
|
||||
|
||||
# OK
|
||||
datetime.datetime.strptime("something", "%H:%M:%S%z")
|
||||
|
||||
# OK
|
||||
datetime.datetime.strptime("something", something).astimezone()
|
||||
|
||||
# OK
|
||||
datetime.datetime.strptime("something", something).replace(tzinfo=datetime.timezone.utc)
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
# no replace orastimezone unqualified
|
||||
datetime.strptime("something", "something")
|
||||
9
resources/test/fixtures/flake8_datetimez/DTZ011.py
vendored
Normal file
9
resources/test/fixtures/flake8_datetimez/DTZ011.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import datetime
|
||||
|
||||
# qualified
|
||||
datetime.date.today()
|
||||
|
||||
from datetime import date
|
||||
|
||||
# unqualified
|
||||
date.today()
|
||||
9
resources/test/fixtures/flake8_datetimez/DTZ012.py
vendored
Normal file
9
resources/test/fixtures/flake8_datetimez/DTZ012.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import datetime
|
||||
|
||||
# qualified
|
||||
datetime.date.fromtimestamp(1234)
|
||||
|
||||
from datetime import date
|
||||
|
||||
# unqualified
|
||||
date.fromtimestamp(1234)
|
||||
@@ -1,6 +1,5 @@
|
||||
breakpoint()
|
||||
|
||||
|
||||
import pdb
|
||||
import builtins
|
||||
from builtins import breakpoint
|
||||
@@ -9,7 +8,6 @@ from celery.contrib.rdb import set_trace
|
||||
from celery.contrib import rdb
|
||||
import celery.contrib.rdb
|
||||
|
||||
|
||||
breakpoint()
|
||||
st()
|
||||
set_trace()
|
||||
9
resources/test/fixtures/flake8_print/T201.py
vendored
9
resources/test/fixtures/flake8_print/T201.py
vendored
@@ -1 +1,10 @@
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
print("Hello, world!") # T201
|
||||
print("Hello, world!", file=None) # T201
|
||||
print("Hello, world!", file=sys.stdout) # T201
|
||||
print("Hello, world!", file=sys.stderr) # T201
|
||||
|
||||
with tempfile.NamedTemporaryFile() as fp:
|
||||
print("Hello, world!", file=fp) # OK
|
||||
|
||||
1
resources/test/fixtures/flake8_print/T203.py
vendored
1
resources/test/fixtures/flake8_print/T203.py
vendored
@@ -2,7 +2,6 @@ from pprint import pprint
|
||||
|
||||
pprint("Hello, world!") # T203
|
||||
|
||||
|
||||
import pprint
|
||||
|
||||
pprint.pprint("Hello, world!") # T203
|
||||
|
||||
39
resources/test/fixtures/flake8_return/RET504.py
vendored
39
resources/test/fixtures/flake8_return/RET504.py
vendored
@@ -6,18 +6,6 @@ def x():
|
||||
return a # error
|
||||
|
||||
|
||||
def x():
|
||||
b, a = 1, 2
|
||||
print(b)
|
||||
return a # error
|
||||
|
||||
|
||||
def x():
|
||||
a = 1
|
||||
print()
|
||||
return a # error
|
||||
|
||||
|
||||
def x():
|
||||
a = 1
|
||||
print(a)
|
||||
@@ -53,7 +41,6 @@ def x():
|
||||
|
||||
# https://github.com/afonasev/flake8-return/issues/47#issue-641117366
|
||||
def user_agent_username(username=None):
|
||||
|
||||
if not username:
|
||||
return ""
|
||||
|
||||
@@ -136,6 +123,20 @@ def x():
|
||||
return a
|
||||
|
||||
|
||||
# Considered OK, since functions can have side effects.
|
||||
def x():
|
||||
b, a = 1, 2
|
||||
print(b)
|
||||
return a
|
||||
|
||||
|
||||
# Considered OK, since functions can have side effects.
|
||||
def x():
|
||||
a = 1
|
||||
print()
|
||||
return a
|
||||
|
||||
|
||||
# Test cases for using value for assignment then returning it
|
||||
# See:https://github.com/afonasev/flake8-return/issues/47
|
||||
def resolve_from_url(self, url: str) -> dict:
|
||||
@@ -236,3 +237,15 @@ def close(self):
|
||||
any_failed = True
|
||||
report(traceback.format_exc())
|
||||
return any_failed
|
||||
|
||||
def global_assignment():
|
||||
global X
|
||||
X = 1
|
||||
return X
|
||||
|
||||
def nonlocal_assignment():
|
||||
X = 1
|
||||
def inner():
|
||||
nonlocal X
|
||||
X = 1
|
||||
return X
|
||||
|
||||
61
resources/test/fixtures/pycodestyle/E40.py
vendored
Normal file
61
resources/test/fixtures/pycodestyle/E40.py
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
#: E401
|
||||
import os, sys
|
||||
#: Okay
|
||||
import os
|
||||
import sys
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from myclass import MyClass
|
||||
from foo.bar.yourclass import YourClass
|
||||
|
||||
import myclass
|
||||
import foo.bar.yourclass
|
||||
#: Okay
|
||||
__all__ = ['abc']
|
||||
|
||||
import foo
|
||||
#: Okay
|
||||
__version__ = "42"
|
||||
|
||||
import foo
|
||||
#: Okay
|
||||
__author__ = "Simon Gomizelj"
|
||||
|
||||
import foo
|
||||
#: Okay
|
||||
try:
|
||||
import foo
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
print('imported foo')
|
||||
finally:
|
||||
print('made attempt to import foo')
|
||||
|
||||
import bar
|
||||
#: Okay
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("ignore", DeprecationWarning)
|
||||
import foo
|
||||
|
||||
import bar
|
||||
#: Okay
|
||||
if False:
|
||||
import foo
|
||||
elif not True:
|
||||
import bar
|
||||
else:
|
||||
import mwahaha
|
||||
|
||||
import bar
|
||||
#: E402
|
||||
VERSION = '1.2.3'
|
||||
|
||||
import foo
|
||||
#: E402
|
||||
import foo
|
||||
|
||||
a = 1
|
||||
|
||||
import bar
|
||||
5
resources/test/fixtures/pycodestyle/E501.py
vendored
5
resources/test/fixtures/pycodestyle/E501.py
vendored
@@ -55,3 +55,8 @@ sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labor
|
||||
|
||||
# OK
|
||||
# https://loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong.url.com
|
||||
|
||||
# Not OK
|
||||
_ = """
|
||||
Source: https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
"""
|
||||
|
||||
4
resources/test/fixtures/pycodestyle/E721.py
vendored
4
resources/test/fixtures/pycodestyle/E721.py
vendored
@@ -15,7 +15,7 @@ import types
|
||||
if type(res) is not types.ListType:
|
||||
pass
|
||||
#: E721
|
||||
assert type(res) == type(False) or type(res) == type(None)
|
||||
assert type(res) == type(False)
|
||||
#: E721
|
||||
assert type(res) == type([])
|
||||
#: E721
|
||||
@@ -52,3 +52,5 @@ if isinstance(res, types.MethodType):
|
||||
pass
|
||||
if type(a) != type(b) or type(a) == type(ccc):
|
||||
pass
|
||||
|
||||
assert type(res) == type(None)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
def fn() -> None:
|
||||
pass
|
||||
print("Newline present (no W292)")
|
||||
|
||||
0
resources/test/fixtures/pycodestyle/W292_3.py
vendored
Normal file
0
resources/test/fixtures/pycodestyle/W292_3.py
vendored
Normal file
1
resources/test/fixtures/pycodestyle/W292_4.py
vendored
Normal file
1
resources/test/fixtures/pycodestyle/W292_4.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
13
resources/test/fixtures/pyflakes/F821_7.py
vendored
Normal file
13
resources/test/fixtures/pyflakes/F821_7.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Test: Mypy extensions."""
|
||||
|
||||
from mypy_extensions import DefaultNamedArg
|
||||
|
||||
# OK
|
||||
_ = DefaultNamedArg(bool | None, name="some_prop_name")
|
||||
_ = DefaultNamedArg(type=bool | None, name="some_prop_name")
|
||||
_ = DefaultNamedArg(bool | None, "some_prop_name")
|
||||
|
||||
# Not OK
|
||||
_ = DefaultNamedArg("Undefined", name="some_prop_name")
|
||||
_ = DefaultNamedArg(type="Undefined", name="some_prop_name")
|
||||
_ = DefaultNamedArg("Undefined", "some_prop_name")
|
||||
8
resources/test/fixtures/pygrep-hooks/PGH002_0.py
vendored
Normal file
8
resources/test/fixtures/pygrep-hooks/PGH002_0.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import logging
|
||||
import warnings
|
||||
from warnings import warn
|
||||
|
||||
warnings.warn("this is ok")
|
||||
warn("by itself is also ok")
|
||||
logging.warning("this is fine")
|
||||
log.warning("this is ok")
|
||||
15
resources/test/fixtures/pygrep-hooks/PGH002_1.py
vendored
Normal file
15
resources/test/fixtures/pygrep-hooks/PGH002_1.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import logging
|
||||
from logging import warn
|
||||
|
||||
logging.warn("this is not ok")
|
||||
log.warn("this is also not ok")
|
||||
warn("not ok")
|
||||
|
||||
|
||||
def foo():
|
||||
from logging import warn
|
||||
|
||||
def warn():
|
||||
pass
|
||||
|
||||
warn("has been redefined, but we will still report it")
|
||||
11
resources/test/fixtures/pygrep-hooks/PGH003_0.py
vendored
Normal file
11
resources/test/fixtures/pygrep-hooks/PGH003_0.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
x = 1 # type: ignore
|
||||
x = 1 # type ignore
|
||||
x = 1 # type:ignore
|
||||
|
||||
x = 1
|
||||
x = 1 # type ignore # noqa
|
||||
x = 1 # type: ignore[attr-defined]
|
||||
x = 1 # type: ignore[attr-defined, name-defined]
|
||||
x = 1 # type: ignore[type-mismatch] # noqa
|
||||
x = 1 # type: Union[int, str]
|
||||
x = 1 # type: ignoreme
|
||||
@@ -109,6 +109,11 @@ def f():
|
||||
del x
|
||||
|
||||
|
||||
def f():
|
||||
print(f"{x=}")
|
||||
global x
|
||||
|
||||
|
||||
###
|
||||
# Non-errors.
|
||||
###
|
||||
@@ -146,3 +151,8 @@ def f():
|
||||
global x, y
|
||||
|
||||
del x
|
||||
|
||||
|
||||
def f():
|
||||
global x
|
||||
print(f"{x=}")
|
||||
|
||||
11
resources/test/fixtures/pyupgrade/UP017.py
vendored
Normal file
11
resources/test/fixtures/pyupgrade/UP017.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import datetime
|
||||
import datetime as dt
|
||||
from datetime import timezone
|
||||
from datetime import timezone as tz
|
||||
|
||||
print(datetime.timezone(-1))
|
||||
print(timezone.utc)
|
||||
print(tz.utc)
|
||||
|
||||
print(datetime.timezone.utc)
|
||||
print(dt.timezone.utc)
|
||||
25
resources/test/fixtures/pyupgrade/UP018.py
vendored
Normal file
25
resources/test/fixtures/pyupgrade/UP018.py
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# These remain unchanged
|
||||
str(1)
|
||||
str(*a)
|
||||
str("foo", *a)
|
||||
str(**k)
|
||||
str("foo", **k)
|
||||
str("foo", encoding="UTF-8")
|
||||
str("foo"
|
||||
"bar")
|
||||
bytes("foo", encoding="UTF-8")
|
||||
bytes(*a)
|
||||
bytes("foo", *a)
|
||||
bytes("foo", **a)
|
||||
bytes(b"foo"
|
||||
b"bar")
|
||||
|
||||
# These become string or byte literals
|
||||
str()
|
||||
str("foo")
|
||||
str("""
|
||||
foo""")
|
||||
bytes()
|
||||
bytes(b"foo")
|
||||
bytes(b"""
|
||||
foo""")
|
||||
21
resources/test/fixtures/ruff/RUF100.py
vendored
21
resources/test/fixtures/ruff/RUF100.py
vendored
@@ -15,8 +15,8 @@ def f() -> None:
|
||||
# Invalid
|
||||
d = 1 # noqa: F841, E501
|
||||
|
||||
# Invalid (and unimplemented)
|
||||
d = 1 # noqa: F841, W191
|
||||
# Invalid (and unimplemented or not enabled)
|
||||
d = 1 # noqa: F841, W191, F821
|
||||
|
||||
# Invalid (but external)
|
||||
d = 1 # noqa: F841, V101
|
||||
@@ -69,3 +69,20 @@ _ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
""" # noqa
|
||||
|
||||
# Valid
|
||||
# this is a veryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy long comment # noqa: E501
|
||||
|
||||
# Valid
|
||||
_ = """Here's a source: https://github.com/ethereum/web3.py/blob/ffe59daf10edc19ee5f05227b25bac8d090e8aa4/web3/_utils/events.py#L201
|
||||
|
||||
May raise:
|
||||
- DeserializationError if the abi string is invalid or abi or log topics/data do not match
|
||||
""" # noqa: E501
|
||||
|
||||
import collections # noqa
|
||||
import os # noqa: F401, RUF100
|
||||
import shelve # noqa: RUF100
|
||||
import sys # noqa: F401, RUF100
|
||||
|
||||
print(sys.path)
|
||||
|
||||
@@ -9,14 +9,14 @@ Running from the repo root should pick up and enforce the appropriate settings f
|
||||
|
||||
```
|
||||
∴ cargo run resources/test/project/
|
||||
Found 7 error(s).
|
||||
resources/test/project/examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
|
||||
resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
|
||||
resources/test/project/examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
resources/test/project/examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
resources/test/project/src/file.py:1:8: F401 `os` imported but unused
|
||||
resources/test/project/src/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
resources/test/project/project/file.py:1:8: F401 `os` imported but unused
|
||||
resources/test/project/project/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
Found 7 error(s).
|
||||
6 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
@@ -24,14 +24,14 @@ Running from the project directory itself should exhibit the same behavior:
|
||||
|
||||
```
|
||||
∴ (cd resources/test/project/ && cargo run .)
|
||||
Found 7 error(s).
|
||||
examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
|
||||
examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
|
||||
examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
src/file.py:1:8: F401 `os` imported but unused
|
||||
src/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
project/file.py:1:8: F401 `os` imported but unused
|
||||
project/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
Found 7 error(s).
|
||||
6 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
@@ -40,9 +40,9 @@ files:
|
||||
|
||||
```
|
||||
∴ (cd resources/test/project/examples/docs && cargo run .)
|
||||
Found 2 error(s).
|
||||
docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
Found 2 error(s).
|
||||
1 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
@@ -51,8 +51,6 @@ file paths from the current working directory:
|
||||
|
||||
```
|
||||
∴ (cargo run -- --config=resources/test/project/pyproject.toml resources/test/project/)
|
||||
Found 11 error(s).
|
||||
resources/test/project/examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
|
||||
resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
|
||||
resources/test/project/examples/docs/docs/concepts/file.py:1:8: F401 `os` imported but unused
|
||||
@@ -61,9 +59,9 @@ resources/test/project/examples/docs/docs/file.py:1:8: F401 `os` imported but un
|
||||
resources/test/project/examples/docs/docs/file.py:3:8: F401 `numpy` imported but unused
|
||||
resources/test/project/examples/docs/docs/file.py:4:27: F401 `docs.concepts.file` imported but unused
|
||||
resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused
|
||||
resources/test/project/src/file.py:1:8: F401 `os` imported but unused
|
||||
resources/test/project/src/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
11 potentially fixable with the --fix option.
|
||||
resources/test/project/project/file.py:1:8: F401 `os` imported but unused
|
||||
Found 9 error(s).
|
||||
9 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Running from a parent directory should this "ignore" the `exclude` (hence, `concepts/file.py` gets
|
||||
@@ -71,11 +69,11 @@ included in the output):
|
||||
|
||||
```
|
||||
∴ (cd resources/test/project/examples && cargo run -- --config=docs/pyproject.toml .)
|
||||
Found 4 error(s).
|
||||
docs/docs/concepts/file.py:5:5: F841 Local variable `x` is assigned to but never used
|
||||
docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
excluded/script.py:5:5: F841 Local variable `x` is assigned to but never used
|
||||
Found 4 error(s).
|
||||
1 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
@@ -83,7 +81,14 @@ Passing an excluded directory directly should report errors in the contained fil
|
||||
|
||||
```
|
||||
∴ cargo run resources/test/project/examples/excluded/
|
||||
Found 1 error(s).
|
||||
resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused
|
||||
Found 1 error(s).
|
||||
1 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Unless we `--force-exclude`:
|
||||
|
||||
```
|
||||
∴ cargo run resources/test/project/examples/excluded/ --force-exclude
|
||||
∴ cargo run resources/test/project/examples/excluded/script.py --force-exclude
|
||||
```
|
||||
|
||||
1194
ruff.schema.json
Normal file
1194
ruff.schema.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.185"
|
||||
version = "0.0.193"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -11,8 +11,10 @@ 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 = "8d879a53197f9c73062f6160410bdba796a71cbf" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "1b6cb170e925a43d605b3fed9f6b878e63e47744" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "1b6cb170e925a43d605b3fed9f6b878e63e47744" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "1b6cb170e925a43d605b3fed9f6b878e63e47744" }
|
||||
schemars = { version = "0.8.11" }
|
||||
serde_json = {version="1.0.91"}
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.24.3" }
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
//! Generate the `CheckCodePrefix` enum.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fs::OpenOptions;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Output, Stdio};
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{ensure, Result};
|
||||
use clap::Parser;
|
||||
use codegen::{Scope, Type, Variant};
|
||||
use itertools::Itertools;
|
||||
use ruff::checks::{CheckCode, CODE_REDIRECTS, PREFIX_REDIRECTS};
|
||||
use ruff::checks::{CheckCode, PREFIX_REDIRECTS};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
const FILE: &str = "src/checks_gen.rs";
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub struct Cli {
|
||||
@@ -40,18 +40,7 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
}
|
||||
|
||||
// Add any prefix aliases (e.g., "U" to "UP").
|
||||
for (alias, source) in PREFIX_REDIRECTS.iter() {
|
||||
prefix_to_codes.insert(
|
||||
(*alias).to_string(),
|
||||
prefix_to_codes
|
||||
.get(&(*source).to_string())
|
||||
.unwrap_or_else(|| panic!("Unknown CheckCode: {source:?}"))
|
||||
.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
// Add any check code aliases (e.g., "U001" to "UP001").
|
||||
for (alias, check_code) in CODE_REDIRECTS.iter() {
|
||||
for (alias, check_code) in PREFIX_REDIRECTS.iter() {
|
||||
prefix_to_codes.insert(
|
||||
(*alias).to_string(),
|
||||
prefix_to_codes
|
||||
@@ -76,7 +65,8 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
.derive("Ord")
|
||||
.derive("Clone")
|
||||
.derive("Serialize")
|
||||
.derive("Deserialize");
|
||||
.derive("Deserialize")
|
||||
.derive("JsonSchema");
|
||||
for prefix in prefix_to_codes.keys() {
|
||||
gen = gen.push_variant(Variant::new(prefix.to_string()));
|
||||
}
|
||||
@@ -105,9 +95,9 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
.line("#[allow(clippy::match_same_arms)]")
|
||||
.line("match self {");
|
||||
for (prefix, codes) in &prefix_to_codes {
|
||||
if let Some(target) = CODE_REDIRECTS.get(&prefix.as_str()) {
|
||||
if let Some(target) = PREFIX_REDIRECTS.get(&prefix.as_str()) {
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => {{ eprintln!(\"{{}}{{}} {{}}\", \
|
||||
"CheckCodePrefix::{prefix} => {{ one_time_warning!(\"{{}}{{}} {{}}\", \
|
||||
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been remapped to \
|
||||
`{}`\".bold()); \n vec![{}] }}",
|
||||
prefix,
|
||||
@@ -117,18 +107,6 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
.map(|code| format!("CheckCode::{}", code.as_ref()))
|
||||
.join(", ")
|
||||
));
|
||||
} else if let Some(target) = PREFIX_REDIRECTS.get(&prefix.as_str()) {
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => {{ eprintln!(\"{{}}{{}} {{}}\", \
|
||||
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been remapped to \
|
||||
`{}`\".bold()); \n vec![{}] }}",
|
||||
prefix,
|
||||
target,
|
||||
codes
|
||||
.iter()
|
||||
.map(|code| format!("CheckCode::{}", code.as_ref()))
|
||||
.join(", ")
|
||||
));
|
||||
} else {
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => vec![{}],",
|
||||
@@ -161,8 +139,7 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
_ => panic!("Invalid prefix: {prefix}"),
|
||||
};
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => SuffixLength::{},",
|
||||
specificity
|
||||
"CheckCodePrefix::{prefix} => SuffixLength::{specificity},"
|
||||
));
|
||||
}
|
||||
gen.line("}");
|
||||
@@ -175,6 +152,8 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
output.push('\n');
|
||||
output.push_str("use colored::Colorize;");
|
||||
output.push('\n');
|
||||
output.push_str("use schemars::JsonSchema;");
|
||||
output.push('\n');
|
||||
output.push_str("use serde::{Deserialize, Serialize};");
|
||||
output.push('\n');
|
||||
output.push_str("use strum_macros::{AsRefStr, EnumString};");
|
||||
@@ -182,6 +161,8 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
output.push('\n');
|
||||
output.push_str("use crate::checks::CheckCode;");
|
||||
output.push('\n');
|
||||
output.push_str("use crate::one_time_warning;");
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
output.push_str(&scope.to_string());
|
||||
output.push('\n');
|
||||
@@ -202,12 +183,25 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
|
||||
let rustfmt = Command::new("rustfmt")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
write!(rustfmt.stdin.as_ref().unwrap(), "{output}")?;
|
||||
let Output { status, stdout, .. } = rustfmt.wait_with_output()?;
|
||||
ensure!(status.success(), "rustfmt failed with {status}");
|
||||
|
||||
// Write the output to `src/checks_gen.rs` (or stdout).
|
||||
if cli.dry_run {
|
||||
println!("{output}");
|
||||
println!("{}", String::from_utf8(stdout)?);
|
||||
} else {
|
||||
let mut f = OpenOptions::new().write(true).truncate(true).open(FILE)?;
|
||||
write!(f, "{output}")?;
|
||||
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.expect("Failed to find root directory")
|
||||
.join("src/checks_gen.rs");
|
||||
if fs::read(&file).map_or(true, |old| old != stdout) {
|
||||
fs::write(&file, stdout)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
30
ruff_dev/src/generate_json_schema.rs
Normal file
30
ruff_dev/src/generate_json_schema.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use ruff::settings::options::Options;
|
||||
use schemars::schema_for;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Cli {
|
||||
/// Write the generated table to stdout (rather than to `ruff.schema.json`).
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
}
|
||||
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
let schema = schema_for!(Options);
|
||||
let schema_string = serde_json::to_string_pretty(&schema).unwrap();
|
||||
|
||||
if cli.dry_run {
|
||||
println!("{schema_string}");
|
||||
} else {
|
||||
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.expect("Failed to find root directory")
|
||||
.join("ruff.schema.json");
|
||||
fs::write(file, schema_string.as_bytes())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
)]
|
||||
|
||||
pub mod generate_check_code_prefix;
|
||||
pub mod generate_json_schema;
|
||||
pub mod generate_options;
|
||||
pub mod generate_rules_table;
|
||||
pub mod generate_source_code;
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use ruff_dev::{
|
||||
generate_check_code_prefix, generate_options, generate_rules_table, generate_source_code,
|
||||
print_ast, print_cst, print_tokens,
|
||||
generate_check_code_prefix, generate_json_schema, generate_options, generate_rules_table,
|
||||
generate_source_code, print_ast, print_cst, print_tokens,
|
||||
};
|
||||
|
||||
#[derive(Parser)]
|
||||
@@ -30,6 +30,8 @@ struct Cli {
|
||||
enum Commands {
|
||||
/// Generate the `CheckCodePrefix` enum.
|
||||
GenerateCheckCodePrefix(generate_check_code_prefix::Cli),
|
||||
/// Generate JSON schema for the TOML configuration file.
|
||||
GenerateJSONSchema(generate_json_schema::Cli),
|
||||
/// Generate a Markdown-compatible table of supported lint rules.
|
||||
GenerateRulesTable(generate_rules_table::Cli),
|
||||
/// Generate a Markdown-compatible listing of configuration options.
|
||||
@@ -48,6 +50,7 @@ fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
match &cli.command {
|
||||
Commands::GenerateCheckCodePrefix(args) => generate_check_code_prefix::main(args)?,
|
||||
Commands::GenerateJSONSchema(args) => generate_json_schema::main(args)?,
|
||||
Commands::GenerateRulesTable(args) => generate_rules_table::main(args)?,
|
||||
Commands::GenerateSourceCode(args) => generate_source_code::main(args)?,
|
||||
Commands::GenerateOptions(args) => generate_options::main(args)?,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.185"
|
||||
version = "0.0.193"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -13,13 +13,14 @@
|
||||
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::token::Comma;
|
||||
use syn::{
|
||||
parse_macro_input, AngleBracketedGenericArguments, Attribute, Data, DataStruct, DeriveInput,
|
||||
Field, Fields, Lit, LitStr, Path, PathArguments, PathSegment, Token, Type, TypePath,
|
||||
};
|
||||
|
||||
#[proc_macro_derive(ConfigurationOptions, attributes(option, option_group))]
|
||||
#[proc_macro_derive(ConfigurationOptions, attributes(option, doc, option_group))]
|
||||
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
@@ -39,11 +40,28 @@ fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
|
||||
let mut output = vec![];
|
||||
|
||||
for field in fields.named.iter() {
|
||||
if let Some(attr) = field.attrs.iter().find(|a| a.path.is_ident("option")) {
|
||||
output.push(handle_option(field, attr)?);
|
||||
let docs: Vec<&Attribute> = field
|
||||
.attrs
|
||||
.iter()
|
||||
.filter(|attr| attr.path.is_ident("doc"))
|
||||
.collect();
|
||||
|
||||
if docs.is_empty() {
|
||||
return Err(syn::Error::new(
|
||||
field.span(),
|
||||
"Missing documentation for field",
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(attr) = field.attrs.iter().find(|attr| attr.path.is_ident("option")) {
|
||||
output.push(handle_option(field, attr, docs)?);
|
||||
};
|
||||
|
||||
if field.attrs.iter().any(|a| a.path.is_ident("option_group")) {
|
||||
if field
|
||||
.attrs
|
||||
.iter()
|
||||
.any(|attr| attr.path.is_ident("option_group"))
|
||||
{
|
||||
output.push(handle_option_group(field)?);
|
||||
};
|
||||
}
|
||||
@@ -70,8 +88,10 @@ fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
|
||||
/// deriving `ConfigurationOptions`, create code that calls retrieves options
|
||||
/// from that group: `Foobar::get_available_options()`
|
||||
fn handle_option_group(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
|
||||
// unwrap is safe because we're only going over named fields
|
||||
let ident = field.ident.as_ref().unwrap();
|
||||
let ident = field
|
||||
.ident
|
||||
.as_ref()
|
||||
.expect("Expected to handle named fields");
|
||||
|
||||
match &field.ty {
|
||||
Type::Path(TypePath {
|
||||
@@ -103,17 +123,49 @@ fn handle_option_group(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a `doc` attribute into it a string literal.
|
||||
fn parse_doc(doc: &Attribute) -> syn::Result<String> {
|
||||
let doc = doc
|
||||
.parse_meta()
|
||||
.map_err(|e| syn::Error::new(doc.span(), e))?;
|
||||
|
||||
match doc {
|
||||
syn::Meta::NameValue(syn::MetaNameValue {
|
||||
lit: Lit::Str(lit_str),
|
||||
..
|
||||
}) => Ok(lit_str.value()),
|
||||
_ => Err(syn::Error::new(doc.span(), "Expected doc attribute.")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an `#[option(doc="...", default="...", value_type="...",
|
||||
/// example="...")]` attribute and return data in the form of an `OptionField`.
|
||||
fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::TokenStream> {
|
||||
// unwrap is safe because we're only going over named fields
|
||||
let ident = field.ident.as_ref().unwrap();
|
||||
fn handle_option(
|
||||
field: &Field,
|
||||
attr: &Attribute,
|
||||
docs: Vec<&Attribute>,
|
||||
) -> syn::Result<proc_macro2::TokenStream> {
|
||||
// Convert the list of `doc` attributes into a single string.
|
||||
let doc = textwrap::dedent(
|
||||
&docs
|
||||
.into_iter()
|
||||
.map(parse_doc)
|
||||
.collect::<syn::Result<Vec<_>>>()?
|
||||
.join("\n"),
|
||||
)
|
||||
.trim_matches('\n')
|
||||
.to_string();
|
||||
|
||||
let ident = field
|
||||
.ident
|
||||
.as_ref()
|
||||
.expect("Expected to handle named fields");
|
||||
|
||||
let FieldAttributes {
|
||||
doc,
|
||||
default,
|
||||
value_type,
|
||||
example,
|
||||
..
|
||||
} = attr.parse_args::<FieldAttributes>()?;
|
||||
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
|
||||
|
||||
@@ -130,7 +182,6 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::To
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FieldAttributes {
|
||||
doc: String,
|
||||
default: String,
|
||||
value_type: String,
|
||||
example: String,
|
||||
@@ -138,8 +189,6 @@ struct FieldAttributes {
|
||||
|
||||
impl Parse for FieldAttributes {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let doc = _parse_key_value(input, "doc")?;
|
||||
input.parse::<Comma>()?;
|
||||
let default = _parse_key_value(input, "default")?;
|
||||
input.parse::<Comma>()?;
|
||||
let value_type = _parse_key_value(input, "value_type")?;
|
||||
@@ -150,7 +199,6 @@ impl Parse for FieldAttributes {
|
||||
}
|
||||
|
||||
Ok(FieldAttributes {
|
||||
doc: textwrap::dedent(&doc).trim_matches('\n').to_string(),
|
||||
default,
|
||||
value_type,
|
||||
example: textwrap::dedent(&example).trim_matches('\n').to_string(),
|
||||
|
||||
@@ -3,7 +3,8 @@ use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::{
|
||||
Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location, Stmt, StmtKind,
|
||||
Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword, KeywordData,
|
||||
Location, Stmt, StmtKind,
|
||||
};
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::Tok;
|
||||
@@ -210,6 +211,34 @@ pub fn is_constant_non_singleton(expr: &Expr) -> bool {
|
||||
is_constant(expr) && !is_singleton(expr)
|
||||
}
|
||||
|
||||
/// Return the `Keyword` with the given name, if it's present in the list of
|
||||
/// `Keyword` arguments.
|
||||
pub fn find_keyword<'a>(keywords: &'a [Keyword], keyword_name: &str) -> Option<&'a Keyword> {
|
||||
keywords.iter().find(|keyword| {
|
||||
let KeywordData { arg, .. } = &keyword.node;
|
||||
arg.as_ref().map_or(false, |arg| arg == keyword_name)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if an `Expr` is `None`.
|
||||
pub fn is_const_none(expr: &Expr) -> bool {
|
||||
matches!(
|
||||
&expr.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::None,
|
||||
kind: None
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Return `true` if a keyword argument is present with a non-`None` value.
|
||||
pub fn has_non_none_keyword(keywords: &[Keyword], keyword: &str) -> bool {
|
||||
find_keyword(keywords, keyword).map_or(false, |keyword| {
|
||||
let KeywordData { value, .. } = &keyword.node;
|
||||
!is_const_none(value)
|
||||
})
|
||||
}
|
||||
|
||||
/// Extract the names of all handled exceptions.
|
||||
pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<Vec<&str>> {
|
||||
let mut handler_names = vec![];
|
||||
|
||||
39
src/cache.rs
39
src/cache.rs
@@ -3,11 +3,12 @@ use std::fs;
|
||||
use std::fs::{create_dir_all, File, Metadata};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use filetime::FileTime;
|
||||
use log::error;
|
||||
use once_cell::sync::Lazy;
|
||||
use path_absolutize::Absolutize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -15,6 +16,7 @@ use crate::autofix::fixer;
|
||||
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");
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -34,12 +36,16 @@ struct CheckResult {
|
||||
messages: Vec<Message>,
|
||||
}
|
||||
|
||||
fn cache_dir() -> &'static str {
|
||||
"./.ruff_cache"
|
||||
/// Return the cache directory for a given project root. Defers to the
|
||||
/// `RUFF_CACHE_DIR` environment variable, if set.
|
||||
pub fn cache_dir(project_root: &Path) -> PathBuf {
|
||||
CACHE_DIR
|
||||
.as_ref()
|
||||
.map_or_else(|| project_root.join(".ruff_cache"), PathBuf::from)
|
||||
}
|
||||
|
||||
fn content_dir() -> &'static str {
|
||||
"content"
|
||||
fn content_dir() -> &'static Path {
|
||||
Path::new("content")
|
||||
}
|
||||
|
||||
fn cache_key<P: AsRef<Path>>(path: P, settings: &Settings, autofix: fixer::Mode) -> u64 {
|
||||
@@ -51,10 +57,8 @@ fn cache_key<P: AsRef<Path>>(path: P, settings: &Settings, autofix: fixer::Mode)
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
/// Initialize the cache directory.
|
||||
pub fn init() -> Result<()> {
|
||||
let path = Path::new(cache_dir());
|
||||
|
||||
/// Initialize the cache at the specified `Path`.
|
||||
pub fn init(path: &Path) -> Result<()> {
|
||||
// Create the cache directories.
|
||||
create_dir_all(path.join(content_dir()))?;
|
||||
|
||||
@@ -73,21 +77,15 @@ pub fn init() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_sync(key: u64, value: &[u8]) -> Result<(), std::io::Error> {
|
||||
fn write_sync(cache_dir: &Path, key: u64, value: &[u8]) -> Result<(), std::io::Error> {
|
||||
fs::write(
|
||||
Path::new(cache_dir())
|
||||
.join(content_dir())
|
||||
.join(format!("{key:x}")),
|
||||
cache_dir.join(content_dir()).join(format!("{key:x}")),
|
||||
value,
|
||||
)
|
||||
}
|
||||
|
||||
fn read_sync(key: u64) -> Result<Vec<u8>, std::io::Error> {
|
||||
fs::read(
|
||||
Path::new(cache_dir())
|
||||
.join(content_dir())
|
||||
.join(format!("{key:x}")),
|
||||
)
|
||||
fn read_sync(cache_dir: &Path, key: u64) -> Result<Vec<u8>, std::io::Error> {
|
||||
fs::read(cache_dir.join(content_dir()).join(format!("{key:x}")))
|
||||
}
|
||||
|
||||
/// Get a value from the cache.
|
||||
@@ -102,7 +100,7 @@ pub fn get<P: AsRef<Path>>(
|
||||
return None;
|
||||
};
|
||||
|
||||
let encoded = read_sync(cache_key(path, settings, autofix)).ok()?;
|
||||
let encoded = read_sync(&settings.cache_dir, cache_key(path, settings, autofix)).ok()?;
|
||||
let (mtime, messages) = match bincode::deserialize::<CheckResult>(&encoded[..]) {
|
||||
Ok(CheckResult {
|
||||
metadata: CacheMetadata { mtime },
|
||||
@@ -139,6 +137,7 @@ pub fn set<P: AsRef<Path>>(
|
||||
messages,
|
||||
};
|
||||
if let Err(e) = write_sync(
|
||||
&settings.cache_dir,
|
||||
cache_key(path, settings, autofix),
|
||||
&bincode::serialize(&check_result).unwrap(),
|
||||
) {
|
||||
|
||||
@@ -37,10 +37,10 @@ use crate::vendored::cformat::{CFormatError, CFormatErrorType};
|
||||
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
|
||||
use crate::{
|
||||
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
|
||||
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, 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, visibility,
|
||||
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, visibility,
|
||||
};
|
||||
|
||||
const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
@@ -73,6 +73,7 @@ pub struct Checker<'a> {
|
||||
pub(crate) child_to_parent: FxHashMap<RefEquality<'a, Stmt>, RefEquality<'a, Stmt>>,
|
||||
pub(crate) bindings: Vec<Binding<'a>>,
|
||||
pub(crate) redefinitions: IntMap<usize, Vec<usize>>,
|
||||
exprs: Vec<RefEquality<'a, Expr>>,
|
||||
scopes: Vec<Scope<'a>>,
|
||||
scope_stack: Vec<usize>,
|
||||
dead_scopes: Vec<usize>,
|
||||
@@ -95,7 +96,7 @@ pub struct Checker<'a> {
|
||||
annotations_future_enabled: bool,
|
||||
except_handlers: Vec<Vec<Vec<&'a str>>>,
|
||||
// Check-specific state.
|
||||
pub(crate) seen_b023: Vec<&'a Expr>,
|
||||
pub(crate) flake8_bugbear_seen: Vec<&'a Expr>,
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
@@ -124,6 +125,7 @@ impl<'a> Checker<'a> {
|
||||
child_to_parent: FxHashMap::default(),
|
||||
bindings: vec![],
|
||||
redefinitions: IntMap::default(),
|
||||
exprs: vec![],
|
||||
scopes: vec![],
|
||||
scope_stack: vec![],
|
||||
dead_scopes: vec![],
|
||||
@@ -149,24 +151,19 @@ impl<'a> Checker<'a> {
|
||||
annotations_future_enabled: false,
|
||||
except_handlers: vec![],
|
||||
// Check-specific state.
|
||||
seen_b023: vec![],
|
||||
flake8_bugbear_seen: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a `Check` to the `Checker`.
|
||||
pub(crate) fn add_check(&mut self, check: Check) {
|
||||
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.
|
||||
let check = if let Some(range) = self.in_f_string {
|
||||
Check {
|
||||
location: range.location,
|
||||
end_location: range.end_location,
|
||||
..check
|
||||
}
|
||||
} else {
|
||||
check
|
||||
};
|
||||
if let Some(range) = self.in_f_string {
|
||||
check.location = range.location;
|
||||
check.end_location = range.end_location;
|
||||
}
|
||||
self.checks.push(check);
|
||||
}
|
||||
|
||||
@@ -187,6 +184,13 @@ impl<'a> Checker<'a> {
|
||||
&& 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))
|
||||
}
|
||||
|
||||
/// Return `true` if the `Expr` is a reference to `typing.${target}`.
|
||||
pub fn match_typing_expr(&self, expr: &Expr, target: &str) -> bool {
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), &self.import_aliases);
|
||||
@@ -543,7 +547,7 @@ where
|
||||
kind: BindingKind::FunctionDefinition,
|
||||
used: None,
|
||||
range: Range::from_located(stmt),
|
||||
source: Some(self.current_parent().clone()),
|
||||
source: Some(self.current_stmt().clone()),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -624,6 +628,15 @@ where
|
||||
}
|
||||
}
|
||||
StmtKind::Import { names } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E401) {
|
||||
if names.len() > 1 {
|
||||
self.add_check(Check::new(
|
||||
CheckKind::MultipleImportsOnOneLine,
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::E402) {
|
||||
if self.seen_import_boundary && stmt.location.column() == 0 {
|
||||
self.add_check(Check::new(
|
||||
@@ -648,7 +661,7 @@ where
|
||||
),
|
||||
used: None,
|
||||
range: Range::from_located(alias),
|
||||
source: Some(self.current_parent().clone()),
|
||||
source: Some(self.current_stmt().clone()),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
@@ -688,7 +701,7 @@ where
|
||||
None
|
||||
},
|
||||
range: Range::from_located(alias),
|
||||
source: Some(self.current_parent().clone()),
|
||||
source: Some(self.current_stmt().clone()),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -847,7 +860,7 @@ where
|
||||
Range::from_located(alias),
|
||||
)),
|
||||
range: Range::from_located(alias),
|
||||
source: Some(self.current_parent().clone()),
|
||||
source: Some(self.current_stmt().clone()),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -878,7 +891,7 @@ where
|
||||
kind: BindingKind::StarImportation(*level, module.clone()),
|
||||
used: None,
|
||||
range: Range::from_located(stmt),
|
||||
source: Some(self.current_parent().clone()),
|
||||
source: Some(self.current_stmt().clone()),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -947,7 +960,7 @@ where
|
||||
None
|
||||
},
|
||||
range,
|
||||
source: Some(self.current_parent().clone()),
|
||||
source: Some(self.current_stmt().clone()),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1054,17 +1067,11 @@ where
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::EM101)
|
||||
| self.settings.enabled.contains(&CheckCode::EM102)
|
||||
| self.settings.enabled.contains(&CheckCode::EM103)
|
||||
|| self.settings.enabled.contains(&CheckCode::EM102)
|
||||
|| self.settings.enabled.contains(&CheckCode::EM103)
|
||||
{
|
||||
if let Some(exc) = exc {
|
||||
self.add_checks(
|
||||
flake8_errmsg::checks::check_string_in_exception(
|
||||
exc,
|
||||
self.settings.flake8_errmsg.max_string_length,
|
||||
)
|
||||
.into_iter(),
|
||||
);
|
||||
flake8_errmsg::plugins::string_in_exception(self, exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1144,7 +1151,7 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::B014)
|
||||
|| self.settings.enabled.contains(&CheckCode::B025)
|
||||
{
|
||||
flake8_bugbear::plugins::duplicate_exceptions(self, stmt, handlers);
|
||||
flake8_bugbear::plugins::duplicate_exceptions(self, handlers);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B013) {
|
||||
flake8_bugbear::plugins::redundant_tuple_in_exception_handler(self, handlers);
|
||||
@@ -1179,7 +1186,7 @@ where
|
||||
self, stmt, targets, value,
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::PDV901) {
|
||||
if self.settings.enabled.contains(&CheckCode::PD901) {
|
||||
if let Some(check) = pandas_vet::checks::assignment_to_df(targets) {
|
||||
self.add_check(check);
|
||||
}
|
||||
@@ -1382,7 +1389,7 @@ where
|
||||
kind: BindingKind::ClassDefinition,
|
||||
used: None,
|
||||
range: Range::from_located(stmt),
|
||||
source: Some(self.current_parent().clone()),
|
||||
source: Some(self.current_stmt().clone()),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1403,10 +1410,6 @@ where
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'b Expr) {
|
||||
let prev_in_f_string = self.in_f_string;
|
||||
let prev_in_literal = self.in_literal;
|
||||
let prev_in_type_definition = self.in_type_definition;
|
||||
|
||||
if !(self.in_deferred_type_definition || self.in_deferred_string_type_definition)
|
||||
&& self.in_type_definition
|
||||
&& self.annotations_future_enabled
|
||||
@@ -1432,6 +1435,12 @@ where
|
||||
return;
|
||||
}
|
||||
|
||||
self.push_expr(expr);
|
||||
|
||||
let prev_in_f_string = self.in_f_string;
|
||||
let prev_in_literal = self.in_literal;
|
||||
let prev_in_type_definition = self.in_type_definition;
|
||||
|
||||
// Pre-visit.
|
||||
match &expr.node {
|
||||
ExprKind::Subscript { value, slice, .. } => {
|
||||
@@ -1538,15 +1547,21 @@ where
|
||||
pyupgrade::plugins::remove_six_compat(self, expr);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::UP017)
|
||||
&& self.settings.target_version >= PythonVersion::Py311
|
||||
{
|
||||
pyupgrade::plugins::datetime_utc_alias(self, expr);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::YTT202) {
|
||||
flake8_2020::plugins::name_or_attribute(self, expr);
|
||||
}
|
||||
|
||||
for (code, name) in vec![
|
||||
(CheckCode::PDV007, "ix"),
|
||||
(CheckCode::PDV008, "at"),
|
||||
(CheckCode::PDV009, "iat"),
|
||||
(CheckCode::PDV011, "values"),
|
||||
(CheckCode::PD007, "ix"),
|
||||
(CheckCode::PD008, "at"),
|
||||
(CheckCode::PD009, "iat"),
|
||||
(CheckCode::PD011, "values"),
|
||||
] {
|
||||
if self.settings.enabled.contains(&code) {
|
||||
if attr == name {
|
||||
@@ -1629,6 +1644,9 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::UP016) {
|
||||
pyupgrade::plugins::remove_six_compat(self, expr);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP018) {
|
||||
pyupgrade::plugins::native_literals(self, expr, func, args, keywords);
|
||||
}
|
||||
|
||||
// flake8-super
|
||||
if self.settings.enabled.contains(&CheckCode::UP008) {
|
||||
@@ -1639,7 +1657,7 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::T201)
|
||||
|| self.settings.enabled.contains(&CheckCode::T203)
|
||||
{
|
||||
flake8_print::plugins::print_call(self, expr, func);
|
||||
flake8_print::plugins::print_call(self, expr, func, keywords);
|
||||
}
|
||||
|
||||
// flake8-bugbear
|
||||
@@ -1928,17 +1946,17 @@ where
|
||||
}
|
||||
|
||||
// pandas-vet
|
||||
if self.settings.enabled.contains(&CheckCode::PDV002) {
|
||||
if self.settings.enabled.contains(&CheckCode::PD002) {
|
||||
self.add_checks(pandas_vet::checks::inplace_argument(keywords).into_iter());
|
||||
}
|
||||
|
||||
for (code, name) in vec![
|
||||
(CheckCode::PDV003, "isnull"),
|
||||
(CheckCode::PDV004, "notnull"),
|
||||
(CheckCode::PDV010, "pivot"),
|
||||
(CheckCode::PDV010, "unstack"),
|
||||
(CheckCode::PDV012, "read_table"),
|
||||
(CheckCode::PDV013, "stack"),
|
||||
(CheckCode::PD003, "isnull"),
|
||||
(CheckCode::PD004, "notnull"),
|
||||
(CheckCode::PD010, "pivot"),
|
||||
(CheckCode::PD010, "unstack"),
|
||||
(CheckCode::PD012, "read_table"),
|
||||
(CheckCode::PD013, "stack"),
|
||||
] {
|
||||
if self.settings.enabled.contains(&code) {
|
||||
if let ExprKind::Attribute { attr, .. } = &func.node {
|
||||
@@ -1949,15 +1967,90 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::PDV015) {
|
||||
if self.settings.enabled.contains(&CheckCode::PD015) {
|
||||
if let Some(check) = pandas_vet::checks::use_of_pd_merge(func) {
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
// flake8-datetimez
|
||||
if self.settings.enabled.contains(&CheckCode::DTZ001) {
|
||||
flake8_datetimez::plugins::call_datetime_without_tzinfo(
|
||||
self,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
Range::from_located(expr),
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::DTZ002) {
|
||||
flake8_datetimez::plugins::call_datetime_today(
|
||||
self,
|
||||
func,
|
||||
Range::from_located(expr),
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::DTZ003) {
|
||||
flake8_datetimez::plugins::call_datetime_utcnow(
|
||||
self,
|
||||
func,
|
||||
Range::from_located(expr),
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::DTZ004) {
|
||||
flake8_datetimez::plugins::call_datetime_utcfromtimestamp(
|
||||
self,
|
||||
func,
|
||||
Range::from_located(expr),
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::DTZ005) {
|
||||
flake8_datetimez::plugins::call_datetime_now_without_tzinfo(
|
||||
self,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
Range::from_located(expr),
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::DTZ006) {
|
||||
flake8_datetimez::plugins::call_datetime_fromtimestamp(
|
||||
self,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
Range::from_located(expr),
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::DTZ007) {
|
||||
flake8_datetimez::plugins::call_datetime_strptime_without_zone(
|
||||
self,
|
||||
func,
|
||||
args,
|
||||
Range::from_located(expr),
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::DTZ011) {
|
||||
flake8_datetimez::plugins::call_date_today(
|
||||
self,
|
||||
func,
|
||||
Range::from_located(expr),
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::DTZ012) {
|
||||
flake8_datetimez::plugins::call_date_fromtimestamp(
|
||||
self,
|
||||
func,
|
||||
Range::from_located(expr),
|
||||
);
|
||||
}
|
||||
|
||||
// pygrep-hooks
|
||||
if self.settings.enabled.contains(&CheckCode::PGH001) {
|
||||
pygrep_hooks::checks::no_eval(self, func);
|
||||
pygrep_hooks::plugins::no_eval(self, func);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::PGH002) {
|
||||
pygrep_hooks::plugins::deprecated_log_warn(self, func);
|
||||
}
|
||||
|
||||
// pylint
|
||||
@@ -2428,6 +2521,29 @@ where
|
||||
self.visit_expr(value);
|
||||
self.in_type_definition = prev_in_type_definition;
|
||||
}
|
||||
} else if ["Arg", "DefaultArg", "NamedArg", "DefaultNamedArg"]
|
||||
.iter()
|
||||
.any(|target| {
|
||||
match_call_path(&call_path, "mypy_extensions", target, &self.from_imports)
|
||||
})
|
||||
{
|
||||
self.visit_expr(func);
|
||||
|
||||
// Ex) DefaultNamedArg(bool | None, name="some_prop_name")
|
||||
let mut arguments = args.iter().chain(keywords.iter().map(|keyword| {
|
||||
let KeywordData { value, .. } = &keyword.node;
|
||||
value
|
||||
}));
|
||||
if let Some(expr) = arguments.next() {
|
||||
self.in_type_definition = true;
|
||||
self.visit_expr(expr);
|
||||
self.in_type_definition = prev_in_type_definition;
|
||||
}
|
||||
for expr in arguments {
|
||||
self.in_type_definition = false;
|
||||
self.visit_expr(expr);
|
||||
self.in_type_definition = prev_in_type_definition;
|
||||
}
|
||||
} else {
|
||||
visitor::walk_expr(self, expr);
|
||||
}
|
||||
@@ -2507,6 +2623,8 @@ where
|
||||
self.in_type_definition = prev_in_type_definition;
|
||||
self.in_literal = prev_in_literal;
|
||||
self.in_f_string = prev_in_f_string;
|
||||
|
||||
self.pop_expr();
|
||||
}
|
||||
|
||||
fn visit_excepthandler(&mut self, excepthandler: &'b Excepthandler) {
|
||||
@@ -2652,7 +2770,7 @@ where
|
||||
kind: BindingKind::Argument,
|
||||
used: None,
|
||||
range: Range::from_located(arg),
|
||||
source: Some(self.current_parent().clone()),
|
||||
source: Some(self.current_stmt().clone()),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -2687,7 +2805,17 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
fn pop_parent(&mut self) {
|
||||
self.parents.pop().expect("Attempted to pop without scope");
|
||||
self.parents.pop().expect("Attempted to pop without parent");
|
||||
}
|
||||
|
||||
fn push_expr(&mut self, expr: &'a Expr) {
|
||||
self.exprs.push(RefEquality(expr));
|
||||
}
|
||||
|
||||
fn pop_expr(&mut self) {
|
||||
self.exprs
|
||||
.pop()
|
||||
.expect("Attempted to pop without expression");
|
||||
}
|
||||
|
||||
fn push_scope(&mut self, scope: Scope<'a>) {
|
||||
@@ -2718,6 +2846,36 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the current `Stmt`.
|
||||
pub fn current_stmt(&self) -> &RefEquality<'a, Stmt> {
|
||||
self.parents.iter().rev().next().expect("No parent found")
|
||||
}
|
||||
|
||||
/// Return the parent `Stmt` of the current `Stmt`, if any.
|
||||
pub fn current_stmt_parent(&self) -> Option<&RefEquality<'a, Stmt>> {
|
||||
self.parents.iter().rev().nth(1)
|
||||
}
|
||||
|
||||
/// Return the grandparent `Stmt` of the current `Stmt`, if any.
|
||||
pub fn current_stmt_grandparent(&self) -> Option<&RefEquality<'a, Stmt>> {
|
||||
self.parents.iter().rev().nth(2)
|
||||
}
|
||||
|
||||
/// Return the current `Expr`.
|
||||
pub fn current_expr(&self) -> Option<&RefEquality<'a, Expr>> {
|
||||
self.exprs.iter().rev().next()
|
||||
}
|
||||
|
||||
/// Return the parent `Expr` of the current `Expr`.
|
||||
pub fn current_expr_parent(&self) -> Option<&RefEquality<'a, Expr>> {
|
||||
self.exprs.iter().rev().nth(1)
|
||||
}
|
||||
|
||||
/// Return the grandparent `Expr` of the current `Expr`.
|
||||
pub fn current_expr_grandparent(&self) -> Option<&RefEquality<'a, Expr>> {
|
||||
self.exprs.iter().rev().nth(2)
|
||||
}
|
||||
|
||||
pub fn current_scope(&self) -> &Scope {
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found"))]
|
||||
}
|
||||
@@ -2729,14 +2887,6 @@ impl<'a> Checker<'a> {
|
||||
.map(|index| &self.scopes[*index])
|
||||
}
|
||||
|
||||
pub fn current_parent(&self) -> &RefEquality<'a, Stmt> {
|
||||
self.parents.iter().rev().next().expect("No parent found")
|
||||
}
|
||||
|
||||
pub fn current_grandparent(&self) -> Option<&RefEquality<'a, Stmt>> {
|
||||
self.parents.iter().rev().nth(1)
|
||||
}
|
||||
|
||||
fn add_binding<'b>(&mut self, name: &'b str, binding: Binding<'a>)
|
||||
where
|
||||
'b: 'a,
|
||||
@@ -2966,7 +3116,7 @@ impl<'a> Checker<'a> {
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
let parent = self.current_parent().0;
|
||||
let parent = self.current_stmt().0;
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::F823) {
|
||||
let scopes: Vec<&Scope> = self
|
||||
@@ -3011,7 +3161,7 @@ impl<'a> Checker<'a> {
|
||||
kind: BindingKind::Annotation,
|
||||
used: None,
|
||||
range: Range::from_located(expr),
|
||||
source: Some(self.current_parent().clone()),
|
||||
source: Some(self.current_stmt().clone()),
|
||||
},
|
||||
);
|
||||
return;
|
||||
@@ -3028,7 +3178,7 @@ impl<'a> Checker<'a> {
|
||||
kind: BindingKind::LoopVar,
|
||||
used: None,
|
||||
range: Range::from_located(expr),
|
||||
source: Some(self.current_parent().clone()),
|
||||
source: Some(self.current_stmt().clone()),
|
||||
},
|
||||
);
|
||||
return;
|
||||
@@ -3041,7 +3191,7 @@ impl<'a> Checker<'a> {
|
||||
kind: BindingKind::Binding,
|
||||
used: None,
|
||||
range: Range::from_located(expr),
|
||||
source: Some(self.current_parent().clone()),
|
||||
source: Some(self.current_stmt().clone()),
|
||||
},
|
||||
);
|
||||
return;
|
||||
@@ -3091,7 +3241,7 @@ impl<'a> Checker<'a> {
|
||||
)),
|
||||
used: None,
|
||||
range: Range::from_located(expr),
|
||||
source: Some(self.current_parent().clone()),
|
||||
source: Some(self.current_stmt().clone()),
|
||||
},
|
||||
);
|
||||
return;
|
||||
@@ -3104,7 +3254,7 @@ impl<'a> Checker<'a> {
|
||||
kind: BindingKind::Assignment,
|
||||
used: None,
|
||||
range: Range::from_located(expr),
|
||||
source: Some(self.current_parent().clone()),
|
||||
source: Some(self.current_stmt().clone()),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,42 +2,64 @@
|
||||
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::pycodestyle::checks::{line_too_long, no_newline_at_end_of_file};
|
||||
use crate::pygrep_hooks::plugins::blanket_type_ignore;
|
||||
use crate::pyupgrade::checks::unnecessary_coding_comment;
|
||||
use crate::settings::{flags, Settings};
|
||||
|
||||
pub fn check_lines(contents: &str, settings: &Settings, autofix: flags::Autofix) -> Vec<Check> {
|
||||
pub fn check_lines(
|
||||
contents: &str,
|
||||
commented_lines: &[usize],
|
||||
settings: &Settings,
|
||||
autofix: flags::Autofix,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
let enforce_unnecessary_coding_comment = settings.enabled.contains(&CheckCode::UP009);
|
||||
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
|
||||
let enforce_no_newline_at_end_of_file = settings.enabled.contains(&CheckCode::W292);
|
||||
let enforce_blanket_type_ignore = settings.enabled.contains(&CheckCode::PGH003);
|
||||
|
||||
for (lineno, line) in contents.lines().enumerate() {
|
||||
// Enforce unnecessary coding comments (UP009).
|
||||
if enforce_unnecessary_coding_comment {
|
||||
if lineno < 2 {
|
||||
if let Some(check) = unnecessary_coding_comment(
|
||||
lineno,
|
||||
line,
|
||||
matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.fixable.contains(&CheckCode::UP009),
|
||||
) {
|
||||
checks.push(check);
|
||||
let mut commented_lines_iter = commented_lines.iter().peekable();
|
||||
for (index, line) in contents.lines().enumerate() {
|
||||
while commented_lines_iter
|
||||
.next_if(|lineno| &(index + 1) == *lineno)
|
||||
.is_some()
|
||||
{
|
||||
if enforce_unnecessary_coding_comment {
|
||||
if index < 2 {
|
||||
if let Some(check) = unnecessary_coding_comment(
|
||||
index,
|
||||
line,
|
||||
matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.fixable.contains(&CheckCode::UP009),
|
||||
) {
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if enforce_blanket_type_ignore {
|
||||
if commented_lines.contains(&(index + 1)) {
|
||||
if let Some(check) = blanket_type_ignore(index, line) {
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce line length violations (E501).
|
||||
if enforce_line_too_long {
|
||||
if let Some(check) = line_too_long(lineno, line, settings.line_length) {
|
||||
if let Some(check) = line_too_long(index, line, settings.line_length) {
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce newlines at end of files (W292).
|
||||
if enforce_no_newline_at_end_of_file {
|
||||
if let Some(check) = no_newline_at_end_of_file(contents) {
|
||||
if let Some(check) = no_newline_at_end_of_file(
|
||||
contents,
|
||||
matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.fixable.contains(&CheckCode::W292),
|
||||
) {
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
@@ -58,6 +80,7 @@ mod tests {
|
||||
let check_with_max_line_length = |line_length: usize| {
|
||||
check_lines(
|
||||
line,
|
||||
&[],
|
||||
&Settings {
|
||||
line_length,
|
||||
..Settings::for_rule(CheckCode::E501)
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
//! `NoQA` enforcement and validation.
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use nohash_hasher::IntMap;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checks::{Check, CheckCode, CheckKind, CODE_REDIRECTS};
|
||||
use crate::checks::{Check, CheckCode, CheckKind, UnusedCodes, CODE_REDIRECTS};
|
||||
use crate::noqa;
|
||||
use crate::noqa::{is_file_exempt, Directive};
|
||||
use crate::settings::{flags, Settings};
|
||||
@@ -98,20 +100,56 @@ pub fn check_noqa(
|
||||
}
|
||||
}
|
||||
Directive::Codes(spaces, start, end, codes) => {
|
||||
let mut invalid_codes = vec![];
|
||||
let mut disabled_codes = vec![];
|
||||
let mut unknown_codes = vec![];
|
||||
let mut unmatched_codes = vec![];
|
||||
let mut valid_codes = vec![];
|
||||
let mut self_ignore = false;
|
||||
for code in codes {
|
||||
let code = CODE_REDIRECTS.get(code).map_or(code, AsRef::as_ref);
|
||||
if code == CheckCode::RUF100.as_ref() {
|
||||
self_ignore = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if matches.contains(&code) || settings.external.contains(code) {
|
||||
valid_codes.push(code.to_string());
|
||||
valid_codes.push(code);
|
||||
} else {
|
||||
invalid_codes.push(code.to_string());
|
||||
if let Ok(check_code) = CheckCode::from_str(code) {
|
||||
if settings.enabled.contains(&check_code) {
|
||||
unmatched_codes.push(code);
|
||||
} else {
|
||||
disabled_codes.push(code);
|
||||
}
|
||||
} else {
|
||||
unknown_codes.push(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !invalid_codes.is_empty() {
|
||||
if self_ignore {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !(disabled_codes.is_empty()
|
||||
&& unknown_codes.is_empty()
|
||||
&& unmatched_codes.is_empty())
|
||||
{
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedNOQA(Some(invalid_codes)),
|
||||
CheckKind::UnusedNOQA(Some(UnusedCodes {
|
||||
disabled: disabled_codes
|
||||
.iter()
|
||||
.map(|code| (*code).to_string())
|
||||
.collect(),
|
||||
unknown: unknown_codes
|
||||
.iter()
|
||||
.map(|code| (*code).to_string())
|
||||
.collect(),
|
||||
unmatched: unmatched_codes
|
||||
.iter()
|
||||
.map(|code| (*code).to_string())
|
||||
.collect(),
|
||||
})),
|
||||
Range {
|
||||
location: Location::new(row + 1, start),
|
||||
end_location: Location::new(row + 1, end),
|
||||
|
||||
@@ -89,7 +89,11 @@ pub fn check_tokens(
|
||||
if enforce_invalid_escape_sequence {
|
||||
if matches!(tok, Tok::String { .. }) {
|
||||
checks.extend(pycodestyle::checks::invalid_escape_sequence(
|
||||
locator, start, end,
|
||||
locator,
|
||||
start,
|
||||
end,
|
||||
matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.fixable.contains(&CheckCode::W605),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
626
src/checks.rs
626
src/checks.rs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
17
src/cli.rs
17
src/cli.rs
@@ -92,6 +92,12 @@ pub struct Cli {
|
||||
respect_gitignore: bool,
|
||||
#[clap(long, overrides_with("respect_gitignore"), hide = true)]
|
||||
no_respect_gitignore: bool,
|
||||
/// Enforce exclusions, even for paths passed to Ruff directly on the
|
||||
/// command-line.
|
||||
#[arg(long, overrides_with("no_show_source"))]
|
||||
force_exclude: bool,
|
||||
#[clap(long, overrides_with("force_exclude"), hide = true)]
|
||||
no_force_exclude: bool,
|
||||
/// See the files Ruff will be run against with the current settings.
|
||||
#[arg(long)]
|
||||
pub show_files: bool,
|
||||
@@ -120,13 +126,16 @@ pub struct Cli {
|
||||
pub autoformat: bool,
|
||||
/// The name of the file when passing it through stdin.
|
||||
#[arg(long)]
|
||||
pub stdin_filename: Option<String>,
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
/// Explain a rule.
|
||||
#[arg(long)]
|
||||
pub explain: Option<CheckCode>,
|
||||
/// Generate shell completion
|
||||
#[arg(long, hide = true, value_name = "SHELL")]
|
||||
pub generate_shell_completion: Option<clap_complete_command::Shell>,
|
||||
/// Path to the cache directory.
|
||||
#[arg(long)]
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
@@ -173,6 +182,8 @@ impl Cli {
|
||||
// TODO(charlie): Included in `pyproject.toml`, but not inherited.
|
||||
fix: resolve_bool_arg(self.fix, self.no_fix),
|
||||
format: self.format,
|
||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||
cache_dir: self.cache_dir,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -203,7 +214,7 @@ pub struct Arguments {
|
||||
pub show_files: bool,
|
||||
pub show_settings: bool,
|
||||
pub silent: bool,
|
||||
pub stdin_filename: Option<String>,
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
pub verbose: bool,
|
||||
pub watch: bool,
|
||||
}
|
||||
@@ -230,6 +241,8 @@ pub struct Overrides {
|
||||
// TODO(charlie): Captured in pyproject.toml as a default, but not part of `Settings`.
|
||||
pub fix: Option<bool>,
|
||||
pub format: Option<SerializationFormat>,
|
||||
pub force_exclude: Option<bool>,
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Map the CLI settings to a `LogLevel`.
|
||||
|
||||
@@ -20,7 +20,7 @@ use crate::message::Message;
|
||||
use crate::resolver::{FileDiscovery, PyprojectDiscovery};
|
||||
use crate::settings::flags;
|
||||
use crate::settings::types::SerializationFormat;
|
||||
use crate::{packages, resolver};
|
||||
use crate::{cache, packages, resolver};
|
||||
|
||||
/// Run the linter over a collection of files.
|
||||
pub fn run(
|
||||
@@ -47,6 +47,30 @@ pub fn run(
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
// Initialize the cache.
|
||||
if matches!(cache, flags::Cache::Enabled) {
|
||||
match &pyproject_strategy {
|
||||
PyprojectDiscovery::Fixed(settings) => {
|
||||
if let Err(e) = cache::init(&settings.cache_dir) {
|
||||
error!(
|
||||
"Failed to initialize cache at {}: {e:?}",
|
||||
settings.cache_dir.to_string_lossy()
|
||||
);
|
||||
}
|
||||
}
|
||||
PyprojectDiscovery::Hierarchical(default) => {
|
||||
for settings in std::iter::once(default).chain(resolver.iter()) {
|
||||
if let Err(e) = cache::init(&settings.cache_dir) {
|
||||
error!(
|
||||
"Failed to initialize cache at {}: {e:?}",
|
||||
settings.cache_dir.to_string_lossy()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
let mut diagnostics: Diagnostics = par_iter(&paths)
|
||||
.map(|entry| {
|
||||
@@ -114,16 +138,26 @@ fn read_from_stdin() -> Result<String> {
|
||||
|
||||
/// Run the linter over a single file, read from `stdin`.
|
||||
pub fn run_stdin(
|
||||
strategy: &PyprojectDiscovery,
|
||||
filename: &Path,
|
||||
filename: Option<&Path>,
|
||||
pyproject_strategy: &PyprojectDiscovery,
|
||||
file_strategy: &FileDiscovery,
|
||||
overrides: &Overrides,
|
||||
autofix: fixer::Mode,
|
||||
) -> Result<Diagnostics> {
|
||||
let stdin = read_from_stdin()?;
|
||||
let settings = match strategy {
|
||||
if let Some(filename) = filename {
|
||||
if !resolver::python_file_at_path(filename, pyproject_strategy, file_strategy, overrides)? {
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
}
|
||||
let settings = match pyproject_strategy {
|
||||
PyprojectDiscovery::Fixed(settings) => settings,
|
||||
PyprojectDiscovery::Hierarchical(settings) => settings,
|
||||
};
|
||||
let mut diagnostics = lint_stdin(filename, &stdin, settings, autofix)?;
|
||||
let package_root = filename
|
||||
.and_then(Path::parent)
|
||||
.and_then(packages::detect_package_root);
|
||||
let stdin = read_from_stdin()?;
|
||||
let mut diagnostics = lint_stdin(filename, package_root, &stdin, settings, autofix)?;
|
||||
diagnostics.messages.sort_unstable();
|
||||
Ok(diagnostics)
|
||||
}
|
||||
|
||||
@@ -1,51 +1,53 @@
|
||||
//! Settings for the `flake-annotations` plugin.
|
||||
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
|
||||
)]
|
||||
#[serde(
|
||||
deny_unknown_fields,
|
||||
rename_all = "kebab-case",
|
||||
rename = "Flake8AnnotationsOptions"
|
||||
)]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
doc = r#"
|
||||
Whether to allow the omission of a return type hint for `__init__` if at least one
|
||||
argument is annotated.
|
||||
"#,
|
||||
default = "false",
|
||||
value_type = "bool",
|
||||
example = "mypy-init-return = true"
|
||||
)]
|
||||
/// Whether to allow the omission of a return type hint for `__init__` if at
|
||||
/// least one argument is annotated.
|
||||
pub mypy_init_return: Option<bool>,
|
||||
#[option(
|
||||
doc = r#"
|
||||
Whether to suppress `ANN000`-level errors for arguments matching the "dummy" variable
|
||||
regex (like `_`).
|
||||
"#,
|
||||
default = "false",
|
||||
value_type = "bool",
|
||||
example = "suppress-dummy-args = true"
|
||||
)]
|
||||
/// Whether to suppress `ANN000`-level errors for arguments matching the
|
||||
/// "dummy" variable regex (like `_`).
|
||||
pub suppress_dummy_args: Option<bool>,
|
||||
#[option(
|
||||
doc = r#"
|
||||
Whether to suppress `ANN200`-level errors for functions that meet either of the
|
||||
following criteria:
|
||||
|
||||
- Contain no `return` statement.
|
||||
- Explicit `return` statement(s) all return `None` (explicitly or implicitly).
|
||||
"#,
|
||||
default = "false",
|
||||
value_type = "bool",
|
||||
example = "suppress-none-returning = true"
|
||||
)]
|
||||
/// Whether to suppress `ANN200`-level errors for functions that meet either
|
||||
/// of the following criteria:
|
||||
///
|
||||
/// - Contain no `return` statement.
|
||||
/// - Explicit `return` statement(s) all return `None` (explicitly or
|
||||
/// implicitly).
|
||||
pub suppress_none_returning: Option<bool>,
|
||||
#[option(
|
||||
doc = "Whether to suppress `ANN401` for dynamically typed `*args` and `**kwargs` \
|
||||
arguments.",
|
||||
default = "false",
|
||||
value_type = "bool",
|
||||
example = "allow-star-arg-any = true"
|
||||
)]
|
||||
/// Whether to suppress `ANN401` for dynamically typed `*args` and
|
||||
/// `**kwargs` arguments.
|
||||
pub allow_star_arg_any: Option<bool>,
|
||||
}
|
||||
|
||||
|
||||
@@ -5,16 +5,35 @@ use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
const FUNC_NAME_ALLOWLIST: &[&str] = &["get", "setdefault", "pop", "fromkeys"];
|
||||
const FUNC_NAME_ALLOWLIST: &[&str] = &[
|
||||
"assertEqual",
|
||||
"assertEquals",
|
||||
"assertNotEqual",
|
||||
"assertNotEquals",
|
||||
"failIfEqual",
|
||||
"failUnlessEqual",
|
||||
"fromkeys",
|
||||
"get",
|
||||
"getattr",
|
||||
"index",
|
||||
"pop",
|
||||
"setattr",
|
||||
"setdefault",
|
||||
];
|
||||
|
||||
/// Returns `true` if an argument is allowed to use a boolean trap. To return
|
||||
/// `true`, the function name must be explicitly allowed, and the argument must
|
||||
/// be either the first or second argument in the call.
|
||||
fn allow_boolean_trap(func: &Expr) -> bool {
|
||||
let ExprKind::Attribute { attr, .. } = &func.node else {
|
||||
return false;
|
||||
};
|
||||
FUNC_NAME_ALLOWLIST.contains(&attr.as_ref())
|
||||
if let ExprKind::Attribute { attr, .. } = &func.node {
|
||||
return FUNC_NAME_ALLOWLIST.contains(&attr.as_ref());
|
||||
}
|
||||
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
return FUNC_NAME_ALLOWLIST.contains(&id.as_ref());
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn is_boolean_arg(arg: &Expr) -> bool {
|
||||
@@ -79,8 +98,8 @@ pub fn check_boolean_positional_value_in_function_call(
|
||||
args: &[Expr],
|
||||
func: &Expr,
|
||||
) {
|
||||
for (index, arg) in args.iter().enumerate() {
|
||||
if index < 2 && allow_boolean_trap(func) {
|
||||
for arg in args {
|
||||
if allow_boolean_trap(func) {
|
||||
continue;
|
||||
}
|
||||
add_if_boolean(
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashSet;
|
||||
use rustpython_ast::{
|
||||
Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Location, Stmt,
|
||||
};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Location};
|
||||
|
||||
use crate::ast::helpers;
|
||||
use crate::ast::types::Range;
|
||||
@@ -26,17 +24,17 @@ fn duplicate_handler_exceptions<'a>(
|
||||
checker: &mut Checker,
|
||||
expr: &'a Expr,
|
||||
elts: &'a [Expr],
|
||||
) -> FxHashSet<Vec<&'a str>> {
|
||||
let mut seen: FxHashSet<Vec<&str>> = FxHashSet::default();
|
||||
) -> FxHashMap<Vec<&'a str>, &'a Expr> {
|
||||
let mut seen: FxHashMap<Vec<&str>, &Expr> = FxHashMap::default();
|
||||
let mut duplicates: FxHashSet<Vec<&str>> = FxHashSet::default();
|
||||
let mut unique_elts: Vec<&Expr> = Vec::default();
|
||||
for type_ in elts {
|
||||
let call_path = helpers::collect_call_paths(type_);
|
||||
if !call_path.is_empty() {
|
||||
if seen.contains(&call_path) {
|
||||
if seen.contains_key(&call_path) {
|
||||
duplicates.insert(call_path);
|
||||
} else {
|
||||
seen.insert(call_path);
|
||||
seen.entry(call_path).or_insert(type_);
|
||||
unique_elts.push(type_);
|
||||
}
|
||||
}
|
||||
@@ -56,9 +54,12 @@ fn duplicate_handler_exceptions<'a>(
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
// TODO(charlie): If we have a single element, remove the tuple.
|
||||
let mut generator = SourceGenerator::new();
|
||||
generator.unparse_expr(&type_pattern(unique_elts), 0);
|
||||
if unique_elts.len() == 1 {
|
||||
generator.unparse_expr(unique_elts[0], 0);
|
||||
} else {
|
||||
generator.unparse_expr(&type_pattern(unique_elts), 0);
|
||||
}
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
@@ -74,9 +75,9 @@ fn duplicate_handler_exceptions<'a>(
|
||||
seen
|
||||
}
|
||||
|
||||
pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Excepthandler]) {
|
||||
pub fn duplicate_exceptions(checker: &mut Checker, handlers: &[Excepthandler]) {
|
||||
let mut seen: FxHashSet<Vec<&str>> = FxHashSet::default();
|
||||
let mut duplicates: FxHashSet<Vec<&str>> = FxHashSet::default();
|
||||
let mut duplicates: FxHashMap<Vec<&str>, Vec<&Expr>> = FxHashMap::default();
|
||||
for handler in handlers {
|
||||
let ExcepthandlerKind::ExceptHandler { type_: Some(type_), .. } = &handler.node else {
|
||||
continue;
|
||||
@@ -86,16 +87,16 @@ pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Exce
|
||||
let call_path = helpers::collect_call_paths(type_);
|
||||
if !call_path.is_empty() {
|
||||
if seen.contains(&call_path) {
|
||||
duplicates.insert(call_path);
|
||||
duplicates.entry(call_path).or_default().push(type_);
|
||||
} else {
|
||||
seen.insert(call_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Tuple { elts, .. } => {
|
||||
for name in duplicate_handler_exceptions(checker, type_, elts) {
|
||||
for (name, expr) in duplicate_handler_exceptions(checker, type_, elts) {
|
||||
if seen.contains(&name) {
|
||||
duplicates.insert(name);
|
||||
duplicates.entry(name).or_default().push(expr);
|
||||
} else {
|
||||
seen.insert(name);
|
||||
}
|
||||
@@ -106,11 +107,13 @@ pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Exce
|
||||
}
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::B025) {
|
||||
for duplicate in duplicates.into_iter().sorted() {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::DuplicateTryBlockException(duplicate.join(".")),
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
for (name, exprs) in duplicates {
|
||||
for expr in exprs {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::DuplicateTryBlockException(name.join(".")),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,8 +219,8 @@ where
|
||||
// loop, flag it.
|
||||
for (name, expr, range) in suspicious_variables {
|
||||
if reassigned_in_loop.contains(name) {
|
||||
if !checker.seen_b023.contains(&expr) {
|
||||
checker.seen_b023.push(expr);
|
||||
if !checker.flake8_bugbear_seen.contains(&expr) {
|
||||
checker.flake8_bugbear_seen.push(expr);
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::FunctionUsesLoopVariable(name.to_string()),
|
||||
range,
|
||||
|
||||
@@ -1,52 +1,10 @@
|
||||
use anyhow::{bail, Result};
|
||||
use log::error;
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, ExprKind, Located};
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::Tok;
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, ExprKind};
|
||||
|
||||
use crate::ast::helpers;
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::SourceCodeLocator;
|
||||
|
||||
/// Given a statement like `except (ValueError,)`, find the range of the
|
||||
/// parenthesized expression.
|
||||
fn match_tuple_range<T>(located: &Located<T>, locator: &SourceCodeLocator) -> Result<Range> {
|
||||
// Extract contents from the source code.
|
||||
let range = Range::from_located(located);
|
||||
let contents = locator.slice_source_code_range(&range);
|
||||
|
||||
// Find the left (opening) and right (closing) parentheses.
|
||||
let mut location = None;
|
||||
let mut end_location = None;
|
||||
let mut count: usize = 0;
|
||||
for (start, tok, end) in lexer::make_tokenizer(&contents).flatten() {
|
||||
if matches!(tok, Tok::Lpar) {
|
||||
if count == 0 {
|
||||
location = Some(helpers::to_absolute(start, range.location));
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Rpar) {
|
||||
count -= 1;
|
||||
if count == 0 {
|
||||
end_location = Some(helpers::to_absolute(end, range.location));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let (Some(location), Some(end_location)) = (location, end_location) else {
|
||||
bail!("Unable to find left and right parentheses");
|
||||
};
|
||||
Ok(Range {
|
||||
location,
|
||||
end_location,
|
||||
})
|
||||
}
|
||||
|
||||
/// B013
|
||||
pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[Excepthandler]) {
|
||||
@@ -68,16 +26,11 @@ pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[E
|
||||
let mut generator = SourceGenerator::new();
|
||||
generator.unparse_expr(elt, 0);
|
||||
if let Ok(content) = generator.generate() {
|
||||
match match_tuple_range(handler, checker.locator) {
|
||||
Ok(range) => {
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
range.location,
|
||||
range.end_location,
|
||||
));
|
||||
}
|
||||
Err(e) => error!("Failed to locate parentheses: {e}"),
|
||||
}
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
type_.location,
|
||||
type_.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
|
||||
@@ -59,7 +59,7 @@ pub fn setattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
|
||||
// We can only replace a `setattr` call (which is an `Expr`) with an assignment
|
||||
// (which is a `Stmt`) if the `Expr` is already being used as a `Stmt`
|
||||
// (i.e., it's directly within an `StmtKind::Expr`).
|
||||
if let StmtKind::Expr { value: child } = &checker.current_parent().0.node {
|
||||
if let StmtKind::Expr { value: child } = &checker.current_stmt().0.node {
|
||||
if expr == child.as_ref() {
|
||||
let mut check = Check::new(CheckKind::SetAttrWithConstant, Range::from_located(expr));
|
||||
if checker.patch(check.kind.code()) {
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
//! Settings for the `flake8-bugbear` plugin.
|
||||
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
|
||||
)]
|
||||
#[serde(
|
||||
deny_unknown_fields,
|
||||
rename_all = "kebab-case",
|
||||
rename = "Flake8BugbearOptions"
|
||||
)]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
doc = r#"
|
||||
Additional callable functions to consider "immutable" when evaluating, e.g.,
|
||||
`no-mutable-default-argument` checks (`B006`).
|
||||
"#,
|
||||
default = r#"[]"#,
|
||||
value_type = "Vec<String>",
|
||||
example = r#"
|
||||
@@ -18,6 +21,8 @@ pub struct Options {
|
||||
extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]
|
||||
"#
|
||||
)]
|
||||
/// Additional callable functions to consider "immutable" when evaluating,
|
||||
/// e.g., `no-mutable-default-argument` checks (`B006`).
|
||||
pub extend_immutable_calls: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ expression: checks
|
||||
RedundantTupleInExceptionHandler: ValueError
|
||||
location:
|
||||
row: 3
|
||||
column: 8
|
||||
column: 7
|
||||
end_location:
|
||||
row: 3
|
||||
column: 19
|
||||
column: 20
|
||||
fix:
|
||||
content: ValueError
|
||||
location:
|
||||
|
||||
@@ -7,50 +7,50 @@ expression: checks
|
||||
- OSError
|
||||
location:
|
||||
row: 17
|
||||
column: 8
|
||||
column: 7
|
||||
end_location:
|
||||
row: 17
|
||||
column: 24
|
||||
column: 25
|
||||
fix:
|
||||
content: "OSError,"
|
||||
content: OSError
|
||||
location:
|
||||
row: 17
|
||||
column: 8
|
||||
column: 7
|
||||
end_location:
|
||||
row: 17
|
||||
column: 24
|
||||
column: 25
|
||||
- kind:
|
||||
DuplicateHandlerException:
|
||||
- MyError
|
||||
location:
|
||||
row: 28
|
||||
column: 8
|
||||
column: 7
|
||||
end_location:
|
||||
row: 28
|
||||
column: 24
|
||||
column: 25
|
||||
fix:
|
||||
content: "MyError,"
|
||||
content: MyError
|
||||
location:
|
||||
row: 28
|
||||
column: 8
|
||||
column: 7
|
||||
end_location:
|
||||
row: 28
|
||||
column: 24
|
||||
column: 25
|
||||
- kind:
|
||||
DuplicateHandlerException:
|
||||
- re.error
|
||||
location:
|
||||
row: 49
|
||||
column: 8
|
||||
column: 7
|
||||
end_location:
|
||||
row: 49
|
||||
column: 26
|
||||
column: 27
|
||||
fix:
|
||||
content: "re.error,"
|
||||
content: re.error
|
||||
location:
|
||||
row: 49
|
||||
column: 8
|
||||
column: 7
|
||||
end_location:
|
||||
row: 49
|
||||
column: 26
|
||||
column: 27
|
||||
|
||||
|
||||
@@ -5,37 +5,37 @@ expression: checks
|
||||
- kind:
|
||||
DuplicateTryBlockException: ValueError
|
||||
location:
|
||||
row: 15
|
||||
column: 0
|
||||
row: 19
|
||||
column: 7
|
||||
end_location:
|
||||
row: 20
|
||||
column: 9
|
||||
row: 19
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
DuplicateTryBlockException: pickle.PickleError
|
||||
location:
|
||||
row: 22
|
||||
column: 0
|
||||
row: 28
|
||||
column: 7
|
||||
end_location:
|
||||
row: 29
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
DuplicateTryBlockException: TypeError
|
||||
location:
|
||||
row: 31
|
||||
column: 0
|
||||
end_location:
|
||||
row: 38
|
||||
column: 9
|
||||
row: 28
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind:
|
||||
DuplicateTryBlockException: ValueError
|
||||
location:
|
||||
row: 31
|
||||
column: 0
|
||||
row: 35
|
||||
column: 7
|
||||
end_location:
|
||||
row: 38
|
||||
column: 9
|
||||
row: 35
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
DuplicateTryBlockException: TypeError
|
||||
location:
|
||||
row: 37
|
||||
column: 17
|
||||
end_location:
|
||||
row: 37
|
||||
column: 26
|
||||
fix: ~
|
||||
|
||||
|
||||
36
src/flake8_datetimez/mod.rs
Normal file
36
src/flake8_datetimez/mod.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
pub mod plugins;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::AsRef;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::settings;
|
||||
|
||||
#[test_case(CheckCode::DTZ001, Path::new("DTZ001.py"); "DTZ001")]
|
||||
#[test_case(CheckCode::DTZ002, Path::new("DTZ002.py"); "DTZ002")]
|
||||
#[test_case(CheckCode::DTZ003, Path::new("DTZ003.py"); "DTZ003")]
|
||||
#[test_case(CheckCode::DTZ004, Path::new("DTZ004.py"); "DTZ004")]
|
||||
#[test_case(CheckCode::DTZ005, Path::new("DTZ005.py"); "DTZ005")]
|
||||
#[test_case(CheckCode::DTZ006, Path::new("DTZ006.py"); "DTZ006")]
|
||||
#[test_case(CheckCode::DTZ007, Path::new("DTZ007.py"); "DTZ007")]
|
||||
#[test_case(CheckCode::DTZ011, Path::new("DTZ011.py"); "DTZ011")]
|
||||
#[test_case(CheckCode::DTZ012, Path::new("DTZ012.py"); "DTZ012")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_datetimez")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
234
src/flake8_datetimez/plugins.rs
Normal file
234
src/flake8_datetimez/plugins.rs
Normal file
@@ -0,0 +1,234 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprKind, Keyword};
|
||||
|
||||
use crate::ast::helpers::{
|
||||
collect_call_paths, dealias_call_path, has_non_none_keyword, is_const_none, match_call_path,
|
||||
};
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
pub fn call_datetime_without_tzinfo(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
location: Range,
|
||||
) {
|
||||
let call_path = dealias_call_path(collect_call_paths(func), &checker.import_aliases);
|
||||
if !match_call_path(&call_path, "datetime", "datetime", &checker.from_imports) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No positional arg: keyword is missing or constant None.
|
||||
if args.len() < 8 && !has_non_none_keyword(keywords, "tzinfo") {
|
||||
checker.add_check(Check::new(CheckKind::CallDatetimeWithoutTzinfo, location));
|
||||
return;
|
||||
}
|
||||
|
||||
// Positional arg: is constant None.
|
||||
if args.len() >= 8 && is_const_none(&args[7]) {
|
||||
checker.add_check(Check::new(CheckKind::CallDatetimeWithoutTzinfo, location));
|
||||
}
|
||||
}
|
||||
|
||||
/// DTZ002
|
||||
pub fn call_datetime_today(checker: &mut Checker, func: &Expr, location: Range) {
|
||||
let call_path = dealias_call_path(collect_call_paths(func), &checker.import_aliases);
|
||||
if match_call_path(
|
||||
&call_path,
|
||||
"datetime.datetime",
|
||||
"today",
|
||||
&checker.from_imports,
|
||||
) {
|
||||
checker.add_check(Check::new(CheckKind::CallDatetimeToday, location));
|
||||
}
|
||||
}
|
||||
|
||||
/// DTZ003
|
||||
pub fn call_datetime_utcnow(checker: &mut Checker, func: &Expr, location: Range) {
|
||||
let call_path = dealias_call_path(collect_call_paths(func), &checker.import_aliases);
|
||||
if match_call_path(
|
||||
&call_path,
|
||||
"datetime.datetime",
|
||||
"utcnow",
|
||||
&checker.from_imports,
|
||||
) {
|
||||
checker.add_check(Check::new(CheckKind::CallDatetimeUtcnow, location));
|
||||
}
|
||||
}
|
||||
|
||||
/// DTZ004
|
||||
pub fn call_datetime_utcfromtimestamp(checker: &mut Checker, func: &Expr, location: Range) {
|
||||
let call_path = dealias_call_path(collect_call_paths(func), &checker.import_aliases);
|
||||
if match_call_path(
|
||||
&call_path,
|
||||
"datetime.datetime",
|
||||
"utcfromtimestamp",
|
||||
&checker.from_imports,
|
||||
) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::CallDatetimeUtcfromtimestamp,
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// DTZ005
|
||||
pub fn call_datetime_now_without_tzinfo(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
location: Range,
|
||||
) {
|
||||
let call_path = dealias_call_path(collect_call_paths(func), &checker.import_aliases);
|
||||
if !match_call_path(
|
||||
&call_path,
|
||||
"datetime.datetime",
|
||||
"now",
|
||||
&checker.from_imports,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// no args / no args unqualified
|
||||
if args.is_empty() && keywords.is_empty() {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::CallDatetimeNowWithoutTzinfo,
|
||||
location,
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// none args
|
||||
if !args.is_empty() && is_const_none(&args[0]) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::CallDatetimeNowWithoutTzinfo,
|
||||
location,
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// wrong keywords / none keyword
|
||||
if !keywords.is_empty() && !has_non_none_keyword(keywords, "tz") {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::CallDatetimeNowWithoutTzinfo,
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// DTZ006
|
||||
pub fn call_datetime_fromtimestamp(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
location: Range,
|
||||
) {
|
||||
let call_path = dealias_call_path(collect_call_paths(func), &checker.import_aliases);
|
||||
if !match_call_path(
|
||||
&call_path,
|
||||
"datetime.datetime",
|
||||
"fromtimestamp",
|
||||
&checker.from_imports,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// no args / no args unqualified
|
||||
if args.len() < 2 && keywords.is_empty() {
|
||||
checker.add_check(Check::new(CheckKind::CallDatetimeFromtimestamp, location));
|
||||
return;
|
||||
}
|
||||
|
||||
// none args
|
||||
if args.len() > 1 && is_const_none(&args[1]) {
|
||||
checker.add_check(Check::new(CheckKind::CallDatetimeFromtimestamp, location));
|
||||
return;
|
||||
}
|
||||
|
||||
// wrong keywords / none keyword
|
||||
if !keywords.is_empty() && !has_non_none_keyword(keywords, "tz") {
|
||||
checker.add_check(Check::new(CheckKind::CallDatetimeFromtimestamp, location));
|
||||
}
|
||||
}
|
||||
|
||||
/// DTZ007
|
||||
pub fn call_datetime_strptime_without_zone(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
location: Range,
|
||||
) {
|
||||
let call_path = dealias_call_path(collect_call_paths(func), &checker.import_aliases);
|
||||
if !match_call_path(
|
||||
&call_path,
|
||||
"datetime.datetime",
|
||||
"strptime",
|
||||
&checker.from_imports,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Does the `strptime` call contain a format string with a timezone specifier?
|
||||
if let Some(ExprKind::Constant {
|
||||
value: Constant::Str(format),
|
||||
kind: None,
|
||||
}) = args.get(1).as_ref().map(|arg| &arg.node)
|
||||
{
|
||||
if format.contains("%z") {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let (Some(grandparent), Some(parent)) = (checker.current_expr_grandparent(), checker.current_expr_parent()) else {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::CallDatetimeStrptimeWithoutZone,
|
||||
location,
|
||||
));
|
||||
return;
|
||||
};
|
||||
|
||||
if let ExprKind::Call { keywords, .. } = &grandparent.0.node {
|
||||
if let ExprKind::Attribute { attr, .. } = &parent.0.node {
|
||||
// Ex) `datetime.strptime(...).astimezone()`
|
||||
if attr == "astimezone" {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ex) `datetime.strptime(...).replace(tzinfo=UTC)`
|
||||
if attr == "replace" {
|
||||
if has_non_none_keyword(keywords, "tzinfo") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::CallDatetimeStrptimeWithoutZone,
|
||||
location,
|
||||
));
|
||||
}
|
||||
|
||||
/// DTZ011
|
||||
pub fn call_date_today(checker: &mut Checker, func: &Expr, location: Range) {
|
||||
let call_path = dealias_call_path(collect_call_paths(func), &checker.import_aliases);
|
||||
if match_call_path(&call_path, "datetime.date", "today", &checker.from_imports) {
|
||||
checker.add_check(Check::new(CheckKind::CallDateToday, location));
|
||||
}
|
||||
}
|
||||
|
||||
/// DTZ012
|
||||
pub fn call_date_fromtimestamp(checker: &mut Checker, func: &Expr, location: Range) {
|
||||
let call_path = dealias_call_path(collect_call_paths(func), &checker.import_aliases);
|
||||
if match_call_path(
|
||||
&call_path,
|
||||
"datetime.date",
|
||||
"fromtimestamp",
|
||||
&checker.from_imports,
|
||||
) {
|
||||
checker.add_check(Check::new(CheckKind::CallDateFromtimestamp, location));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
source: src/flake8_datetimez/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: CallDatetimeWithoutTzinfo
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 38
|
||||
fix: ~
|
||||
- kind: CallDatetimeWithoutTzinfo
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 47
|
||||
fix: ~
|
||||
- kind: CallDatetimeWithoutTzinfo
|
||||
location:
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
column: 37
|
||||
fix: ~
|
||||
- kind: CallDatetimeWithoutTzinfo
|
||||
location:
|
||||
row: 16
|
||||
column: 0
|
||||
end_location:
|
||||
row: 16
|
||||
column: 42
|
||||
fix: ~
|
||||
- kind: CallDatetimeWithoutTzinfo
|
||||
location:
|
||||
row: 21
|
||||
column: 0
|
||||
end_location:
|
||||
row: 21
|
||||
column: 29
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
source: src/flake8_datetimez/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: CallDatetimeToday
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: CallDatetimeToday
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 16
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
source: src/flake8_datetimez/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: CallDatetimeUtcnow
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 26
|
||||
fix: ~
|
||||
- kind: CallDatetimeUtcnow
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 17
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
source: src/flake8_datetimez/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: CallDatetimeUtcfromtimestamp
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 40
|
||||
fix: ~
|
||||
- kind: CallDatetimeUtcfromtimestamp
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 31
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
source: src/flake8_datetimez/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: CallDatetimeNowWithoutTzinfo
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 23
|
||||
fix: ~
|
||||
- kind: CallDatetimeNowWithoutTzinfo
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 48
|
||||
fix: ~
|
||||
- kind: CallDatetimeNowWithoutTzinfo
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 27
|
||||
fix: ~
|
||||
- kind: CallDatetimeNowWithoutTzinfo
|
||||
location:
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
column: 30
|
||||
fix: ~
|
||||
- kind: CallDatetimeNowWithoutTzinfo
|
||||
location:
|
||||
row: 18
|
||||
column: 0
|
||||
end_location:
|
||||
row: 18
|
||||
column: 14
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
source: src/flake8_datetimez/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: CallDatetimeFromtimestamp
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 37
|
||||
fix: ~
|
||||
- kind: CallDatetimeFromtimestamp
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 64
|
||||
fix: ~
|
||||
- kind: CallDatetimeFromtimestamp
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 43
|
||||
fix: ~
|
||||
- kind: CallDatetimeFromtimestamp
|
||||
location:
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
column: 46
|
||||
fix: ~
|
||||
- kind: CallDatetimeFromtimestamp
|
||||
location:
|
||||
row: 18
|
||||
column: 0
|
||||
end_location:
|
||||
row: 18
|
||||
column: 28
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
source: src/flake8_datetimez/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: CallDatetimeStrptimeWithoutZone
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 53
|
||||
fix: ~
|
||||
- kind: CallDatetimeStrptimeWithoutZone
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 52
|
||||
fix: ~
|
||||
- kind: CallDatetimeStrptimeWithoutZone
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 52
|
||||
fix: ~
|
||||
- kind: CallDatetimeStrptimeWithoutZone
|
||||
location:
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
column: 52
|
||||
fix: ~
|
||||
- kind: CallDatetimeStrptimeWithoutZone
|
||||
location:
|
||||
row: 35
|
||||
column: 0
|
||||
end_location:
|
||||
row: 35
|
||||
column: 43
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
source: src/flake8_datetimez/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: CallDateToday
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind: CallDateToday
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 12
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
source: src/flake8_datetimez/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: CallDateFromtimestamp
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 33
|
||||
fix: ~
|
||||
- kind: CallDateFromtimestamp
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 24
|
||||
fix: ~
|
||||
|
||||
@@ -1,2 +1,29 @@
|
||||
pub mod checks;
|
||||
pub mod types;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::AsRef;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::settings;
|
||||
|
||||
#[test_case(CheckCode::T100, Path::new("T100.py"); "T100")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_debugger")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: src/flake8_print/mod.rs
|
||||
source: src/flake8_debugger/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
@@ -16,80 +16,80 @@ expression: checks
|
||||
Debugger:
|
||||
Import: pdb
|
||||
location:
|
||||
row: 4
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
row: 3
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind:
|
||||
Debugger:
|
||||
Import: builtins.breakpoint
|
||||
location:
|
||||
row: 6
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
row: 5
|
||||
column: 31
|
||||
fix: ~
|
||||
- kind:
|
||||
Debugger:
|
||||
Import: pdb.set_trace
|
||||
location:
|
||||
row: 7
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
row: 6
|
||||
column: 31
|
||||
fix: ~
|
||||
- kind:
|
||||
Debugger:
|
||||
Import: celery.contrib.rdb.set_trace
|
||||
location:
|
||||
row: 8
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
row: 7
|
||||
column: 40
|
||||
fix: ~
|
||||
- kind:
|
||||
Debugger:
|
||||
Import: celery.contrib.rdb
|
||||
location:
|
||||
row: 10
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
row: 9
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind:
|
||||
Debugger:
|
||||
Call: breakpoint
|
||||
location:
|
||||
row: 13
|
||||
row: 11
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
row: 11
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
Debugger:
|
||||
Call: set_trace
|
||||
location:
|
||||
row: 14
|
||||
row: 12
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
row: 12
|
||||
column: 4
|
||||
fix: ~
|
||||
- kind:
|
||||
Debugger:
|
||||
Call: set_trace
|
||||
location:
|
||||
row: 15
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 15
|
||||
row: 13
|
||||
column: 11
|
||||
fix: ~
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
pub fn check_string_in_exception(exc: &Expr, max_string_length: usize) -> Vec<Check> {
|
||||
let mut checks = vec![];
|
||||
|
||||
if let ExprKind::Call { args, .. } = &exc.node {
|
||||
if let Some(first) = args.first() {
|
||||
match &first.node {
|
||||
// Check for string literals
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} => {
|
||||
if string.len() > max_string_length {
|
||||
checks.push(Check::new(
|
||||
CheckKind::RawStringInException,
|
||||
Range::from_located(first),
|
||||
));
|
||||
}
|
||||
}
|
||||
// Check for f-strings
|
||||
ExprKind::JoinedStr { .. } => checks.push(Check::new(
|
||||
CheckKind::FStringInException,
|
||||
Range::from_located(first),
|
||||
)),
|
||||
// Check for .format() calls
|
||||
ExprKind::Call { func, .. } => {
|
||||
if let ExprKind::Attribute { value, attr, .. } = &func.node {
|
||||
if attr == "format" && matches!(value.node, ExprKind::Constant { .. }) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::DotFormatInException,
|
||||
Range::from_located(first),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checks
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
pub mod checks;
|
||||
pub mod plugins;
|
||||
pub mod settings;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
52
src/flake8_errmsg/plugins.rs
Normal file
52
src/flake8_errmsg/plugins.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
|
||||
/// EM101, EM102, EM103
|
||||
pub fn string_in_exception(checker: &mut Checker, exc: &Expr) {
|
||||
if let ExprKind::Call { args, .. } = &exc.node {
|
||||
if let Some(first) = args.first() {
|
||||
match &first.node {
|
||||
// Check for string literals
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} => {
|
||||
if checker.settings.enabled.contains(&CheckCode::EM101) {
|
||||
if string.len() > checker.settings.flake8_errmsg.max_string_length {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::RawStringInException,
|
||||
Range::from_located(first),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check for f-strings
|
||||
ExprKind::JoinedStr { .. } => {
|
||||
if checker.settings.enabled.contains(&CheckCode::EM102) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::FStringInException,
|
||||
Range::from_located(first),
|
||||
));
|
||||
}
|
||||
}
|
||||
// Check for .format() calls
|
||||
ExprKind::Call { func, .. } => {
|
||||
if checker.settings.enabled.contains(&CheckCode::EM103) {
|
||||
if let ExprKind::Attribute { value, attr, .. } = &func.node {
|
||||
if attr == "format" && matches!(value.node, ExprKind::Constant { .. }) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::DotFormatInException,
|
||||
Range::from_located(first),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,24 @@
|
||||
//! Settings for the `flake8-errmsg` plugin.
|
||||
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
|
||||
)]
|
||||
#[serde(
|
||||
deny_unknown_fields,
|
||||
rename_all = "kebab-case",
|
||||
rename = "Flake8ErrMsgOptions"
|
||||
)]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
doc = r#"
|
||||
Maximum string length for string literals in exception messages.
|
||||
"#,
|
||||
default = "0",
|
||||
value_type = "usize",
|
||||
example = "max-string-length = 20"
|
||||
)]
|
||||
/// Maximum string length for string literals in exception messages.
|
||||
pub max_string_length: Option<usize>,
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::hash::{Hash, Hasher};
|
||||
use itertools::Itertools;
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use rustc_hash::FxHashMap;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const CONVENTIONAL_ALIASES: &[(&str, &str)] = &[
|
||||
@@ -15,12 +16,16 @@ const CONVENTIONAL_ALIASES: &[(&str, &str)] = &[
|
||||
("seaborn", "sns"),
|
||||
];
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
|
||||
)]
|
||||
#[serde(
|
||||
deny_unknown_fields,
|
||||
rename_all = "kebab-case",
|
||||
rename = "Flake8ImportConventionsOptions"
|
||||
)]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
doc = "The conventional aliases for imports. These aliases can be extended by the \
|
||||
`extend_aliases` option.",
|
||||
default = r#"{"altair": "alt", "matplotlib.pyplot": "plt", "numpy": "np", "pandas": "pd", "seaborn": "sns"}"#,
|
||||
value_type = "FxHashMap<String, String>",
|
||||
example = r#"
|
||||
@@ -32,10 +37,10 @@ pub struct Options {
|
||||
seaborn = "sns"
|
||||
"#
|
||||
)]
|
||||
/// The conventional aliases for imports. These aliases can be extended by
|
||||
/// the `extend_aliases` option.
|
||||
pub aliases: Option<FxHashMap<String, String>>,
|
||||
#[option(
|
||||
doc = "A mapping of modules to their conventional import aliases. These aliases will be \
|
||||
added to the `aliases` mapping.",
|
||||
default = r#"{}"#,
|
||||
value_type = "FxHashMap<String, String>",
|
||||
example = r#"
|
||||
@@ -43,6 +48,8 @@ pub struct Options {
|
||||
"dask.dataframe" = "dd"
|
||||
"#
|
||||
)]
|
||||
/// A mapping of modules to their conventional import aliases. These aliases
|
||||
/// will be added to the `aliases` mapping.
|
||||
pub extend_aliases: Option<FxHashMap<String, String>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// Check whether a function call is a `print` or `pprint` invocation
|
||||
pub fn print_call(
|
||||
func: &Expr,
|
||||
check_print: bool,
|
||||
check_pprint: bool,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if check_print && id == "print" {
|
||||
return Some(Check::new(CheckKind::PrintFound, location));
|
||||
} else if check_pprint && id == "pprint" {
|
||||
return Some(Check::new(CheckKind::PPrintFound, location));
|
||||
}
|
||||
}
|
||||
|
||||
if let ExprKind::Attribute { value, attr, .. } = &func.node {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
if check_pprint && id == "pprint" && attr == "pprint" {
|
||||
return Some(Check::new(CheckKind::PPrintFound, location));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
mod checks;
|
||||
pub mod plugins;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -13,7 +12,6 @@ mod tests {
|
||||
use crate::linter::test_path;
|
||||
use crate::settings;
|
||||
|
||||
#[test_case(CheckCode::T100, Path::new("T100.py"); "T100")]
|
||||
#[test_case(CheckCode::T201, Path::new("T201.py"); "T201")]
|
||||
#[test_case(CheckCode::T203, Path::new("T203.py"); "T203")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
|
||||
@@ -1,26 +1,47 @@
|
||||
use log::error;
|
||||
use rustpython_ast::{Expr, Stmt, StmtKind};
|
||||
use rustpython_ast::{Expr, Keyword, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::{collect_call_paths, dealias_call_path, is_const_none, match_call_path};
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::helpers;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::CheckCode;
|
||||
use crate::flake8_print::checks;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// T201, T203
|
||||
pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
|
||||
let Some(mut check) = checks::print_call(
|
||||
func,
|
||||
checker.settings.enabled.contains(&CheckCode::T201),
|
||||
checker.settings.enabled.contains(&CheckCode::T203),
|
||||
Range::from_located(expr),
|
||||
) else {
|
||||
return;
|
||||
pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr, keywords: &[Keyword]) {
|
||||
let mut check = {
|
||||
let call_path = dealias_call_path(collect_call_paths(func), &checker.import_aliases);
|
||||
if match_call_path(&call_path, "", "print", &checker.from_imports) {
|
||||
// If the print call has a `file=` argument (that isn't `None`, `"sys.stdout"`,
|
||||
// or `"sys.stderr"`), don't trigger T201.
|
||||
if let Some(keyword) = keywords
|
||||
.iter()
|
||||
.find(|keyword| keyword.node.arg.as_ref().map_or(false, |arg| arg == "file"))
|
||||
{
|
||||
if !is_const_none(&keyword.node.value) {
|
||||
let call_path = collect_call_paths(&keyword.node.value);
|
||||
if !(match_call_path(&call_path, "sys", "stdout", &checker.from_imports)
|
||||
|| match_call_path(&call_path, "sys", "stderr", &checker.from_imports))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Check::new(CheckKind::PrintFound, Range::from_located(expr))
|
||||
} else if match_call_path(&call_path, "pprint", "pprint", &checker.from_imports) {
|
||||
Check::new(CheckKind::PPrintFound, Range::from_located(expr))
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if !checker.settings.enabled.contains(check.kind.code()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if checker.patch(check.kind.code()) {
|
||||
let defined_by = checker.current_parent();
|
||||
let defined_in = checker.current_grandparent();
|
||||
let defined_by = checker.current_stmt();
|
||||
let defined_in = checker.current_stmt_parent();
|
||||
if matches!(defined_by.0.node, StmtKind::Expr { .. }) {
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(|node| node.0).collect();
|
||||
match helpers::delete_stmt(
|
||||
|
||||
@@ -4,17 +4,62 @@ expression: checks
|
||||
---
|
||||
- kind: PrintFound
|
||||
location:
|
||||
row: 1
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 1
|
||||
row: 4
|
||||
column: 22
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 1
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 2
|
||||
row: 5
|
||||
column: 0
|
||||
- kind: PrintFound
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 33
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 0
|
||||
- kind: PrintFound
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 39
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 0
|
||||
- kind: PrintFound
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 39
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
column: 0
|
||||
|
||||
|
||||
@@ -19,17 +19,17 @@ expression: checks
|
||||
column: 0
|
||||
- kind: PPrintFound
|
||||
location:
|
||||
row: 8
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
row: 7
|
||||
column: 30
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 8
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
row: 8
|
||||
column: 0
|
||||
|
||||
|
||||
@@ -1,57 +1,56 @@
|
||||
//! Settings for the `flake8-quotes` plugin.
|
||||
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub enum Quote {
|
||||
Single,
|
||||
Double,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
|
||||
)]
|
||||
#[serde(
|
||||
deny_unknown_fields,
|
||||
rename_all = "kebab-case",
|
||||
rename = "Flake8QuotesOptions"
|
||||
)]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
doc = r#"
|
||||
Quote style to prefer for inline strings (either "single" (`'`) or "double" (`"`)).
|
||||
"#,
|
||||
default = r#""double""#,
|
||||
value_type = "Quote",
|
||||
example = r#"
|
||||
inline-quotes = "single"
|
||||
"#
|
||||
)]
|
||||
/// Quote style to prefer for inline strings (either "single" (`'`) or
|
||||
/// "double" (`"`)).
|
||||
pub inline_quotes: Option<Quote>,
|
||||
#[option(
|
||||
doc = r#"
|
||||
Quote style to prefer for multiline strings (either "single" (`'`) or "double" (`"`)).
|
||||
"#,
|
||||
default = r#""double""#,
|
||||
value_type = "Quote",
|
||||
example = r#"
|
||||
multiline-quotes = "single"
|
||||
"#
|
||||
)]
|
||||
/// Quote style to prefer for multiline strings (either "single" (`'`) or
|
||||
/// "double" (`"`)).
|
||||
pub multiline_quotes: Option<Quote>,
|
||||
#[option(
|
||||
doc = r#"
|
||||
Quote style to prefer for docstrings (either "single" (`'`) or "double" (`"`)).
|
||||
"#,
|
||||
default = r#""double""#,
|
||||
value_type = "Quote",
|
||||
example = r#"
|
||||
docstring-quotes = "single"
|
||||
"#
|
||||
)]
|
||||
/// Quote style to prefer for docstrings (either "single" (`'`) or "double"
|
||||
/// (`"`)).
|
||||
pub docstring_quotes: Option<Quote>,
|
||||
#[option(
|
||||
doc = r#"
|
||||
Whether to avoid using single quotes if a string contains single quotes, or vice-versa
|
||||
with double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes).
|
||||
This minimizes the need to escape quotation marks within strings.
|
||||
"#,
|
||||
default = r#"true"#,
|
||||
value_type = "bool",
|
||||
example = r#"
|
||||
@@ -59,6 +58,9 @@ pub struct Options {
|
||||
avoid-escape = false
|
||||
"#
|
||||
)]
|
||||
/// Whether to avoid using single quotes if a string contains single quotes,
|
||||
/// or vice-versa with double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes).
|
||||
/// This minimizes the need to escape quotation marks within strings.
|
||||
pub avoid_escape: Option<bool>,
|
||||
}
|
||||
|
||||
|
||||
@@ -203,6 +203,10 @@ fn unnecessary_assign(checker: &mut Checker, stack: &Stack, expr: &Expr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if stack.non_locals.contains(id.as_str()) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::UnnecessaryAssign,
|
||||
Range::from_located(expr),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user