Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
057414ddd4 | ||
|
|
ca94e9aa26 | ||
|
|
797b5bd261 | ||
|
|
a64f62f439 | ||
|
|
058ee8e6bf | ||
|
|
39fc1f0c1b | ||
|
|
34842b4c4b | ||
|
|
dfa6fa8f83 | ||
|
|
6131c819ed | ||
|
|
79ba420faa | ||
|
|
d16ba890ae | ||
|
|
6b6851bf1f | ||
|
|
056718ce75 | ||
|
|
4521fdf021 | ||
|
|
8e479628f2 | ||
|
|
2a11c4b1f1 | ||
|
|
a8cde5a936 | ||
|
|
1822b57ed5 | ||
|
|
c679570041 | ||
|
|
edcb3a7217 | ||
|
|
6e43dc7270 | ||
|
|
570d0864f2 | ||
|
|
d22e96916c | ||
|
|
043d31dcdf | ||
|
|
1392e4cced | ||
|
|
59ee89a091 | ||
|
|
6a7c3728ee | ||
|
|
0a60eb0aca | ||
|
|
3e96803033 | ||
|
|
c59035139c | ||
|
|
7632d7eda7 | ||
|
|
b4dbe62da0 | ||
|
|
9106d5338b | ||
|
|
534d8d049c | ||
|
|
e692c4a2cc | ||
|
|
e0b39fa63e | ||
|
|
320a48977b | ||
|
|
0d05aaeb6e |
71
.github/workflows/ci.yaml
vendored
71
.github/workflows/ci.yaml
vendored
@@ -36,18 +36,12 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo build --all --release
|
||||
- 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
|
||||
- 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
|
||||
- run: ./target/release/ruff_dev generate-playground-options
|
||||
- run: git diff --quiet playground/src/ruff_options.rs || echo "::error file=playground/src/ruff_options.ts::This file is outdated. You may have to rerun 'cargo dev generate-playground-options'."
|
||||
- run: git diff --exit-code -- README.md src/checks_gen.rs playground/src/ruff_options.ts
|
||||
- run: ./target/release/ruff_dev generate-all
|
||||
- run: git diff --quiet README.md || echo "::error file=README.md::This file is outdated. Run 'cargo +nightly dev generate-all'."
|
||||
- run: git diff --quiet src/checks_gen.rs || echo "::error file=src/checks_gen.rs::This file is outdated. Run 'cargo +nightly dev generate-all'."
|
||||
- run: git diff --quiet ruff.schema.json || echo "::error file=ruff.schema.json::This file is outdated. Run 'cargo +nightly dev generate-all'."
|
||||
- run: git diff --quiet playground/src/ruff_options.ts || echo "::error file=playground/src/ruff_options.ts::This file is outdated. Run 'cargo +nightly dev generate-all'."
|
||||
- run: git diff --exit-code -- README.md src/checks_gen.rs ruff.schema.json playground/src/ruff_options.ts
|
||||
|
||||
cargo-fmt:
|
||||
name: "cargo fmt"
|
||||
@@ -127,30 +121,35 @@ jobs:
|
||||
- run: cargo test --all
|
||||
- run: cargo test --package ruff --test black_compatibility_test -- --ignored
|
||||
|
||||
wasm-pack-test:
|
||||
name: "wasm-pack test"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly-2022-11-01
|
||||
override: true
|
||||
- uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-cargo
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- uses: jetli/wasm-pack-action@v0.4.0
|
||||
- run: wasm-pack test --node
|
||||
# TODO(charlie): Re-enable the `wasm-pack` tests.
|
||||
# See: https://github.com/charliermarsh/ruff/issues/1425
|
||||
# wasm-pack-test:
|
||||
# name: "wasm-pack test"
|
||||
# runs-on: ubuntu-latest
|
||||
# env:
|
||||
# WASM_BINDGEN_TEST_TIMEOUT: 60
|
||||
# steps:
|
||||
# - uses: actions/checkout@v3
|
||||
# - uses: actions-rs/toolchain@v1
|
||||
# with:
|
||||
# profile: minimal
|
||||
# toolchain: nightly-2022-11-01
|
||||
# override: true
|
||||
# - uses: actions/cache@v3
|
||||
# env:
|
||||
# cache-name: cache-cargo
|
||||
# with:
|
||||
# path: |
|
||||
# ~/.cargo/registry
|
||||
# ~/.cargo/git
|
||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
# ${{ runner.os }}-build-
|
||||
# ${{ runner.os }}-
|
||||
# - uses: jetli/wasm-pack-action@v0.4.0
|
||||
# - uses: jetli/wasm-bindgen-action@v0.2.0
|
||||
# - run: wasm-pack test --node
|
||||
|
||||
maturin-build:
|
||||
name: "maturin build"
|
||||
|
||||
9
.github/workflows/playground.yaml
vendored
9
.github/workflows/playground.yaml
vendored
@@ -4,8 +4,6 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
@@ -15,6 +13,8 @@ env:
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
@@ -29,6 +29,7 @@ jobs:
|
||||
cache: "npm"
|
||||
cache-dependency-path: playground/package-lock.json
|
||||
- uses: jetli/wasm-pack-action@v0.4.0
|
||||
- uses: jetli/wasm-bindgen-action@v0.2.0
|
||||
- name: "Run wasm-pack"
|
||||
run: wasm-pack build --target web --out-dir playground/src/pkg
|
||||
- name: "Install Node dependencies"
|
||||
@@ -41,9 +42,7 @@ jobs:
|
||||
run: npm run build
|
||||
working-directory: playground
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
env:
|
||||
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
||||
if: ${{ env.CF_API_TOKEN }} != null
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@2.0.0
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.195
|
||||
rev: v0.0.199
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
@@ -59,9 +59,9 @@ pattern implemented therein.
|
||||
|
||||
To trigger the rule, you'll likely want to augment the logic in `src/check_ast.rs`, which defines
|
||||
the Python AST visitor, responsible for iterating over the abstract syntax tree and collecting
|
||||
lint-rule violations as it goes. If you need to inspect the AST, you can run `cargo dev print-ast`
|
||||
with a Python file. Grep for the `Check::new` invocations to understand how other, similar rules
|
||||
are implemented.
|
||||
lint-rule violations as it goes. If you need to inspect the AST, you can run
|
||||
`cargo +nightly dev print-ast` with a Python file. Grep for the `Check::new` invocations to
|
||||
understand how other, similar rules are implemented.
|
||||
|
||||
To add a test fixture, create a file under `resources/test/fixtures`, named to match the `CheckCode`
|
||||
you defined earlier (e.g., `E402.py`). This file should contain a variety of violations and
|
||||
@@ -79,9 +79,7 @@ Then, run `cargo test`. Your test will fail, but you'll be prompted to follow-up
|
||||
`cargo insta review`. Accept the generated snapshot, then commit the snapshot file alongside the
|
||||
rest of your changes.
|
||||
|
||||
Finally, to update the documentation, run `cargo dev generate-rules-table` from the repo root. To
|
||||
update the generated prefix map, run `cargo dev generate-check-code-prefix`. Both of these commands
|
||||
should be run whenever a new check is added to the codebase.
|
||||
Finally, regenerate the documentation and generated code with `cargo +nightly dev generate-all`.
|
||||
|
||||
### Example: Adding a new configuration option
|
||||
|
||||
@@ -105,8 +103,7 @@ 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`.
|
||||
|
||||
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`.
|
||||
Finally, regenerate the documentation and generated code with `cargo +nightly dev generate-all`.
|
||||
|
||||
## Release process
|
||||
|
||||
|
||||
17
Cargo.lock
generated
17
Cargo.lock
generated
@@ -750,7 +750,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.195-dev.0"
|
||||
version = "0.0.199-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.32",
|
||||
@@ -1878,7 +1878,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.195"
|
||||
version = "0.0.199"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1930,6 +1930,7 @@ dependencies = [
|
||||
"serde-wasm-bindgen",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"similar",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"test-case",
|
||||
@@ -1945,7 +1946,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.195"
|
||||
version = "0.0.199"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.32",
|
||||
@@ -1966,7 +1967,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.195"
|
||||
version = "0.0.199"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2009,7 +2010,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=247e815880766d556ef1ca6f0af69daf1a5fe59a#247e815880766d556ef1ca6f0af69daf1a5fe59a"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=68d26955b3e24198a150315e7959719b03709dee#68d26955b3e24198a150315e7959719b03709dee"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-common",
|
||||
@@ -2019,7 +2020,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-common"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=247e815880766d556ef1ca6f0af69daf1a5fe59a#247e815880766d556ef1ca6f0af69daf1a5fe59a"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=68d26955b3e24198a150315e7959719b03709dee#68d26955b3e24198a150315e7959719b03709dee"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -2042,7 +2043,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=247e815880766d556ef1ca6f0af69daf1a5fe59a#247e815880766d556ef1ca6f0af69daf1a5fe59a"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=68d26955b3e24198a150315e7959719b03709dee#68d26955b3e24198a150315e7959719b03709dee"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -2059,7 +2060,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=247e815880766d556ef1ca6f0af69daf1a5fe59a#247e815880766d556ef1ca6f0af69daf1a5fe59a"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=68d26955b3e24198a150315e7959719b03709dee#68d26955b3e24198a150315e7959719b03709dee"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
|
||||
17
Cargo.toml
17
Cargo.toml
@@ -6,9 +6,15 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.195"
|
||||
version = "0.0.199"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
documentation = "https://github.com/charliermarsh/ruff"
|
||||
homepage = "https://github.com/charliermarsh/ruff"
|
||||
repository = "https://github.com/charliermarsh/ruff"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
|
||||
[lib]
|
||||
name = "ruff"
|
||||
@@ -45,16 +51,17 @@ path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix
|
||||
quick-junit = { version = "0.3.2" }
|
||||
regex = { version = "1.6.0" }
|
||||
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
|
||||
ruff_macros = { version = "0.0.195", path = "ruff_macros" }
|
||||
ruff_macros = { version = "0.0.199", path = "ruff_macros" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "247e815880766d556ef1ca6f0af69daf1a5fe59a" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "247e815880766d556ef1ca6f0af69daf1a5fe59a" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "247e815880766d556ef1ca6f0af69daf1a5fe59a" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
|
||||
schemars = { version = "0.8.11" }
|
||||
semver = { version = "1.0.16" }
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_json = { version = "1.0.87" }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
similar = { version = "2.2.1" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.24.3" }
|
||||
textwrap = { version = "0.16.0" }
|
||||
|
||||
147
README.md
147
README.md
@@ -23,15 +23,20 @@ An extremely fast Python linter, written in Rust.
|
||||
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
- ⚖️ [Near-parity](#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
|
||||
- 🔌 Native re-implementations of popular Flake8 plugins, like [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
|
||||
- 🌎 Monorepo-friendly configuration via hierarchical and cascading settings
|
||||
|
||||
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
|
||||
functionality behind a single, common interface. Ruff can be used to replace Flake8 (plus a variety
|
||||
of plugins), [`isort`](https://pypi.org/project/isort/), [`pydocstyle`](https://pypi.org/project/pydocstyle/),
|
||||
[`yesqa`](https://github.com/asottile/yesqa), [`eradicate`](https://pypi.org/project/eradicate/),
|
||||
and even a subset of [`pyupgrade`](https://pypi.org/project/pyupgrade/) and [`autoflake`](https://pypi.org/project/autoflake/)
|
||||
all while executing tens or hundreds of times faster than any individual tool. Ruff goes beyond the
|
||||
responsibilities of a traditional linter, instead functioning as an advanced code transformation
|
||||
tool capable of upgrading type annotations, rewriting class definitions, sorting imports, and more.
|
||||
functionality behind a single, common interface.
|
||||
|
||||
Ruff can be used to replace Flake8 (plus a variety of plugins), [`isort`](https://pypi.org/project/isort/),
|
||||
[`pydocstyle`](https://pypi.org/project/pydocstyle/), [`yesqa`](https://github.com/asottile/yesqa),
|
||||
[`eradicate`](https://pypi.org/project/eradicate/), [`pyupgrade`](https://pypi.org/project/pyupgrade/),
|
||||
and [`autoflake`](https://pypi.org/project/autoflake/), all while executing tens or hundreds of
|
||||
times faster than any individual tool.
|
||||
|
||||
Ruff goes beyond the responsibilities of a traditional linter, instead functioning as an advanced
|
||||
code transformation tool capable of upgrading type annotations, rewriting class definitions, sorting
|
||||
imports, and more.
|
||||
|
||||
Ruff is extremely actively developed and used in major open-source projects like:
|
||||
|
||||
@@ -162,7 +167,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.195'
|
||||
rev: 'v0.0.199'
|
||||
hooks:
|
||||
- id: ruff
|
||||
# Respect `exclude` and `extend-exclude` settings.
|
||||
@@ -220,8 +225,8 @@ max-complexity = 10
|
||||
```
|
||||
|
||||
As an example, the following would configure Ruff to: (1) avoid checking for line-length
|
||||
violations (`E501`); (2), always autofix, but never remove unused imports (`F401`); and (3) ignore
|
||||
import-at-top-of-file errors (`E402`) in `__init__.py` files:
|
||||
violations (`E501`); (2) never remove unused imports (`F401`); and (3) ignore import-at-top-of-file
|
||||
errors (`E402`) in `__init__.py` files:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
@@ -231,8 +236,7 @@ select = ["E", "F"]
|
||||
# Never enforce `E501` (line length violations).
|
||||
ignore = ["E501"]
|
||||
|
||||
# Always autofix, but never try to fix `F401` (unused imports).
|
||||
fix = true
|
||||
# Never try to fix `F401` (unused imports).
|
||||
unfixable = ["F401"]
|
||||
|
||||
# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
|
||||
@@ -252,6 +256,17 @@ select = ["E", "F", "Q"]
|
||||
docstring-quotes = "double"
|
||||
```
|
||||
|
||||
Ruff mirrors Flake8's error code system, in which each error code consists of a one-to-three letter
|
||||
prefix, followed by three digits (e.g., `F401`). The prefix indicates that "source" of the error
|
||||
code (e.g., `F` for Pyflakes, `E` for `pycodestyle`, `ANN` for `flake8-annotations`). The set of
|
||||
enabled errors is determined by the `select` and `ignore` options, which support both the full
|
||||
error code (e.g., `F401`) and the prefix (e.g., `F`).
|
||||
|
||||
As a special-case, Ruff also supports the `ALL` error code, which enables all error codes. Note that
|
||||
some of the `pydocstyle` error codes are conflicting (e.g., `D203` and `D211`) as they represent
|
||||
alternative docstring formats. Enabling `ALL` without further configuration may result in suboptimal
|
||||
behavior, especially for the `pydocstyle` plugin.
|
||||
|
||||
As an alternative to `pyproject.toml`, Ruff will also respect a `ruff.toml` file, which implements
|
||||
an equivalent schema (though the `[tool.ruff]` hierarchy can be omitted). For example, the above
|
||||
`pyproject.toml` described above would be represented via the following `ruff.toml`:
|
||||
@@ -308,6 +323,8 @@ Options:
|
||||
Attempt to automatically fix lint errors
|
||||
--fix-only
|
||||
Fix any fixable lint errors, but don't report on leftover violations. Implies `--fix`
|
||||
--diff
|
||||
Avoid writing any fixed files back; instead, output a diff for each changed file to stdout
|
||||
-n, --no-cache
|
||||
Disable cache reads
|
||||
--select <SELECT>
|
||||
@@ -329,7 +346,7 @@ Options:
|
||||
--per-file-ignores <PER_FILE_IGNORES>
|
||||
List of mappings from file pattern to code to exclude
|
||||
--format <FORMAT>
|
||||
Output serialization format for error messages [possible values: text, json, junit, grouped, github]
|
||||
Output serialization format for error messages [possible values: text, json, junit, grouped, github, gitlab]
|
||||
--show-source
|
||||
Show violations with source code
|
||||
--respect-gitignore
|
||||
@@ -639,7 +656,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
|
||||
| UP001 | UselessMetaclassType | `__metaclass__ = type` is implied | 🛠 |
|
||||
| UP003 | TypeOfPrimitive | Use `str` instead of `type(...)` | 🛠 |
|
||||
| UP004 | UselessObjectInheritance | Class `...` inherits from object | 🛠 |
|
||||
| UP005 | DeprecatedUnittestAlias | `assertEquals` is deprecated, use `assertEqual` instead | 🛠 |
|
||||
| UP005 | DeprecatedUnittestAlias | `assertEquals` is deprecated, use `assertEqual` | 🛠 |
|
||||
| UP006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | 🛠 |
|
||||
| UP007 | UsePEP604Annotation | Use `X \| Y` for type annotations | 🛠 |
|
||||
| UP008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | 🛠 |
|
||||
@@ -654,6 +671,10 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
|
||||
| UP017 | DatetimeTimezoneUTC | Use `datetime.UTC` alias | 🛠 |
|
||||
| UP018 | NativeLiterals | Unnecessary call to `str` and `bytes` | 🛠 |
|
||||
| UP019 | TypingTextStrAlias | `typing.Text` is deprecated, use `str` | 🛠 |
|
||||
| UP020 | OpenAlias | Use builtin `open` | 🛠 |
|
||||
| UP021 | ReplaceUniversalNewlines | `universal_newlines` is deprecated, use `text` | 🛠 |
|
||||
| UP022 | ReplaceStdoutStderr | Sending stdout and stderr to pipe is deprecated, use `capture_output` | 🛠 |
|
||||
| UP023 | RewriteCElementTree | `cElementTree` is deprecated, use `ElementTree` | 🛠 |
|
||||
|
||||
### pep8-naming (N)
|
||||
|
||||
@@ -977,6 +998,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'?) | |
|
||||
| RUF004 | KeywordArgumentBeforeStarArgument | Keyword argument `...` must come after starred arguments | |
|
||||
| RUF100 | UnusedNOQA | Unused blanket `noqa` directive | 🛠 |
|
||||
|
||||
<!-- End auto-generated sections. -->
|
||||
@@ -1257,7 +1279,7 @@ natively, including:
|
||||
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
|
||||
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
|
||||
- [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (19/33)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (20/33)
|
||||
- [`yesqa`](https://github.com/asottile/yesqa)
|
||||
|
||||
Note that, in some cases, Ruff uses different error code prefixes than would be found in the
|
||||
@@ -1314,7 +1336,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
Ruff can also replace [`isort`](https://pypi.org/project/isort/),
|
||||
[`yesqa`](https://github.com/asottile/yesqa), [`eradicate`](https://pypi.org/project/eradicate/),
|
||||
[`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10), and a subset of the rules
|
||||
implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (19/33).
|
||||
implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (20/33).
|
||||
|
||||
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
|
||||
|
||||
@@ -1361,6 +1383,22 @@ src = ["src", "tests"]
|
||||
known-first-party = ["my_module1", "my_module2"]
|
||||
```
|
||||
|
||||
### Does Ruff support Jupyter Notebooks?
|
||||
|
||||
Ruff is integrated into [nbQA](https://github.com/nbQA-dev/nbQA), a tool for running linters and
|
||||
code formatters over Jupyter Notebooks.
|
||||
|
||||
After installing `ruff` and `nbqa`, you can run Ruff over a notebook like so:
|
||||
|
||||
```shell
|
||||
> nbqa ruff Untitled.ipynb
|
||||
Untitled.ipynb:cell_1:2:5: F841 Local variable `x` is assigned to but never used
|
||||
Untitled.ipynb:cell_2:1:1: E402 Module level import not at top of file
|
||||
Untitled.ipynb:cell_2:1:8: F401 `os` imported but unused
|
||||
Found 3 error(s).
|
||||
1 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
### Does Ruff support NumPy- or Google-style docstrings?
|
||||
|
||||
Yes! To enable a specific docstring convention, start by enabling all `pydocstyle` error codes, and
|
||||
@@ -1431,6 +1469,18 @@ extend-ignore = [
|
||||
]
|
||||
```
|
||||
|
||||
Note that Ruff _also_ supports a [`convention`](#convention) setting:
|
||||
|
||||
```toml
|
||||
[tool.ruff.pydocstyle]
|
||||
convention = "google"
|
||||
```
|
||||
|
||||
However, this setting is purely used to implement robust detection of Google and NumPy-style
|
||||
sections, and thus avoid the [false negatives](https://github.com/PyCQA/pydocstyle/issues/459) seen
|
||||
in `pydocstyle`; it does not affect which errors are enabled, which is driven by the `select` and
|
||||
`ignore` settings, as described above.
|
||||
|
||||
## Development
|
||||
|
||||
Ruff is written in Rust (1.65.0). You'll need to install the [Rust toolchain](https://www.rust-lang.org/tools/install)
|
||||
@@ -1446,8 +1496,8 @@ For development, we use [nightly Rust](https://rust-lang.github.io/rustup/concep
|
||||
|
||||
```shell
|
||||
cargo +nightly fmt
|
||||
cargo +nightly clippy
|
||||
cargo +nightly test
|
||||
cargo +nightly clippy --fix --workspace --all-targets --all-features -- -W clippy::pedantic
|
||||
cargo +nightly test --all
|
||||
```
|
||||
|
||||
## Releases
|
||||
@@ -1883,8 +1933,9 @@ force-exclude = true
|
||||
|
||||
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).
|
||||
(machine-readable), `"junit"` (machine-readable XML), `"github"`
|
||||
(GitHub Actions annotations) or `"gitlab"`
|
||||
(GitLab CI code quality report).
|
||||
|
||||
**Default value**: `"text"`
|
||||
|
||||
@@ -2462,6 +2513,23 @@ extra-standard-library = ["path"]
|
||||
|
||||
---
|
||||
|
||||
#### [`force-single-line`](#force-single-line)
|
||||
|
||||
Forces all from imports to appear on their own line.
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.isort]
|
||||
force-single-line = true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`force-wrap-aliases`](#force-wrap-aliases)
|
||||
|
||||
Force `import from` statements with multiple members and at least one
|
||||
@@ -2531,6 +2599,23 @@ known-third-party = ["src"]
|
||||
|
||||
---
|
||||
|
||||
#### [`single-line-exclusions`](#single-line-exclusions)
|
||||
|
||||
One or more modules to exclude from the single line rule.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
**Type**: `Vec<String>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.isort]
|
||||
single-line-exclusions = ["os", "json"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`split-on-trailing-comma`](#split-on-trailing-comma)
|
||||
|
||||
If a comma is placed after the last member in a multi-line import, then
|
||||
@@ -2632,6 +2717,28 @@ staticmethod-decorators = ["staticmethod", "stcmthd"]
|
||||
|
||||
---
|
||||
|
||||
### `pydocstyle`
|
||||
|
||||
#### [`convention`](#convention)
|
||||
|
||||
Whether to use Google-style or Numpy-style conventions when detecting
|
||||
docstring sections. By default, conventions will be inferred from
|
||||
the available sections.
|
||||
|
||||
**Default value**: `"convention"`
|
||||
|
||||
**Type**: `Convention`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.pydocstyle]
|
||||
# Use Google-style docstrings.
|
||||
convention = "google"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `pyupgrade`
|
||||
|
||||
#### [`keep-runtime-typing`](#keep-runtime-typing)
|
||||
|
||||
4
flake8_to_ruff/Cargo.lock
generated
4
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.195"
|
||||
version = "0.0.199"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.195"
|
||||
version = "0.0.199"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.195-dev.0"
|
||||
version = "0.0.199-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -4,11 +4,12 @@ use anyhow::Result;
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
use ruff::flake8_quotes::settings::Quote;
|
||||
use ruff::flake8_tidy_imports::settings::Strictness;
|
||||
use ruff::pydocstyle::settings::Convention;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::pyproject::Pyproject;
|
||||
use ruff::{
|
||||
flake8_annotations, flake8_bugbear, flake8_errmsg, flake8_quotes, flake8_tidy_imports, mccabe,
|
||||
pep8_naming,
|
||||
pep8_naming, pydocstyle,
|
||||
};
|
||||
|
||||
use crate::black::Black;
|
||||
@@ -91,6 +92,7 @@ pub fn convert(
|
||||
let mut flake8_tidy_imports = flake8_tidy_imports::settings::Options::default();
|
||||
let mut mccabe = mccabe::settings::Options::default();
|
||||
let mut pep8_naming = pep8_naming::settings::Options::default();
|
||||
let mut pydocstyle = pydocstyle::settings::Options::default();
|
||||
for (key, value) in flake8 {
|
||||
if let Some(value) = value {
|
||||
match key.as_str() {
|
||||
@@ -200,9 +202,12 @@ pub fn convert(
|
||||
_ => eprintln!("Unexpected '{key}' value: {value}"),
|
||||
},
|
||||
// flake8-docstrings
|
||||
"docstring-convention" => {
|
||||
// No-op (handled above).
|
||||
}
|
||||
"docstring-convention" => match value.trim() {
|
||||
"google" => pydocstyle.convention = Some(Convention::Google),
|
||||
"numpy" => pydocstyle.convention = Some(Convention::Numpy),
|
||||
"pep257" | "all" => pydocstyle.convention = None,
|
||||
_ => eprintln!("Unexpected '{key}' value: {value}"),
|
||||
},
|
||||
// mccabe
|
||||
"max-complexity" | "max_complexity" => match value.clone().parse::<usize>() {
|
||||
Ok(max_complexity) => mccabe.max_complexity = Some(max_complexity),
|
||||
@@ -247,6 +252,9 @@ pub fn convert(
|
||||
if pep8_naming != pep8_naming::settings::Options::default() {
|
||||
options.pep8_naming = Some(pep8_naming);
|
||||
}
|
||||
if pydocstyle != pydocstyle::settings::Options::default() {
|
||||
options.pydocstyle = Some(pydocstyle);
|
||||
}
|
||||
|
||||
// Extract any settings from the existing `pyproject.toml`.
|
||||
if let Some(black) = black {
|
||||
@@ -271,9 +279,10 @@ mod tests {
|
||||
|
||||
use anyhow::Result;
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
use ruff::flake8_quotes;
|
||||
use ruff::pydocstyle::settings::Convention;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::pyproject::Pyproject;
|
||||
use ruff::{flake8_quotes, pydocstyle};
|
||||
|
||||
use crate::converter::convert;
|
||||
use crate::plugin::Plugin;
|
||||
@@ -325,6 +334,7 @@ mod tests {
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pydocstyle: None,
|
||||
pyupgrade: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -382,6 +392,7 @@ mod tests {
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pydocstyle: None,
|
||||
pyupgrade: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -439,6 +450,7 @@ mod tests {
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pydocstyle: None,
|
||||
pyupgrade: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -496,6 +508,7 @@ mod tests {
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pydocstyle: None,
|
||||
pyupgrade: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -558,6 +571,7 @@ mod tests {
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pydocstyle: None,
|
||||
pyupgrade: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -654,6 +668,9 @@ mod tests {
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pydocstyle: Some(pydocstyle::settings::Options {
|
||||
convention: Some(Convention::Numpy),
|
||||
}),
|
||||
pyupgrade: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -717,6 +734,7 @@ mod tests {
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pydocstyle: None,
|
||||
pyupgrade: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
@@ -167,6 +167,11 @@ export const AVAILABLE_OPTIONS: OptionGroup[] = [
|
||||
"default": '[]',
|
||||
"type": 'Vec<String>',
|
||||
},
|
||||
{
|
||||
"name": "force-single-line",
|
||||
"default": 'false',
|
||||
"type": 'bool',
|
||||
},
|
||||
{
|
||||
"name": "force-wrap-aliases",
|
||||
"default": 'false',
|
||||
@@ -182,6 +187,11 @@ export const AVAILABLE_OPTIONS: OptionGroup[] = [
|
||||
"default": '[]',
|
||||
"type": 'Vec<String>',
|
||||
},
|
||||
{
|
||||
"name": "single-line-exclusions",
|
||||
"default": '[]',
|
||||
"type": 'Vec<String>',
|
||||
},
|
||||
{
|
||||
"name": "split-on-trailing-comma",
|
||||
"default": 'true',
|
||||
@@ -212,6 +222,13 @@ export const AVAILABLE_OPTIONS: OptionGroup[] = [
|
||||
"type": 'Vec<String>',
|
||||
},
|
||||
]},
|
||||
{"name": "pydocstyle", "fields": [
|
||||
{
|
||||
"name": "convention",
|
||||
"default": '"convention"',
|
||||
"type": 'Convention',
|
||||
},
|
||||
]},
|
||||
{"name": "pyupgrade", "fields": [
|
||||
{
|
||||
"name": "keep-runtime-typing",
|
||||
|
||||
@@ -38,3 +38,8 @@ strip = true
|
||||
[tool.ruff.isort]
|
||||
force-wrap-aliases = true
|
||||
combine-as-imports = true
|
||||
force-single-line = true
|
||||
single-line-exclusions = ["os", "logging.handlers"]
|
||||
|
||||
[tool.ruff.pydocstyle]
|
||||
convention = "google"
|
||||
|
||||
18
resources/test/fixtures/isort/force_single_line.py
vendored
Normal file
18
resources/test/fixtures/isort/force_single_line.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import sys, math
|
||||
from os import path, uname
|
||||
from logging.handlers import StreamHandler, FileHandler
|
||||
|
||||
# comment 1
|
||||
from third_party import lib1, lib2, \
|
||||
lib3, lib7, lib5, lib6
|
||||
# comment 2
|
||||
from third_party import lib4
|
||||
|
||||
from foo import bar # comment 3
|
||||
from foo2 import bar2 # comment 4
|
||||
|
||||
# comment 5
|
||||
from bar import (
|
||||
a, # comment 6
|
||||
b, # comment 7
|
||||
)
|
||||
84
resources/test/fixtures/pydocstyle/D417.py
vendored
Normal file
84
resources/test/fixtures/pydocstyle/D417.py
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
def f(x, y, z):
|
||||
"""Do f.
|
||||
|
||||
Args:
|
||||
x: the value
|
||||
with a hanging indent
|
||||
|
||||
Returns:
|
||||
the value
|
||||
"""
|
||||
return x
|
||||
|
||||
|
||||
def f(x, y, z):
|
||||
"""Do f.
|
||||
|
||||
Args:
|
||||
x:
|
||||
The whole thing has a hanging indent.
|
||||
|
||||
Returns:
|
||||
the value
|
||||
"""
|
||||
return x
|
||||
|
||||
|
||||
def f(x, y, z):
|
||||
"""Do f.
|
||||
|
||||
Args:
|
||||
x:
|
||||
The whole thing has a hanging indent.
|
||||
|
||||
Returns: the value
|
||||
"""
|
||||
return x
|
||||
|
||||
|
||||
def f(x, y, z):
|
||||
"""Do f.
|
||||
|
||||
Args:
|
||||
x: the value def
|
||||
ghi
|
||||
|
||||
Returns:
|
||||
the value
|
||||
"""
|
||||
return x
|
||||
|
||||
|
||||
def f(x, y, z):
|
||||
"""Do f.
|
||||
|
||||
Args:
|
||||
x: the value
|
||||
z: A final argument
|
||||
|
||||
Returns:
|
||||
the value
|
||||
"""
|
||||
return x
|
||||
|
||||
|
||||
def f(x, y, z):
|
||||
"""Do g.
|
||||
|
||||
Args:
|
||||
x: the value
|
||||
z: A final argument
|
||||
|
||||
Returns: the value
|
||||
"""
|
||||
return x
|
||||
|
||||
|
||||
def f(x, y, z):
|
||||
"""Do h.
|
||||
|
||||
Args:
|
||||
x: the value
|
||||
z: A final argument
|
||||
"""
|
||||
return x
|
||||
9
resources/test/fixtures/pyupgrade/UP020.py
vendored
Normal file
9
resources/test/fixtures/pyupgrade/UP020.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
from io import open
|
||||
|
||||
with open("f.txt") as f:
|
||||
print(f.read())
|
||||
|
||||
import io
|
||||
|
||||
with io.open("f.txt", mode="r", buffering=-1, **kwargs) as f:
|
||||
print(f.read())
|
||||
12
resources/test/fixtures/pyupgrade/UP021.py
vendored
Normal file
12
resources/test/fixtures/pyupgrade/UP021.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import subprocess
|
||||
import subprocess as somename
|
||||
from subprocess import run
|
||||
from subprocess import run as anothername
|
||||
|
||||
subprocess.run(["foo"], universal_newlines=True, check=True)
|
||||
somename.run(["foo"], universal_newlines=True)
|
||||
|
||||
run(["foo"], universal_newlines=True, check=False)
|
||||
anothername(["foo"], universal_newlines=True)
|
||||
|
||||
subprocess.run(["foo"], check=True)
|
||||
42
resources/test/fixtures/pyupgrade/UP022.py
vendored
Normal file
42
resources/test/fixtures/pyupgrade/UP022.py
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
from subprocess import run
|
||||
import subprocess
|
||||
|
||||
output = run(["foo"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
output = subprocess.run(["foo"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
output = subprocess.run(stdout=subprocess.PIPE, args=["foo"], stderr=subprocess.PIPE)
|
||||
|
||||
output = subprocess.run(
|
||||
["foo"], stdout=subprocess.PIPE, check=True, stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
output = subprocess.run(
|
||||
["foo"], stderr=subprocess.PIPE, check=True, stdout=subprocess.PIPE
|
||||
)
|
||||
|
||||
output = subprocess.run(
|
||||
["foo"],
|
||||
stdout=subprocess.PIPE,
|
||||
check=True,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
close_fds=True,
|
||||
)
|
||||
|
||||
if output:
|
||||
output = subprocess.run(
|
||||
["foo"],
|
||||
stdout=subprocess.PIPE,
|
||||
check=True,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
# Examples that should NOT trigger the rule
|
||||
from foo import PIPE
|
||||
subprocess.run(["foo"], stdout=PIPE, stderr=PIPE)
|
||||
run(["foo"], stdout=None, stderr=PIPE)
|
||||
31
resources/test/fixtures/pyupgrade/UP023.py
vendored
Normal file
31
resources/test/fixtures/pyupgrade/UP023.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# These two imports have something after cElementTree, so they should be fixed.
|
||||
from xml.etree.cElementTree import XML, Element, SubElement
|
||||
import xml.etree.cElementTree as ET
|
||||
|
||||
# Weird spacing should not cause issues.
|
||||
from xml.etree.cElementTree import XML
|
||||
import xml.etree.cElementTree as ET
|
||||
|
||||
# Multi line imports should also work fine.
|
||||
from xml.etree.cElementTree import (
|
||||
XML,
|
||||
Element,
|
||||
SubElement,
|
||||
)
|
||||
if True:
|
||||
import xml.etree.cElementTree as ET
|
||||
from xml.etree import cElementTree as CET
|
||||
|
||||
from xml.etree import cElementTree as ET
|
||||
|
||||
import contextlib, xml.etree.cElementTree as ET
|
||||
|
||||
# This should fix the second, but not the first invocation.
|
||||
import xml.etree.cElementTree, xml.etree.cElementTree as ET
|
||||
|
||||
# The below items should NOT be changed.
|
||||
import xml.etree.cElementTree
|
||||
|
||||
from .xml.etree.cElementTree import XML
|
||||
|
||||
from xml.etree import cElementTree
|
||||
15
resources/test/fixtures/ruff/RUF004.py
vendored
Normal file
15
resources/test/fixtures/ruff/RUF004.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
def f(*args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
a = (1, 2)
|
||||
b = (3, 4)
|
||||
c = (5, 6)
|
||||
d = (7, 8)
|
||||
|
||||
f(a, b)
|
||||
f(a, kw=b)
|
||||
f(*a, kw=b)
|
||||
f(kw=a, *b)
|
||||
f(kw=a, *b, *c)
|
||||
f(*a, kw=b, *c, kw1=d)
|
||||
@@ -111,7 +111,7 @@
|
||||
}
|
||||
},
|
||||
"flake8-annotations": {
|
||||
"description": "Plugins Options for the `flake8-annotations` plugin.",
|
||||
"description": "Options for the `flake8-annotations` plugin.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Flake8AnnotationsOptions"
|
||||
@@ -195,7 +195,7 @@
|
||||
]
|
||||
},
|
||||
"format": {
|
||||
"description": "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).",
|
||||
"description": "The style in which violation messages should be formatted: `\"text\"` (default), `\"grouped\"` (group messages by file), `\"json\"` (machine-readable), `\"junit\"` (machine-readable XML), `\"github\"` (GitHub Actions annotations) or `\"gitlab\"` (GitLab CI code quality report).",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SerializationFormat"
|
||||
@@ -277,6 +277,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"pydocstyle": {
|
||||
"description": "Options for the `pydocstyle` plugin.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Pydocstyle"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pyupgrade": {
|
||||
"description": "Options for the `pyupgrade` plugin.",
|
||||
"anyOf": [
|
||||
@@ -366,6 +377,7 @@
|
||||
"A001",
|
||||
"A002",
|
||||
"A003",
|
||||
"ALL",
|
||||
"ANN",
|
||||
"ANN0",
|
||||
"ANN00",
|
||||
@@ -794,6 +806,7 @@
|
||||
"RUF001",
|
||||
"RUF002",
|
||||
"RUF003",
|
||||
"RUF004",
|
||||
"RUF1",
|
||||
"RUF10",
|
||||
"RUF100",
|
||||
@@ -865,6 +878,11 @@
|
||||
"UP017",
|
||||
"UP018",
|
||||
"UP019",
|
||||
"UP02",
|
||||
"UP020",
|
||||
"UP021",
|
||||
"UP022",
|
||||
"UP023",
|
||||
"W",
|
||||
"W2",
|
||||
"W29",
|
||||
@@ -891,6 +909,13 @@
|
||||
"YTT303"
|
||||
]
|
||||
},
|
||||
"Convention": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"google",
|
||||
"numpy"
|
||||
]
|
||||
},
|
||||
"Flake8AnnotationsOptions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1078,6 +1103,13 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"force-single-line": {
|
||||
"description": "Forces all from imports to appear on their own line.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"force-wrap-aliases": {
|
||||
"description": "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:\n\n```py from .utils import ( test_directory as test_directory, test_id as test_id ) ```\n\nNote 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.",
|
||||
"type": [
|
||||
@@ -1105,6 +1137,16 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"single-line-exclusions": {
|
||||
"description": "One or more modules to exclude from the single line rule.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"split-on-trailing-comma": {
|
||||
"description": "If a comma is placed after the last member in a multi-line import, then the imports will never be folded into one line.\n\nSee isort's [`split-on-trailing-comma`](https://pycqa.github.io/isort/docs/configuration/options.html#split-on-trailing-comma) option.",
|
||||
"type": [
|
||||
@@ -1179,6 +1221,23 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Pydocstyle": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"convention": {
|
||||
"description": "Whether to use Google-style or Numpy-style conventions when detecting docstring sections. By default, conventions will be inferred from the available sections.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Convention"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"PythonVersion": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -1207,7 +1266,8 @@
|
||||
"json",
|
||||
"junit",
|
||||
"grouped",
|
||||
"github"
|
||||
"github",
|
||||
"gitlab"
|
||||
]
|
||||
},
|
||||
"Strictness": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.195"
|
||||
version = "0.0.199"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -11,9 +11,9 @@ itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
|
||||
once_cell = { version = "1.16.0" }
|
||||
ruff = { path = ".." }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "247e815880766d556ef1ca6f0af69daf1a5fe59a" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "247e815880766d556ef1ca6f0af69daf1a5fe59a" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "247e815880766d556ef1ca6f0af69daf1a5fe59a" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
|
||||
schemars = { version = "0.8.11" }
|
||||
serde_json = {version="1.0.91"}
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
|
||||
35
ruff_dev/src/generate_all.rs
Normal file
35
ruff_dev/src/generate_all.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
//! Run all code and documentation generation steps.
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
|
||||
use crate::{
|
||||
generate_check_code_prefix, generate_json_schema, generate_options,
|
||||
generate_playground_options, generate_rules_table,
|
||||
};
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Cli {
|
||||
/// Write the generated artifacts to stdout (rather than to the filesystem).
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
}
|
||||
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
generate_check_code_prefix::main(&generate_check_code_prefix::Cli {
|
||||
dry_run: cli.dry_run,
|
||||
})?;
|
||||
generate_json_schema::main(&generate_json_schema::Cli {
|
||||
dry_run: cli.dry_run,
|
||||
})?;
|
||||
generate_rules_table::main(&generate_rules_table::Cli {
|
||||
dry_run: cli.dry_run,
|
||||
})?;
|
||||
generate_options::main(&generate_options::Cli {
|
||||
dry_run: cli.dry_run,
|
||||
})?;
|
||||
generate_playground_options::main(&generate_playground_options::Cli {
|
||||
dry_run: cli.dry_run,
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -13,13 +13,15 @@ use itertools::Itertools;
|
||||
use ruff::checks::{CheckCode, PREFIX_REDIRECTS};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
const ALL: &str = "ALL";
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub struct Cli {
|
||||
/// Write the generated source code to stdout (rather than to
|
||||
/// `src/checks_gen.rs`).
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
pub(crate) dry_run: bool,
|
||||
}
|
||||
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
@@ -34,9 +36,15 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
let code_suffix_len = code_str.len() - code_prefix_len;
|
||||
for i in 0..=code_suffix_len {
|
||||
let prefix = code_str[..code_prefix_len + i].to_string();
|
||||
let entry = prefix_to_codes.entry(prefix).or_default();
|
||||
entry.insert(check_code.clone());
|
||||
prefix_to_codes
|
||||
.entry(prefix)
|
||||
.or_default()
|
||||
.insert(check_code.clone());
|
||||
}
|
||||
prefix_to_codes
|
||||
.entry(ALL.to_string())
|
||||
.or_default()
|
||||
.insert(check_code.clone());
|
||||
}
|
||||
|
||||
// Add any prefix aliases (e.g., "U" to "UP").
|
||||
@@ -79,6 +87,7 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
.derive("Eq")
|
||||
.derive("PartialOrd")
|
||||
.derive("Ord")
|
||||
.push_variant(Variant::new("None"))
|
||||
.push_variant(Variant::new("Zero"))
|
||||
.push_variant(Variant::new("One"))
|
||||
.push_variant(Variant::new("Two"))
|
||||
@@ -129,14 +138,18 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
.line("#[allow(clippy::match_same_arms)]")
|
||||
.line("match self {");
|
||||
for prefix in prefix_to_codes.keys() {
|
||||
let num_numeric = prefix.chars().filter(|char| char.is_numeric()).count();
|
||||
let specificity = match num_numeric {
|
||||
0 => "Zero",
|
||||
1 => "One",
|
||||
2 => "Two",
|
||||
3 => "Three",
|
||||
4 => "Four",
|
||||
_ => panic!("Invalid prefix: {prefix}"),
|
||||
let specificity = if prefix == "ALL" {
|
||||
"None"
|
||||
} else {
|
||||
let num_numeric = prefix.chars().filter(|char| char.is_numeric()).count();
|
||||
match num_numeric {
|
||||
0 => "Zero",
|
||||
1 => "One",
|
||||
2 => "Two",
|
||||
3 => "Three",
|
||||
4 => "Four",
|
||||
_ => panic!("Invalid prefix: {prefix}"),
|
||||
}
|
||||
};
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => SuffixLength::{specificity},"
|
||||
|
||||
@@ -10,7 +10,7 @@ use schemars::schema_for;
|
||||
pub struct Cli {
|
||||
/// Write the generated table to stdout (rather than to `ruff.schema.json`).
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
pub(crate) dry_run: bool,
|
||||
}
|
||||
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
|
||||
@@ -18,7 +18,7 @@ const END_PRAGMA: &str = "<!-- End auto-generated options sections. -->";
|
||||
pub struct Cli {
|
||||
/// Write the generated table to stdout (rather than to `README.md`).
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
pub(crate) dry_run: bool,
|
||||
}
|
||||
|
||||
fn emit_field(output: &mut String, field: &OptionField, group_name: Option<&str>) {
|
||||
|
||||
@@ -14,7 +14,7 @@ use ruff::settings::options_base::{ConfigurationOptions, OptionEntry, OptionFiel
|
||||
pub struct Cli {
|
||||
/// Write the generated table to stdout (rather than to `TODO`).
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
pub(crate) dry_run: bool,
|
||||
}
|
||||
|
||||
fn emit_field(output: &mut String, field: &OptionField) {
|
||||
|
||||
@@ -21,7 +21,7 @@ const TOC_END_PRAGMA: &str = "<!-- End auto-generated table of contents. -->";
|
||||
pub struct Cli {
|
||||
/// Write the generated table to stdout (rather than to `README.md`).
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
pub(crate) dry_run: bool,
|
||||
}
|
||||
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
|
||||
@@ -11,12 +11,13 @@
|
||||
clippy::too_many_lines
|
||||
)]
|
||||
|
||||
pub mod generate_all;
|
||||
pub mod generate_check_code_prefix;
|
||||
pub mod generate_json_schema;
|
||||
pub mod generate_options;
|
||||
pub mod generate_playground_options;
|
||||
pub mod generate_rules_table;
|
||||
pub mod generate_source_code;
|
||||
pub mod print_ast;
|
||||
pub mod print_cst;
|
||||
pub mod print_tokens;
|
||||
pub mod round_trip;
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use ruff_dev::{
|
||||
generate_check_code_prefix, generate_json_schema, generate_options,
|
||||
generate_playground_options, generate_rules_table, generate_source_code, print_ast, print_cst,
|
||||
print_tokens,
|
||||
generate_all, generate_check_code_prefix, generate_json_schema, generate_options,
|
||||
generate_playground_options, generate_rules_table, print_ast, print_cst, print_tokens,
|
||||
round_trip,
|
||||
};
|
||||
|
||||
#[derive(Parser)]
|
||||
@@ -29,6 +29,8 @@ struct Cli {
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Run all code and documentation generation steps.
|
||||
GenerateAll(generate_all::Cli),
|
||||
/// Generate the `CheckCodePrefix` enum.
|
||||
GenerateCheckCodePrefix(generate_check_code_prefix::Cli),
|
||||
/// Generate JSON schema for the TOML configuration file.
|
||||
@@ -40,28 +42,29 @@ enum Commands {
|
||||
/// Generate typescript file defining options to be used by the web
|
||||
/// playground.
|
||||
GeneratePlaygroundOptions(generate_playground_options::Cli),
|
||||
/// Run round-trip source code generation on a given Python file.
|
||||
GenerateSourceCode(generate_source_code::Cli),
|
||||
/// Print the AST for a given Python file.
|
||||
PrintAST(print_ast::Cli),
|
||||
/// Print the LibCST CST for a given Python file.
|
||||
PrintCST(print_cst::Cli),
|
||||
/// Print the token stream for a given Python file.
|
||||
PrintTokens(print_tokens::Cli),
|
||||
/// Run round-trip source code generation on a given Python file.
|
||||
RoundTrip(round_trip::Cli),
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
match &cli.command {
|
||||
Commands::GenerateAll(args) => generate_all::main(args)?,
|
||||
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)?,
|
||||
Commands::GeneratePlaygroundOptions(args) => generate_playground_options::main(args)?,
|
||||
Commands::PrintAST(args) => print_ast::main(args)?,
|
||||
Commands::PrintCST(args) => print_cst::main(args)?,
|
||||
Commands::PrintTokens(args) => print_tokens::main(args)?,
|
||||
Commands::RoundTrip(args) => round_trip::main(args)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use ruff::code_gen::SourceGenerator;
|
||||
use ruff::source_code_generator::SourceCodeGenerator;
|
||||
use ruff::source_code_locator::SourceCodeLocator;
|
||||
use ruff::source_code_style::SourceCodeStyleDetector;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
#[derive(Args)]
|
||||
@@ -18,7 +20,9 @@ pub struct Cli {
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
let contents = fs::read_to_string(&cli.file)?;
|
||||
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
|
||||
let mut generator = SourceGenerator::new();
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_suite(&python_ast);
|
||||
println!("{}", generator.generate()?);
|
||||
Ok(())
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.195"
|
||||
version = "0.0.199"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -382,10 +382,8 @@ pub fn identifier_range(stmt: &Stmt, locator: &SourceCodeLocator) -> Range {
|
||||
| StmtKind::AsyncFunctionDef { .. }
|
||||
) {
|
||||
let contents = locator.slice_source_code_range(&Range::from_located(stmt));
|
||||
for (start, tok, end) in lexer::make_tokenizer(&contents).flatten() {
|
||||
for (start, tok, end) in lexer::make_tokenizer_located(&contents, stmt.location).flatten() {
|
||||
if matches!(tok, Tok::Name { .. }) {
|
||||
let start = to_absolute(start, stmt.location);
|
||||
let end = to_absolute(end, stmt.location);
|
||||
return Range {
|
||||
location: start,
|
||||
end_location: end,
|
||||
@@ -412,15 +410,15 @@ pub fn excepthandler_name_range(
|
||||
location: type_end_location,
|
||||
end_location: body[0].location,
|
||||
});
|
||||
let range = lexer::make_tokenizer(&contents)
|
||||
let range = lexer::make_tokenizer_located(&contents, type_end_location)
|
||||
.flatten()
|
||||
.tuple_windows()
|
||||
.find(|(tok, next_tok)| {
|
||||
matches!(tok.1, Tok::As) && matches!(next_tok.1, Tok::Name { .. })
|
||||
})
|
||||
.map(|((..), (start, _, end))| Range {
|
||||
location: to_absolute(start, type_end_location),
|
||||
end_location: to_absolute(end, type_end_location),
|
||||
.map(|((..), (location, _, end_location))| Range {
|
||||
location,
|
||||
end_location,
|
||||
});
|
||||
range
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::source_code_locator::SourceCodeLocator;
|
||||
pub enum Mode {
|
||||
Generate,
|
||||
Apply,
|
||||
Diff,
|
||||
None,
|
||||
}
|
||||
|
||||
|
||||
17
src/cache.rs
17
src/cache.rs
@@ -12,7 +12,6 @@ use once_cell::sync::Lazy;
|
||||
use path_absolutize::Absolutize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::message::Message;
|
||||
use crate::settings::{flags, Settings};
|
||||
|
||||
@@ -48,7 +47,7 @@ fn content_dir() -> &'static Path {
|
||||
Path::new("content")
|
||||
}
|
||||
|
||||
fn cache_key<P: AsRef<Path>>(path: P, settings: &Settings, autofix: fixer::Mode) -> u64 {
|
||||
fn cache_key<P: AsRef<Path>>(path: P, settings: &Settings, autofix: flags::Autofix) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
CARGO_PKG_VERSION.hash(&mut hasher);
|
||||
path.as_ref().absolutize().unwrap().hash(&mut hasher);
|
||||
@@ -93,13 +92,8 @@ pub fn get<P: AsRef<Path>>(
|
||||
path: P,
|
||||
metadata: &Metadata,
|
||||
settings: &Settings,
|
||||
autofix: fixer::Mode,
|
||||
cache: flags::Cache,
|
||||
autofix: flags::Autofix,
|
||||
) -> Option<Vec<Message>> {
|
||||
if matches!(cache, flags::Cache::Disabled) {
|
||||
return None;
|
||||
};
|
||||
|
||||
let encoded = read_sync(&settings.cache_dir, cache_key(path, settings, autofix)).ok()?;
|
||||
let (mtime, messages) = match bincode::deserialize::<CheckResult>(&encoded[..]) {
|
||||
Ok(CheckResult {
|
||||
@@ -122,14 +116,9 @@ pub fn set<P: AsRef<Path>>(
|
||||
path: P,
|
||||
metadata: &Metadata,
|
||||
settings: &Settings,
|
||||
autofix: fixer::Mode,
|
||||
autofix: flags::Autofix,
|
||||
messages: &[Message],
|
||||
cache: flags::Cache,
|
||||
) {
|
||||
if matches!(cache, flags::Cache::Disabled) {
|
||||
return;
|
||||
};
|
||||
|
||||
let check_result = CheckResultRef {
|
||||
metadata: &CacheMetadata {
|
||||
mtime: FileTime::from_last_modification_time(metadata).unix_seconds(),
|
||||
|
||||
@@ -33,14 +33,16 @@ use crate::python::typing::SubscriptKind;
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::vendored::cformat::{CFormatError, CFormatErrorType};
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
use crate::vendor::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_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,
|
||||
pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff,
|
||||
visibility,
|
||||
};
|
||||
|
||||
const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
@@ -56,6 +58,7 @@ pub struct Checker<'a> {
|
||||
pub(crate) settings: &'a Settings,
|
||||
pub(crate) noqa_line_for: &'a IntMap<usize, usize>,
|
||||
pub(crate) locator: &'a SourceCodeLocator<'a>,
|
||||
pub(crate) style: &'a SourceCodeStyleDetector<'a>,
|
||||
// Computed checks.
|
||||
checks: Vec<Check>,
|
||||
// Function and class definition tracking (e.g., for docstring enforcement).
|
||||
@@ -107,6 +110,7 @@ impl<'a> Checker<'a> {
|
||||
noqa: flags::Noqa,
|
||||
path: &'a Path,
|
||||
locator: &'a SourceCodeLocator,
|
||||
style: &'a SourceCodeStyleDetector,
|
||||
) -> Checker<'a> {
|
||||
Checker {
|
||||
settings,
|
||||
@@ -115,6 +119,7 @@ impl<'a> Checker<'a> {
|
||||
noqa,
|
||||
path,
|
||||
locator,
|
||||
style,
|
||||
checks: vec![],
|
||||
definitions: vec![],
|
||||
deletions: FxHashSet::default(),
|
||||
@@ -645,6 +650,9 @@ where
|
||||
));
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP023) {
|
||||
pyupgrade::plugins::replace_c_element_tree(self, stmt);
|
||||
}
|
||||
|
||||
for alias in names {
|
||||
if alias.node.name.contains('.') && alias.node.asname.is_none() {
|
||||
@@ -814,6 +822,9 @@ where
|
||||
} => {
|
||||
// Track `import from` statements, to ensure that we can correctly attribute
|
||||
// references like `from typing import Union`.
|
||||
if self.settings.enabled.contains(&CheckCode::UP023) {
|
||||
pyupgrade::plugins::replace_c_element_tree(self, stmt);
|
||||
}
|
||||
if level.map(|level| level == 0).unwrap_or(true) {
|
||||
if let Some(module) = module {
|
||||
self.from_imports
|
||||
@@ -1547,9 +1558,6 @@ where
|
||||
pyupgrade::plugins::use_pep585_annotation(self, expr, attr);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::UP019) {
|
||||
pyupgrade::plugins::typing_text_str_alias(self, expr);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP016) {
|
||||
pyupgrade::plugins::remove_six_compat(self, expr);
|
||||
}
|
||||
@@ -1559,7 +1567,9 @@ where
|
||||
{
|
||||
pyupgrade::plugins::datetime_utc_alias(self, expr);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::UP019) {
|
||||
pyupgrade::plugins::typing_text_str_alias(self, expr);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::YTT202) {
|
||||
flake8_2020::plugins::name_or_attribute(self, expr);
|
||||
}
|
||||
@@ -1642,22 +1652,35 @@ where
|
||||
}
|
||||
|
||||
// pyupgrade
|
||||
if self.settings.enabled.contains(&CheckCode::UP003) {
|
||||
pyupgrade::plugins::type_of_primitive(self, expr, func, args);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP005) {
|
||||
pyupgrade::plugins::deprecated_unittest_alias(self, func);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP008) {
|
||||
pyupgrade::plugins::super_call_with_parameters(self, expr, func, args);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP012) {
|
||||
pyupgrade::plugins::unnecessary_encode_utf8(self, expr, func, args, keywords);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP015) {
|
||||
pyupgrade::plugins::redundant_open_modes(self, expr);
|
||||
}
|
||||
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) {
|
||||
pyupgrade::plugins::super_call_with_parameters(self, expr, func, args);
|
||||
if self.settings.enabled.contains(&CheckCode::UP020) {
|
||||
pyupgrade::plugins::open_alias(self, expr, func);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP021) {
|
||||
pyupgrade::plugins::replace_universal_newlines(self, expr, keywords);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP022) {
|
||||
pyupgrade::plugins::replace_stdout_stderr(self, expr, keywords);
|
||||
}
|
||||
|
||||
// flake8-print
|
||||
@@ -1722,7 +1745,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C401) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_set(
|
||||
expr,
|
||||
@@ -1736,7 +1758,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C402) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_dict(
|
||||
expr,
|
||||
@@ -1750,7 +1771,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C403) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_list_comprehension_set(
|
||||
@@ -1766,7 +1786,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C404) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_list_comprehension_dict(
|
||||
@@ -1782,7 +1801,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C405) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_set(
|
||||
expr,
|
||||
@@ -1796,7 +1814,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C406) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_dict(
|
||||
expr,
|
||||
@@ -1810,7 +1827,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C408) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_collection_call(
|
||||
expr,
|
||||
@@ -1824,7 +1840,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C409) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_literal_within_tuple_call(
|
||||
@@ -1839,7 +1854,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C410) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_literal_within_list_call(
|
||||
@@ -1854,7 +1868,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C411) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_list_call(
|
||||
expr,
|
||||
@@ -1867,7 +1880,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C413) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_call_around_sorted(
|
||||
@@ -1882,7 +1894,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C414) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_double_cast_or_process(
|
||||
@@ -1894,7 +1905,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C415) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_subscript_reversal(
|
||||
@@ -1906,7 +1916,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C417) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_map(
|
||||
func,
|
||||
@@ -1917,15 +1926,6 @@ where
|
||||
};
|
||||
}
|
||||
|
||||
// pyupgrade
|
||||
if self.settings.enabled.contains(&CheckCode::UP003) {
|
||||
pyupgrade::plugins::type_of_primitive(self, expr, func, args);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::UP015) {
|
||||
pyupgrade::plugins::redundant_open_modes(self, expr);
|
||||
}
|
||||
|
||||
// flake8-boolean-trap
|
||||
if self.settings.enabled.contains(&CheckCode::FBT003) {
|
||||
flake8_boolean_trap::plugins::check_boolean_positional_value_in_function_call(
|
||||
@@ -1956,7 +1956,6 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::PD002) {
|
||||
self.add_checks(pandas_vet::checks::inplace_argument(keywords).into_iter());
|
||||
}
|
||||
|
||||
for (code, name) in vec![
|
||||
(CheckCode::PD003, "isnull"),
|
||||
(CheckCode::PD004, "notnull"),
|
||||
@@ -1973,7 +1972,6 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::PD015) {
|
||||
if let Some(check) = pandas_vet::checks::use_of_pd_merge(func) {
|
||||
self.add_check(check);
|
||||
@@ -2067,6 +2065,14 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::PLR1722) {
|
||||
pylint::plugins::use_sys_exit(self, func);
|
||||
}
|
||||
|
||||
// ruff
|
||||
if self.settings.enabled.contains(&CheckCode::RUF004) {
|
||||
self.add_checks(
|
||||
ruff::checks::keyword_argument_before_star_argument(args, keywords)
|
||||
.into_iter(),
|
||||
);
|
||||
}
|
||||
}
|
||||
ExprKind::Dict { keys, .. } => {
|
||||
let check_repeated_literals = self.settings.enabled.contains(&CheckCode::F601);
|
||||
@@ -3872,7 +3878,11 @@ impl<'a> Checker<'a> {
|
||||
|| self.settings.enabled.contains(&CheckCode::D416)
|
||||
|| self.settings.enabled.contains(&CheckCode::D417)
|
||||
{
|
||||
pydocstyle::plugins::sections(self, &docstring);
|
||||
pydocstyle::plugins::sections(
|
||||
self,
|
||||
&docstring,
|
||||
self.settings.pydocstyle.convention.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3915,16 +3925,26 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn check_ast(
|
||||
python_ast: &Suite,
|
||||
locator: &SourceCodeLocator,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
noqa_line_for: &IntMap<usize, usize>,
|
||||
settings: &Settings,
|
||||
autofix: flags::Autofix,
|
||||
noqa: flags::Noqa,
|
||||
path: &Path,
|
||||
) -> Vec<Check> {
|
||||
let mut checker = Checker::new(settings, noqa_line_for, autofix, noqa, path, locator);
|
||||
let mut checker = Checker::new(
|
||||
settings,
|
||||
noqa_line_for,
|
||||
autofix,
|
||||
noqa,
|
||||
path,
|
||||
locator,
|
||||
stylist,
|
||||
);
|
||||
checker.push_scope(Scope::new(ScopeKind::Module));
|
||||
checker.bind_builtins();
|
||||
|
||||
|
||||
@@ -227,6 +227,10 @@ pub enum CheckCode {
|
||||
UP017,
|
||||
UP018,
|
||||
UP019,
|
||||
UP020,
|
||||
UP021,
|
||||
UP022,
|
||||
UP023,
|
||||
// pydocstyle
|
||||
D100,
|
||||
D101,
|
||||
@@ -326,6 +330,7 @@ pub enum CheckCode {
|
||||
RUF001,
|
||||
RUF002,
|
||||
RUF003,
|
||||
RUF004,
|
||||
RUF100,
|
||||
// pygrep-hooks
|
||||
PGH001,
|
||||
@@ -839,6 +844,10 @@ pub enum CheckKind {
|
||||
RemoveSixCompat,
|
||||
DatetimeTimezoneUTC,
|
||||
NativeLiterals,
|
||||
OpenAlias,
|
||||
ReplaceUniversalNewlines,
|
||||
ReplaceStdoutStderr,
|
||||
RewriteCElementTree,
|
||||
// pydocstyle
|
||||
BlankLineAfterLastSection(String),
|
||||
BlankLineAfterSection(String),
|
||||
@@ -951,6 +960,7 @@ pub enum CheckKind {
|
||||
AmbiguousUnicodeCharacterString(char, char),
|
||||
AmbiguousUnicodeCharacterDocstring(char, char),
|
||||
AmbiguousUnicodeCharacterComment(char, char),
|
||||
KeywordArgumentBeforeStarArgument(String),
|
||||
UnusedNOQA(Option<UnusedCodes>),
|
||||
// flake8-datetimez
|
||||
CallDatetimeWithoutTzinfo,
|
||||
@@ -1215,6 +1225,10 @@ impl CheckCode {
|
||||
CheckCode::UP017 => CheckKind::DatetimeTimezoneUTC,
|
||||
CheckCode::UP018 => CheckKind::NativeLiterals,
|
||||
CheckCode::UP019 => CheckKind::TypingTextStrAlias,
|
||||
CheckCode::UP020 => CheckKind::OpenAlias,
|
||||
CheckCode::UP021 => CheckKind::ReplaceUniversalNewlines,
|
||||
CheckCode::UP022 => CheckKind::ReplaceStdoutStderr,
|
||||
CheckCode::UP023 => CheckKind::RewriteCElementTree,
|
||||
// pydocstyle
|
||||
CheckCode::D100 => CheckKind::PublicModule,
|
||||
CheckCode::D101 => CheckKind::PublicClass,
|
||||
@@ -1354,6 +1368,7 @@ impl CheckCode {
|
||||
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
|
||||
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
|
||||
CheckCode::RUF003 => CheckKind::AmbiguousUnicodeCharacterComment('𝐁', 'B'),
|
||||
CheckCode::RUF004 => CheckKind::KeywordArgumentBeforeStarArgument("...".to_string()),
|
||||
CheckCode::RUF100 => CheckKind::UnusedNOQA(None),
|
||||
}
|
||||
}
|
||||
@@ -1606,6 +1621,7 @@ impl CheckCode {
|
||||
CheckCode::RUF001 => CheckCategory::Ruff,
|
||||
CheckCode::RUF002 => CheckCategory::Ruff,
|
||||
CheckCode::RUF003 => CheckCategory::Ruff,
|
||||
CheckCode::RUF004 => CheckCategory::Ruff,
|
||||
CheckCode::RUF100 => CheckCategory::Ruff,
|
||||
CheckCode::S101 => CheckCategory::Flake8Bandit,
|
||||
CheckCode::S102 => CheckCategory::Flake8Bandit,
|
||||
@@ -1635,6 +1651,10 @@ impl CheckCode {
|
||||
CheckCode::UP017 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP018 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP019 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP020 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP021 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP022 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP023 => CheckCategory::Pyupgrade,
|
||||
CheckCode::W292 => CheckCategory::Pycodestyle,
|
||||
CheckCode::W605 => CheckCategory::Pycodestyle,
|
||||
CheckCode::YTT101 => CheckCategory::Flake82020,
|
||||
@@ -1848,6 +1868,10 @@ impl CheckKind {
|
||||
CheckKind::DatetimeTimezoneUTC => &CheckCode::UP017,
|
||||
CheckKind::NativeLiterals => &CheckCode::UP018,
|
||||
CheckKind::TypingTextStrAlias => &CheckCode::UP019,
|
||||
CheckKind::OpenAlias => &CheckCode::UP020,
|
||||
CheckKind::ReplaceUniversalNewlines => &CheckCode::UP021,
|
||||
CheckKind::ReplaceStdoutStderr => &CheckCode::UP022,
|
||||
CheckKind::RewriteCElementTree => &CheckCode::UP023,
|
||||
// pydocstyle
|
||||
CheckKind::BlankLineAfterLastSection(..) => &CheckCode::D413,
|
||||
CheckKind::BlankLineAfterSection(..) => &CheckCode::D410,
|
||||
@@ -1970,6 +1994,7 @@ impl CheckKind {
|
||||
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
|
||||
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
|
||||
CheckKind::AmbiguousUnicodeCharacterComment(..) => &CheckCode::RUF003,
|
||||
CheckKind::KeywordArgumentBeforeStarArgument(..) => &CheckCode::RUF004,
|
||||
CheckKind::UnusedNOQA(..) => &CheckCode::RUF100,
|
||||
}
|
||||
}
|
||||
@@ -2540,7 +2565,7 @@ impl CheckKind {
|
||||
CheckKind::UselessMetaclassType => "`__metaclass__ = type` is implied".to_string(),
|
||||
CheckKind::TypingTextStrAlias => "`typing.Text` is deprecated, use `str`".to_string(),
|
||||
CheckKind::DeprecatedUnittestAlias(alias, target) => {
|
||||
format!("`{alias}` is deprecated, use `{target}` instead")
|
||||
format!("`{alias}` is deprecated, use `{target}`")
|
||||
}
|
||||
CheckKind::UselessObjectInheritance(name) => {
|
||||
format!("Class `{name}` inherits from object")
|
||||
@@ -2573,9 +2598,19 @@ impl CheckKind {
|
||||
CheckKind::RemoveSixCompat => "Unnecessary `six` compatibility usage".to_string(),
|
||||
CheckKind::DatetimeTimezoneUTC => "Use `datetime.UTC` alias".to_string(),
|
||||
CheckKind::NativeLiterals => "Unnecessary call to `str` and `bytes`".to_string(),
|
||||
CheckKind::OpenAlias => "Use builtin `open`".to_string(),
|
||||
CheckKind::ConvertTypedDictFunctionalToClass(name) => {
|
||||
format!("Convert `{name}` from `TypedDict` functional to class syntax")
|
||||
}
|
||||
CheckKind::ReplaceUniversalNewlines => {
|
||||
"`universal_newlines` is deprecated, use `text`".to_string()
|
||||
}
|
||||
CheckKind::ReplaceStdoutStderr => {
|
||||
"Sending stdout and stderr to pipe is deprecated, use `capture_output`".to_string()
|
||||
}
|
||||
CheckKind::RewriteCElementTree => {
|
||||
"`cElementTree` is deprecated, use `ElementTree`".to_string()
|
||||
}
|
||||
CheckKind::ConvertNamedTupleFunctionalToClass(name) => {
|
||||
format!("Convert `{name}` from `NamedTuple` functional to class syntax")
|
||||
}
|
||||
@@ -2868,6 +2903,9 @@ impl CheckKind {
|
||||
'{representant}'?)"
|
||||
)
|
||||
}
|
||||
CheckKind::KeywordArgumentBeforeStarArgument(name) => {
|
||||
format!("Keyword argument `{name}` must come after starred arguments")
|
||||
}
|
||||
CheckKind::UnusedNOQA(codes) => match codes {
|
||||
None => "Unused blanket `noqa` directive".to_string(),
|
||||
Some(codes) => {
|
||||
@@ -3015,7 +3053,11 @@ impl CheckKind {
|
||||
| CheckKind::MisplacedComparisonConstant(..)
|
||||
| CheckKind::MissingReturnTypeSpecialMethod(..)
|
||||
| CheckKind::NativeLiterals
|
||||
| CheckKind::OpenAlias
|
||||
| CheckKind::NewLineAfterLastParagraph
|
||||
| CheckKind::ReplaceUniversalNewlines
|
||||
| CheckKind::ReplaceStdoutStderr
|
||||
| CheckKind::RewriteCElementTree
|
||||
| CheckKind::NewLineAfterSectionName(..)
|
||||
| CheckKind::NoBlankLineAfterFunction(..)
|
||||
| CheckKind::NoBlankLineBeforeClass(..)
|
||||
|
||||
@@ -28,6 +28,7 @@ pub enum CheckCodePrefix {
|
||||
A001,
|
||||
A002,
|
||||
A003,
|
||||
ALL,
|
||||
ANN,
|
||||
ANN0,
|
||||
ANN00,
|
||||
@@ -456,6 +457,7 @@ pub enum CheckCodePrefix {
|
||||
RUF001,
|
||||
RUF002,
|
||||
RUF003,
|
||||
RUF004,
|
||||
RUF1,
|
||||
RUF10,
|
||||
RUF100,
|
||||
@@ -527,6 +529,11 @@ pub enum CheckCodePrefix {
|
||||
UP017,
|
||||
UP018,
|
||||
UP019,
|
||||
UP02,
|
||||
UP020,
|
||||
UP021,
|
||||
UP022,
|
||||
UP023,
|
||||
W,
|
||||
W2,
|
||||
W29,
|
||||
@@ -555,6 +562,7 @@ pub enum CheckCodePrefix {
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum SuffixLength {
|
||||
None,
|
||||
Zero,
|
||||
One,
|
||||
Two,
|
||||
@@ -572,6 +580,299 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::A001 => vec![CheckCode::A001],
|
||||
CheckCodePrefix::A002 => vec![CheckCode::A002],
|
||||
CheckCodePrefix::A003 => vec![CheckCode::A003],
|
||||
CheckCodePrefix::ALL => vec![
|
||||
CheckCode::E401,
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
CheckCode::E721,
|
||||
CheckCode::E722,
|
||||
CheckCode::E731,
|
||||
CheckCode::E741,
|
||||
CheckCode::E742,
|
||||
CheckCode::E743,
|
||||
CheckCode::E902,
|
||||
CheckCode::E999,
|
||||
CheckCode::W292,
|
||||
CheckCode::W605,
|
||||
CheckCode::F401,
|
||||
CheckCode::F402,
|
||||
CheckCode::F403,
|
||||
CheckCode::F404,
|
||||
CheckCode::F405,
|
||||
CheckCode::F406,
|
||||
CheckCode::F407,
|
||||
CheckCode::F501,
|
||||
CheckCode::F502,
|
||||
CheckCode::F503,
|
||||
CheckCode::F504,
|
||||
CheckCode::F505,
|
||||
CheckCode::F506,
|
||||
CheckCode::F507,
|
||||
CheckCode::F508,
|
||||
CheckCode::F509,
|
||||
CheckCode::F521,
|
||||
CheckCode::F522,
|
||||
CheckCode::F523,
|
||||
CheckCode::F524,
|
||||
CheckCode::F525,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
CheckCode::F602,
|
||||
CheckCode::F621,
|
||||
CheckCode::F622,
|
||||
CheckCode::F631,
|
||||
CheckCode::F632,
|
||||
CheckCode::F633,
|
||||
CheckCode::F634,
|
||||
CheckCode::F701,
|
||||
CheckCode::F702,
|
||||
CheckCode::F704,
|
||||
CheckCode::F706,
|
||||
CheckCode::F707,
|
||||
CheckCode::F722,
|
||||
CheckCode::F811,
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F842,
|
||||
CheckCode::F901,
|
||||
CheckCode::PLC0414,
|
||||
CheckCode::PLC2201,
|
||||
CheckCode::PLC3002,
|
||||
CheckCode::PLE0117,
|
||||
CheckCode::PLE0118,
|
||||
CheckCode::PLE1142,
|
||||
CheckCode::PLR0206,
|
||||
CheckCode::PLR0402,
|
||||
CheckCode::PLR1701,
|
||||
CheckCode::PLR1722,
|
||||
CheckCode::PLW0120,
|
||||
CheckCode::PLW0602,
|
||||
CheckCode::A001,
|
||||
CheckCode::A002,
|
||||
CheckCode::A003,
|
||||
CheckCode::B002,
|
||||
CheckCode::B003,
|
||||
CheckCode::B004,
|
||||
CheckCode::B005,
|
||||
CheckCode::B006,
|
||||
CheckCode::B007,
|
||||
CheckCode::B008,
|
||||
CheckCode::B009,
|
||||
CheckCode::B010,
|
||||
CheckCode::B011,
|
||||
CheckCode::B012,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
CheckCode::B015,
|
||||
CheckCode::B016,
|
||||
CheckCode::B017,
|
||||
CheckCode::B018,
|
||||
CheckCode::B019,
|
||||
CheckCode::B020,
|
||||
CheckCode::B021,
|
||||
CheckCode::B022,
|
||||
CheckCode::B023,
|
||||
CheckCode::B024,
|
||||
CheckCode::B025,
|
||||
CheckCode::B026,
|
||||
CheckCode::B027,
|
||||
CheckCode::B904,
|
||||
CheckCode::B905,
|
||||
CheckCode::BLE001,
|
||||
CheckCode::C400,
|
||||
CheckCode::C401,
|
||||
CheckCode::C402,
|
||||
CheckCode::C403,
|
||||
CheckCode::C404,
|
||||
CheckCode::C405,
|
||||
CheckCode::C406,
|
||||
CheckCode::C408,
|
||||
CheckCode::C409,
|
||||
CheckCode::C410,
|
||||
CheckCode::C411,
|
||||
CheckCode::C413,
|
||||
CheckCode::C414,
|
||||
CheckCode::C415,
|
||||
CheckCode::C416,
|
||||
CheckCode::C417,
|
||||
CheckCode::T100,
|
||||
CheckCode::C901,
|
||||
CheckCode::TID252,
|
||||
CheckCode::RET501,
|
||||
CheckCode::RET502,
|
||||
CheckCode::RET503,
|
||||
CheckCode::RET504,
|
||||
CheckCode::RET505,
|
||||
CheckCode::RET506,
|
||||
CheckCode::RET507,
|
||||
CheckCode::RET508,
|
||||
CheckCode::T201,
|
||||
CheckCode::T203,
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
CheckCode::ANN001,
|
||||
CheckCode::ANN002,
|
||||
CheckCode::ANN003,
|
||||
CheckCode::ANN101,
|
||||
CheckCode::ANN102,
|
||||
CheckCode::ANN201,
|
||||
CheckCode::ANN202,
|
||||
CheckCode::ANN204,
|
||||
CheckCode::ANN205,
|
||||
CheckCode::ANN206,
|
||||
CheckCode::ANN401,
|
||||
CheckCode::YTT101,
|
||||
CheckCode::YTT102,
|
||||
CheckCode::YTT103,
|
||||
CheckCode::YTT201,
|
||||
CheckCode::YTT202,
|
||||
CheckCode::YTT203,
|
||||
CheckCode::YTT204,
|
||||
CheckCode::YTT301,
|
||||
CheckCode::YTT302,
|
||||
CheckCode::YTT303,
|
||||
CheckCode::SIM118,
|
||||
CheckCode::UP001,
|
||||
CheckCode::UP003,
|
||||
CheckCode::UP004,
|
||||
CheckCode::UP005,
|
||||
CheckCode::UP006,
|
||||
CheckCode::UP007,
|
||||
CheckCode::UP008,
|
||||
CheckCode::UP009,
|
||||
CheckCode::UP010,
|
||||
CheckCode::UP011,
|
||||
CheckCode::UP012,
|
||||
CheckCode::UP013,
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
CheckCode::UP017,
|
||||
CheckCode::UP018,
|
||||
CheckCode::UP019,
|
||||
CheckCode::UP020,
|
||||
CheckCode::UP021,
|
||||
CheckCode::UP022,
|
||||
CheckCode::UP023,
|
||||
CheckCode::D100,
|
||||
CheckCode::D101,
|
||||
CheckCode::D102,
|
||||
CheckCode::D103,
|
||||
CheckCode::D104,
|
||||
CheckCode::D105,
|
||||
CheckCode::D106,
|
||||
CheckCode::D107,
|
||||
CheckCode::D200,
|
||||
CheckCode::D201,
|
||||
CheckCode::D202,
|
||||
CheckCode::D203,
|
||||
CheckCode::D204,
|
||||
CheckCode::D205,
|
||||
CheckCode::D206,
|
||||
CheckCode::D207,
|
||||
CheckCode::D208,
|
||||
CheckCode::D209,
|
||||
CheckCode::D210,
|
||||
CheckCode::D211,
|
||||
CheckCode::D212,
|
||||
CheckCode::D213,
|
||||
CheckCode::D214,
|
||||
CheckCode::D215,
|
||||
CheckCode::D300,
|
||||
CheckCode::D301,
|
||||
CheckCode::D400,
|
||||
CheckCode::D402,
|
||||
CheckCode::D403,
|
||||
CheckCode::D404,
|
||||
CheckCode::D405,
|
||||
CheckCode::D406,
|
||||
CheckCode::D407,
|
||||
CheckCode::D408,
|
||||
CheckCode::D409,
|
||||
CheckCode::D410,
|
||||
CheckCode::D411,
|
||||
CheckCode::D412,
|
||||
CheckCode::D413,
|
||||
CheckCode::D414,
|
||||
CheckCode::D415,
|
||||
CheckCode::D416,
|
||||
CheckCode::D417,
|
||||
CheckCode::D418,
|
||||
CheckCode::D419,
|
||||
CheckCode::N801,
|
||||
CheckCode::N802,
|
||||
CheckCode::N803,
|
||||
CheckCode::N804,
|
||||
CheckCode::N805,
|
||||
CheckCode::N806,
|
||||
CheckCode::N807,
|
||||
CheckCode::N811,
|
||||
CheckCode::N812,
|
||||
CheckCode::N813,
|
||||
CheckCode::N814,
|
||||
CheckCode::N815,
|
||||
CheckCode::N816,
|
||||
CheckCode::N817,
|
||||
CheckCode::N818,
|
||||
CheckCode::I001,
|
||||
CheckCode::ERA001,
|
||||
CheckCode::S101,
|
||||
CheckCode::S102,
|
||||
CheckCode::S104,
|
||||
CheckCode::S105,
|
||||
CheckCode::S106,
|
||||
CheckCode::S107,
|
||||
CheckCode::FBT001,
|
||||
CheckCode::FBT002,
|
||||
CheckCode::FBT003,
|
||||
CheckCode::ARG001,
|
||||
CheckCode::ARG002,
|
||||
CheckCode::ARG003,
|
||||
CheckCode::ARG004,
|
||||
CheckCode::ARG005,
|
||||
CheckCode::ICN001,
|
||||
CheckCode::DTZ001,
|
||||
CheckCode::DTZ002,
|
||||
CheckCode::DTZ003,
|
||||
CheckCode::DTZ004,
|
||||
CheckCode::DTZ005,
|
||||
CheckCode::DTZ006,
|
||||
CheckCode::DTZ007,
|
||||
CheckCode::DTZ011,
|
||||
CheckCode::DTZ012,
|
||||
CheckCode::RUF001,
|
||||
CheckCode::RUF002,
|
||||
CheckCode::RUF003,
|
||||
CheckCode::RUF004,
|
||||
CheckCode::RUF100,
|
||||
CheckCode::PGH001,
|
||||
CheckCode::PGH002,
|
||||
CheckCode::PGH003,
|
||||
CheckCode::PD002,
|
||||
CheckCode::PD003,
|
||||
CheckCode::PD004,
|
||||
CheckCode::PD007,
|
||||
CheckCode::PD008,
|
||||
CheckCode::PD009,
|
||||
CheckCode::PD010,
|
||||
CheckCode::PD011,
|
||||
CheckCode::PD012,
|
||||
CheckCode::PD013,
|
||||
CheckCode::PD015,
|
||||
CheckCode::PD901,
|
||||
CheckCode::EM101,
|
||||
CheckCode::EM102,
|
||||
CheckCode::EM103,
|
||||
],
|
||||
CheckCodePrefix::ANN => vec![
|
||||
CheckCode::ANN001,
|
||||
CheckCode::ANN002,
|
||||
@@ -2022,13 +2323,25 @@ impl CheckCodePrefix {
|
||||
CheckCode::RUF001,
|
||||
CheckCode::RUF002,
|
||||
CheckCode::RUF003,
|
||||
CheckCode::RUF004,
|
||||
CheckCode::RUF100,
|
||||
],
|
||||
CheckCodePrefix::RUF0 => vec![CheckCode::RUF001, CheckCode::RUF002, CheckCode::RUF003],
|
||||
CheckCodePrefix::RUF00 => vec![CheckCode::RUF001, CheckCode::RUF002, CheckCode::RUF003],
|
||||
CheckCodePrefix::RUF0 => vec![
|
||||
CheckCode::RUF001,
|
||||
CheckCode::RUF002,
|
||||
CheckCode::RUF003,
|
||||
CheckCode::RUF004,
|
||||
],
|
||||
CheckCodePrefix::RUF00 => vec![
|
||||
CheckCode::RUF001,
|
||||
CheckCode::RUF002,
|
||||
CheckCode::RUF003,
|
||||
CheckCode::RUF004,
|
||||
],
|
||||
CheckCodePrefix::RUF001 => vec![CheckCode::RUF001],
|
||||
CheckCodePrefix::RUF002 => vec![CheckCode::RUF002],
|
||||
CheckCodePrefix::RUF003 => vec![CheckCode::RUF003],
|
||||
CheckCodePrefix::RUF004 => vec![CheckCode::RUF004],
|
||||
CheckCodePrefix::RUF1 => vec![CheckCode::RUF100],
|
||||
CheckCodePrefix::RUF10 => vec![CheckCode::RUF100],
|
||||
CheckCodePrefix::RUF100 => vec![CheckCode::RUF100],
|
||||
@@ -2104,6 +2417,10 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP017,
|
||||
CheckCode::UP018,
|
||||
CheckCode::UP019,
|
||||
CheckCode::UP020,
|
||||
CheckCode::UP021,
|
||||
CheckCode::UP022,
|
||||
CheckCode::UP023,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U0 => {
|
||||
@@ -2132,6 +2449,10 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP017,
|
||||
CheckCode::UP018,
|
||||
CheckCode::UP019,
|
||||
CheckCode::UP020,
|
||||
CheckCode::UP021,
|
||||
CheckCode::UP022,
|
||||
CheckCode::UP023,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U00 => {
|
||||
@@ -2344,6 +2665,10 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP017,
|
||||
CheckCode::UP018,
|
||||
CheckCode::UP019,
|
||||
CheckCode::UP020,
|
||||
CheckCode::UP021,
|
||||
CheckCode::UP022,
|
||||
CheckCode::UP023,
|
||||
],
|
||||
CheckCodePrefix::UP0 => vec![
|
||||
CheckCode::UP001,
|
||||
@@ -2364,6 +2689,10 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP017,
|
||||
CheckCode::UP018,
|
||||
CheckCode::UP019,
|
||||
CheckCode::UP020,
|
||||
CheckCode::UP021,
|
||||
CheckCode::UP022,
|
||||
CheckCode::UP023,
|
||||
],
|
||||
CheckCodePrefix::UP00 => vec![
|
||||
CheckCode::UP001,
|
||||
@@ -2405,6 +2734,16 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::UP017 => vec![CheckCode::UP017],
|
||||
CheckCodePrefix::UP018 => vec![CheckCode::UP018],
|
||||
CheckCodePrefix::UP019 => vec![CheckCode::UP019],
|
||||
CheckCodePrefix::UP02 => vec![
|
||||
CheckCode::UP020,
|
||||
CheckCode::UP021,
|
||||
CheckCode::UP022,
|
||||
CheckCode::UP023,
|
||||
],
|
||||
CheckCodePrefix::UP020 => vec![CheckCode::UP020],
|
||||
CheckCodePrefix::UP021 => vec![CheckCode::UP021],
|
||||
CheckCodePrefix::UP022 => vec![CheckCode::UP022],
|
||||
CheckCodePrefix::UP023 => vec![CheckCode::UP023],
|
||||
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
|
||||
CheckCodePrefix::W2 => vec![CheckCode::W292],
|
||||
CheckCodePrefix::W29 => vec![CheckCode::W292],
|
||||
@@ -2464,6 +2803,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::A001 => SuffixLength::Three,
|
||||
CheckCodePrefix::A002 => SuffixLength::Three,
|
||||
CheckCodePrefix::A003 => SuffixLength::Three,
|
||||
CheckCodePrefix::ALL => SuffixLength::None,
|
||||
CheckCodePrefix::ANN => SuffixLength::Zero,
|
||||
CheckCodePrefix::ANN0 => SuffixLength::One,
|
||||
CheckCodePrefix::ANN00 => SuffixLength::Two,
|
||||
@@ -2892,6 +3232,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::RUF001 => SuffixLength::Three,
|
||||
CheckCodePrefix::RUF002 => SuffixLength::Three,
|
||||
CheckCodePrefix::RUF003 => SuffixLength::Three,
|
||||
CheckCodePrefix::RUF004 => SuffixLength::Three,
|
||||
CheckCodePrefix::RUF1 => SuffixLength::One,
|
||||
CheckCodePrefix::RUF10 => SuffixLength::Two,
|
||||
CheckCodePrefix::RUF100 => SuffixLength::Three,
|
||||
@@ -2963,6 +3304,11 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::UP017 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP018 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP019 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP02 => SuffixLength::Two,
|
||||
CheckCodePrefix::UP020 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP021 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP022 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP023 => SuffixLength::Three,
|
||||
CheckCodePrefix::W => SuffixLength::Zero,
|
||||
CheckCodePrefix::W2 => SuffixLength::One,
|
||||
CheckCodePrefix::W29 => SuffixLength::Two,
|
||||
@@ -2993,6 +3339,7 @@ impl CheckCodePrefix {
|
||||
|
||||
pub const CATEGORIES: &[CheckCodePrefix] = &[
|
||||
CheckCodePrefix::A,
|
||||
CheckCodePrefix::ALL,
|
||||
CheckCodePrefix::ANN,
|
||||
CheckCodePrefix::ARG,
|
||||
CheckCodePrefix::B,
|
||||
|
||||
@@ -50,6 +50,10 @@ pub struct Cli {
|
||||
fix_only: bool,
|
||||
#[clap(long, overrides_with("fix_only"), hide = true)]
|
||||
no_fix_only: bool,
|
||||
/// Avoid writing any fixed files back; instead, output a diff for each
|
||||
/// changed file to stdout.
|
||||
#[arg(long)]
|
||||
pub diff: bool,
|
||||
/// Disable cache reads.
|
||||
#[arg(short, long)]
|
||||
pub no_cache: bool,
|
||||
@@ -154,6 +158,7 @@ impl Cli {
|
||||
add_noqa: self.add_noqa,
|
||||
autoformat: self.autoformat,
|
||||
config: self.config,
|
||||
diff: self.diff,
|
||||
exit_zero: self.exit_zero,
|
||||
explain: self.explain,
|
||||
files: self.files,
|
||||
@@ -213,6 +218,7 @@ pub struct Arguments {
|
||||
pub add_noqa: bool,
|
||||
pub autoformat: bool,
|
||||
pub config: Option<PathBuf>,
|
||||
pub diff: bool,
|
||||
pub exit_zero: bool,
|
||||
pub explain: Option<CheckCode>,
|
||||
pub files: Vec<PathBuf>,
|
||||
|
||||
@@ -332,6 +332,9 @@ pub fn explain(code: &CheckCode, format: &SerializationFormat) -> Result<()> {
|
||||
SerializationFormat::Github => {
|
||||
bail!("`--explain` does not support GitHub format")
|
||||
}
|
||||
SerializationFormat::Gitlab => {
|
||||
bail!("`--explain` does not support GitLab format")
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ use rustpython_ast::Stmt;
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::Tok;
|
||||
|
||||
use crate::ast::helpers;
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
@@ -17,13 +16,10 @@ pub fn add_return_none_annotation(locator: &SourceCodeLocator, stmt: &Stmt) -> R
|
||||
let mut seen_lpar = false;
|
||||
let mut seen_rpar = false;
|
||||
let mut count: usize = 0;
|
||||
for (start, tok, ..) in lexer::make_tokenizer(&contents).flatten() {
|
||||
for (start, tok, ..) in lexer::make_tokenizer_located(&contents, range.location).flatten() {
|
||||
if seen_lpar && seen_rpar {
|
||||
if matches!(tok, Tok::Colon) {
|
||||
return Ok(Fix::insertion(
|
||||
" -> None".to_string(),
|
||||
helpers::to_absolute(start, range.location),
|
||||
));
|
||||
return Ok(Fix::insertion(" -> None".to_string(), start));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ 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::source_code_generator::SourceCodeGenerator;
|
||||
|
||||
fn assertion_error(msg: Option<&Expr>) -> Stmt {
|
||||
Stmt::new(
|
||||
@@ -47,7 +47,8 @@ pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option
|
||||
|
||||
let mut check = Check::new(CheckKind::DoNotAssertFalse, Range::from_located(test));
|
||||
if checker.patch(check.kind.code()) {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator =
|
||||
SourceCodeGenerator::new(checker.style.indentation(), checker.style.quote());
|
||||
generator.unparse_stmt(&assertion_error(msg));
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
|
||||
fn type_pattern(elts: Vec<&Expr>) -> Expr {
|
||||
Expr::new(
|
||||
@@ -54,7 +54,8 @@ fn duplicate_handler_exceptions<'a>(
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator =
|
||||
SourceCodeGenerator::new(checker.style.indentation(), checker.style.quote());
|
||||
if unique_elts.len() == 1 {
|
||||
generator.unparse_expr(unique_elts[0], 0);
|
||||
} else {
|
||||
|
||||
@@ -4,9 +4,9 @@ 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::python::identifiers::IDENTIFIER_REGEX;
|
||||
use crate::python::keyword::KWLIST;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
|
||||
fn attribute(value: &Expr, attr: &str) -> Expr {
|
||||
Expr::new(
|
||||
@@ -46,7 +46,8 @@ pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
|
||||
|
||||
let mut check = Check::new(CheckKind::GetAttrWithConstant, Range::from_located(expr));
|
||||
if checker.patch(check.kind.code()) {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator =
|
||||
SourceCodeGenerator::new(checker.style.indentation(), checker.style.quote());
|
||||
generator.unparse_expr(&attribute(obj, value), 0);
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
|
||||
@@ -4,7 +4,7 @@ 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::source_code_generator::SourceCodeGenerator;
|
||||
|
||||
/// B013
|
||||
pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[Excepthandler]) {
|
||||
@@ -23,7 +23,8 @@ pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[E
|
||||
Range::from_located(type_),
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator =
|
||||
SourceCodeGenerator::new(checker.style.indentation(), checker.style.quote());
|
||||
generator.unparse_expr(elt, 0);
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
|
||||
@@ -6,11 +6,17 @@ 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::python::identifiers::IDENTIFIER_REGEX;
|
||||
use crate::python::keyword::KWLIST;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
|
||||
fn assignment(obj: &Expr, name: &str, value: &Expr) -> Result<String> {
|
||||
fn assignment(
|
||||
obj: &Expr,
|
||||
name: &str,
|
||||
value: &Expr,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<String> {
|
||||
let stmt = Stmt::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
@@ -28,7 +34,7 @@ fn assignment(obj: &Expr, name: &str, value: &Expr) -> Result<String> {
|
||||
type_comment: None,
|
||||
},
|
||||
);
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_stmt(&stmt);
|
||||
generator.generate().map_err(std::convert::Into::into)
|
||||
}
|
||||
@@ -63,7 +69,7 @@ pub fn setattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
|
||||
if expr == child.as_ref() {
|
||||
let mut check = Check::new(CheckKind::SetAttrWithConstant, Range::from_located(expr));
|
||||
if checker.patch(check.kind.code()) {
|
||||
match assignment(obj, name, value) {
|
||||
match assignment(obj, name, value, checker.style) {
|
||||
Ok(content) => check.amend(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
|
||||
@@ -25,7 +25,7 @@ expression: checks
|
||||
row: 10
|
||||
column: 12
|
||||
fix:
|
||||
content: "raise AssertionError('message')"
|
||||
content: "raise AssertionError(\"message\")"
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
|
||||
@@ -4,7 +4,6 @@ use rustpython_ast::Location;
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::Tok;
|
||||
|
||||
use crate::ast::helpers;
|
||||
use crate::ast::types::Range;
|
||||
use crate::SourceCodeLocator;
|
||||
|
||||
@@ -18,12 +17,10 @@ pub struct Comment<'a> {
|
||||
/// Collect all comments in an import block.
|
||||
pub fn collect_comments<'a>(range: &Range, locator: &'a SourceCodeLocator) -> Vec<Comment<'a>> {
|
||||
let contents = locator.slice_source_code_range(range);
|
||||
lexer::make_tokenizer(&contents)
|
||||
lexer::make_tokenizer_located(&contents, range.location)
|
||||
.flatten()
|
||||
.filter_map(|(start, tok, end)| {
|
||||
if matches!(tok, Tok::Comment) {
|
||||
let start = helpers::to_absolute(start, range.location);
|
||||
let end = helpers::to_absolute(end, range.location);
|
||||
Some(Comment {
|
||||
value: locator.slice_source_code_range(&Range {
|
||||
location: start,
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::cmp::Ordering;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use itertools::Either::{Left, Right};
|
||||
use itertools::Itertools;
|
||||
use ropey::RopeBuilder;
|
||||
use rustc_hash::FxHashMap;
|
||||
@@ -495,7 +496,54 @@ fn sort_imports(block: ImportBlock) -> OrderedImportBlock {
|
||||
ordered
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn force_single_line_imports<'a>(
|
||||
block: OrderedImportBlock<'a>,
|
||||
single_line_exclusions: &BTreeSet<String>,
|
||||
) -> OrderedImportBlock<'a> {
|
||||
OrderedImportBlock {
|
||||
import: block.import,
|
||||
import_from: block
|
||||
.import_from
|
||||
.into_iter()
|
||||
.flat_map(|(from_data, comment_set, trailing_comma, alias_data)| {
|
||||
if from_data
|
||||
.module
|
||||
.map_or(false, |module| single_line_exclusions.contains(module))
|
||||
{
|
||||
Left(std::iter::once((
|
||||
from_data,
|
||||
comment_set,
|
||||
trailing_comma,
|
||||
alias_data,
|
||||
)))
|
||||
} else {
|
||||
Right(
|
||||
alias_data
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(move |(index, alias_data)| {
|
||||
(
|
||||
from_data.clone(),
|
||||
if index == 0 {
|
||||
comment_set.clone()
|
||||
} else {
|
||||
CommentSet {
|
||||
atop: vec![],
|
||||
inline: vec![],
|
||||
}
|
||||
},
|
||||
TrailingComma::Absent,
|
||||
vec![alias_data],
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)]
|
||||
pub fn format_imports(
|
||||
block: &Block,
|
||||
comments: Vec<Comment>,
|
||||
@@ -509,6 +557,8 @@ pub fn format_imports(
|
||||
combine_as_imports: bool,
|
||||
force_wrap_aliases: bool,
|
||||
split_on_trailing_comma: bool,
|
||||
force_single_line: bool,
|
||||
single_line_exclusions: &BTreeSet<String>,
|
||||
) -> String {
|
||||
let trailer = &block.trailer;
|
||||
let block = annotate_imports(&block.imports, comments, locator, split_on_trailing_comma);
|
||||
@@ -531,7 +581,10 @@ pub fn format_imports(
|
||||
// Generate replacement source code.
|
||||
let mut is_first_block = true;
|
||||
for import_block in block_by_type.into_values() {
|
||||
let import_block = sort_imports(import_block);
|
||||
let mut import_block = sort_imports(import_block);
|
||||
if force_single_line {
|
||||
import_block = force_single_line_imports(import_block, single_line_exclusions);
|
||||
}
|
||||
|
||||
// Add a blank line between every section.
|
||||
if is_first_block {
|
||||
@@ -577,6 +630,7 @@ pub fn format_imports(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeSet;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
@@ -697,4 +751,28 @@ mod tests {
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("force_single_line.py"))]
|
||||
fn force_single_line(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("force_single_line_{}", path.to_string_lossy());
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/isort")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&Settings {
|
||||
isort: isort::settings::Settings {
|
||||
force_single_line: true,
|
||||
single_line_exclusions: vec!["os".to_string(), "logging.handlers".to_string()]
|
||||
.into_iter()
|
||||
.collect::<BTreeSet<_>>(),
|
||||
..isort::settings::Settings::default()
|
||||
},
|
||||
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
|
||||
..Settings::for_rule(CheckCode::I001)
|
||||
},
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,8 @@ pub fn check_imports(
|
||||
settings.isort.combine_as_imports,
|
||||
settings.isort.force_wrap_aliases,
|
||||
settings.isort.split_on_trailing_comma,
|
||||
settings.isort.force_single_line,
|
||||
&settings.isort.single_line_exclusions,
|
||||
);
|
||||
|
||||
// Expand the span the entire range, including leading and trailing space.
|
||||
|
||||
@@ -40,6 +40,22 @@ pub struct Options {
|
||||
/// enabled, every aliased `import from` will be given its own line, in
|
||||
/// which case, wrapping is not necessary.
|
||||
pub force_wrap_aliases: Option<bool>,
|
||||
#[option(
|
||||
default = r#"false"#,
|
||||
value_type = "bool",
|
||||
example = r#"force-single-line = true"#
|
||||
)]
|
||||
/// Forces all from imports to appear on their own line.
|
||||
pub force_single_line: Option<bool>,
|
||||
#[option(
|
||||
default = r#"[]"#,
|
||||
value_type = "Vec<String>",
|
||||
example = r#"
|
||||
single-line-exclusions = ["os", "json"]
|
||||
"#
|
||||
)]
|
||||
/// One or more modules to exclude from the single line rule.
|
||||
pub single_line_exclusions: Option<Vec<String>>,
|
||||
#[option(
|
||||
default = r#"false"#,
|
||||
value_type = "bool",
|
||||
@@ -95,10 +111,13 @@ pub struct Options {
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Settings {
|
||||
pub combine_as_imports: bool,
|
||||
pub force_wrap_aliases: bool,
|
||||
pub split_on_trailing_comma: bool,
|
||||
pub force_single_line: bool,
|
||||
pub single_line_exclusions: BTreeSet<String>,
|
||||
pub known_first_party: BTreeSet<String>,
|
||||
pub known_third_party: BTreeSet<String>,
|
||||
pub extra_standard_library: BTreeSet<String>,
|
||||
@@ -110,6 +129,10 @@ impl Settings {
|
||||
combine_as_imports: options.combine_as_imports.unwrap_or(false),
|
||||
force_wrap_aliases: options.force_wrap_aliases.unwrap_or(false),
|
||||
split_on_trailing_comma: options.split_on_trailing_comma.unwrap_or(true),
|
||||
force_single_line: options.force_single_line.unwrap_or(false),
|
||||
single_line_exclusions: BTreeSet::from_iter(
|
||||
options.single_line_exclusions.unwrap_or_default(),
|
||||
),
|
||||
known_first_party: BTreeSet::from_iter(options.known_first_party.unwrap_or_default()),
|
||||
known_third_party: BTreeSet::from_iter(options.known_third_party.unwrap_or_default()),
|
||||
extra_standard_library: BTreeSet::from_iter(
|
||||
@@ -125,6 +148,8 @@ impl Default for Settings {
|
||||
combine_as_imports: false,
|
||||
force_wrap_aliases: false,
|
||||
split_on_trailing_comma: true,
|
||||
force_single_line: false,
|
||||
single_line_exclusions: BTreeSet::new(),
|
||||
known_first_party: BTreeSet::new(),
|
||||
known_third_party: BTreeSet::new(),
|
||||
extra_standard_library: BTreeSet::new(),
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 19
|
||||
column: 0
|
||||
fix:
|
||||
content: "import math\nimport sys\nfrom logging.handlers import FileHandler, StreamHandler\nfrom os import path, uname\n\n# comment 5\nfrom bar import a # comment 6\nfrom bar import b # comment 7\nfrom foo import bar # comment 3\nfrom foo2 import bar2 # comment 4\n\n# comment 1\n# comment 2\nfrom third_party import lib1\nfrom third_party import lib2\nfrom third_party import lib3\nfrom third_party import lib4\nfrom third_party import lib5\nfrom third_party import lib6\nfrom third_party import lib7\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 19
|
||||
column: 0
|
||||
|
||||
@@ -16,7 +16,7 @@ impl Default for TrailingComma {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq, Clone)]
|
||||
pub struct ImportFromData<'a> {
|
||||
pub module: Option<&'a String>,
|
||||
pub level: Option<&'a usize>,
|
||||
@@ -28,7 +28,7 @@ pub struct AliasData<'a> {
|
||||
pub asname: Option<&'a String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct CommentSet<'a> {
|
||||
pub atop: Vec<Cow<'a, str>>,
|
||||
pub inline: Vec<Cow<'a, str>>,
|
||||
|
||||
@@ -24,7 +24,6 @@ mod checkers;
|
||||
pub mod checks;
|
||||
pub mod checks_gen;
|
||||
pub mod cli;
|
||||
pub mod code_gen;
|
||||
mod cst;
|
||||
mod directives;
|
||||
mod docstrings;
|
||||
@@ -60,7 +59,7 @@ mod pandas_vet;
|
||||
pub mod pep8_naming;
|
||||
pub mod printer;
|
||||
mod pycodestyle;
|
||||
mod pydocstyle;
|
||||
pub mod pydocstyle;
|
||||
mod pyflakes;
|
||||
mod pygrep_hooks;
|
||||
mod pylint;
|
||||
@@ -70,8 +69,10 @@ pub mod resolver;
|
||||
mod ruff;
|
||||
mod rustpython_helpers;
|
||||
pub mod settings;
|
||||
pub mod source_code_generator;
|
||||
pub mod source_code_locator;
|
||||
mod vendored;
|
||||
pub mod source_code_style;
|
||||
mod vendor;
|
||||
pub mod visibility;
|
||||
|
||||
cfg_if! {
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::rustpython_helpers::tokenize;
|
||||
use crate::settings::configuration::Configuration;
|
||||
use crate::settings::{flags, pyproject, Settings};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
use crate::{directives, packages, resolver};
|
||||
|
||||
/// Load the relevant `Settings` for a given `Path`.
|
||||
@@ -38,9 +39,12 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(contents);
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = SourceCodeStyleDetector::from_contents(contents, &locator);
|
||||
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
@@ -55,6 +59,7 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&directives,
|
||||
&settings,
|
||||
autofix.into(),
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::settings::configuration::Configuration;
|
||||
use crate::settings::options::Options;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const TYPES: &'static str = r#"
|
||||
@@ -69,9 +70,12 @@ pub fn check(contents: &str, options: JsValue) -> Result<JsValue, JsValue> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(contents);
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = SourceCodeStyleDetector::from_contents(contents, &locator);
|
||||
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives = directives::extract_directives(&tokens, &locator, directives::Flags::empty());
|
||||
|
||||
@@ -82,6 +86,7 @@ pub fn check(contents: &str, options: JsValue) -> Result<JsValue, JsValue> {
|
||||
contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&directives,
|
||||
&settings,
|
||||
flags::Autofix::Enabled,
|
||||
|
||||
231
src/linter.rs
231
src/linter.rs
@@ -5,8 +5,10 @@ use std::ops::AddAssign;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use log::debug;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use similar::TextDiff;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::fixer;
|
||||
@@ -17,14 +19,18 @@ use crate::checkers::lines::check_lines;
|
||||
use crate::checkers::noqa::check_noqa;
|
||||
use crate::checkers::tokens::check_tokens;
|
||||
use crate::checks::{Check, CheckCode, CheckKind, LintSource};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::directives::Directives;
|
||||
use crate::message::{Message, Source};
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
use crate::{cache, directives, fs, rustpython_helpers};
|
||||
|
||||
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
||||
const CARGO_PKG_REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Diagnostics {
|
||||
pub messages: Vec<Message>,
|
||||
@@ -53,6 +59,7 @@ pub(crate) fn check_path(
|
||||
contents: &str,
|
||||
tokens: Vec<LexResult>,
|
||||
locator: &SourceCodeLocator,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
directives: &Directives,
|
||||
settings: &Settings,
|
||||
autofix: flags::Autofix,
|
||||
@@ -89,6 +96,7 @@ pub(crate) fn check_path(
|
||||
checks.extend(check_ast(
|
||||
&python_ast,
|
||||
locator,
|
||||
stylist,
|
||||
&directives.noqa_line_for,
|
||||
settings,
|
||||
autofix,
|
||||
@@ -180,26 +188,47 @@ pub fn lint_path(
|
||||
// Validate the `Settings` and return any errors.
|
||||
settings.validate()?;
|
||||
|
||||
let metadata = path.metadata()?;
|
||||
|
||||
// Check the cache.
|
||||
if let Some(messages) = cache::get(path, &metadata, settings, autofix, cache) {
|
||||
debug!("Cache hit for: {}", path.to_string_lossy());
|
||||
return Ok(Diagnostics::new(messages));
|
||||
}
|
||||
let metadata = if matches!(cache, flags::Cache::Enabled) {
|
||||
let metadata = path.metadata()?;
|
||||
if let Some(messages) = cache::get(path, &metadata, settings, autofix.into()) {
|
||||
debug!("Cache hit for: {}", path.to_string_lossy());
|
||||
return Ok(Diagnostics::new(messages));
|
||||
}
|
||||
Some(metadata)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Read the file from disk.
|
||||
let contents = fs::read_file(path)?;
|
||||
|
||||
// Lint the file.
|
||||
let (contents, fixed, messages) = lint(contents, path, package, settings, autofix)?;
|
||||
let (messages, fixed) = if matches!(autofix, fixer::Mode::Apply | fixer::Mode::Diff) {
|
||||
let (transformed, fixed, messages) = lint_fix(&contents, path, package, settings)?;
|
||||
if fixed > 0 {
|
||||
if matches!(autofix, fixer::Mode::Apply) {
|
||||
write(path, transformed)?;
|
||||
} else if matches!(autofix, fixer::Mode::Diff) {
|
||||
let mut stdout = io::stdout().lock();
|
||||
TextDiff::from_lines(&contents, &transformed)
|
||||
.unified_diff()
|
||||
.header(&fs::relativize_path(path), &fs::relativize_path(path))
|
||||
.to_writer(&mut stdout)?;
|
||||
stdout.write_all(b"\n")?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
}
|
||||
(messages, fixed)
|
||||
} else {
|
||||
let messages = lint_only(&contents, path, package, settings, autofix.into())?;
|
||||
let fixed = 0;
|
||||
(messages, fixed)
|
||||
};
|
||||
|
||||
// Re-populate the cache.
|
||||
cache::set(path, &metadata, settings, autofix, &messages, cache);
|
||||
|
||||
// If we applied any fixes, write the contents back to disk.
|
||||
if fixed > 0 {
|
||||
write(path, contents)?;
|
||||
if let Some(metadata) = metadata {
|
||||
cache::set(path, &metadata, settings, autofix.into(), &messages);
|
||||
}
|
||||
|
||||
Ok(Diagnostics { messages, fixed })
|
||||
@@ -216,9 +245,12 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
|
||||
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
@@ -233,6 +265,7 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&directives,
|
||||
settings,
|
||||
flags::Autofix::Disabled,
|
||||
@@ -259,9 +292,15 @@ pub fn autoformat_path(path: &Path, settings: &Settings) -> Result<()> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
|
||||
|
||||
// Generate the AST.
|
||||
let python_ast = rustpython_helpers::parse_program_tokens(tokens, "<filename>")?;
|
||||
let mut generator = SourceGenerator::default();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_suite(&python_ast);
|
||||
write(path, generator.generate()?)?;
|
||||
|
||||
@@ -273,40 +312,121 @@ pub fn autoformat_path(path: &Path, settings: &Settings) -> Result<()> {
|
||||
pub fn lint_stdin(
|
||||
path: Option<&Path>,
|
||||
package: Option<&Path>,
|
||||
stdin: &str,
|
||||
contents: &str,
|
||||
settings: &Settings,
|
||||
autofix: fixer::Mode,
|
||||
) -> Result<Diagnostics> {
|
||||
// Validate the `Settings` and return any errors.
|
||||
settings.validate()?;
|
||||
|
||||
// Read the file from disk.
|
||||
let contents = stdin.to_string();
|
||||
// Lint the inputs.
|
||||
let (messages, fixed) = if matches!(autofix, fixer::Mode::Apply | fixer::Mode::Diff) {
|
||||
let (transformed, fixed, messages) = lint_fix(
|
||||
contents,
|
||||
path.unwrap_or_else(|| Path::new("-")),
|
||||
package,
|
||||
settings,
|
||||
)?;
|
||||
|
||||
// Lint the file.
|
||||
let (contents, fixed, messages) = lint(
|
||||
contents,
|
||||
path.unwrap_or_else(|| Path::new("-")),
|
||||
package,
|
||||
settings,
|
||||
autofix,
|
||||
)?;
|
||||
if matches!(autofix, fixer::Mode::Apply) {
|
||||
// Write the contents to stdout, regardless of whether any errors were fixed.
|
||||
io::stdout().write_all(transformed.as_bytes())?;
|
||||
} else if matches!(autofix, fixer::Mode::Diff) {
|
||||
// But only write a diff if it's non-empty.
|
||||
if fixed > 0 {
|
||||
let text_diff = TextDiff::from_lines(contents, &transformed);
|
||||
let mut unified_diff = text_diff.unified_diff();
|
||||
if let Some(path) = path {
|
||||
unified_diff.header(&fs::relativize_path(path), &fs::relativize_path(path));
|
||||
}
|
||||
|
||||
// Write the fixed contents to stdout.
|
||||
if matches!(autofix, fixer::Mode::Apply) {
|
||||
io::stdout().write_all(contents.as_bytes())?;
|
||||
}
|
||||
let mut stdout = io::stdout().lock();
|
||||
unified_diff.to_writer(&mut stdout)?;
|
||||
stdout.write_all(b"\n")?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
}
|
||||
|
||||
(messages, fixed)
|
||||
} else {
|
||||
let messages = lint_only(
|
||||
contents,
|
||||
path.unwrap_or_else(|| Path::new("-")),
|
||||
package,
|
||||
settings,
|
||||
autofix.into(),
|
||||
)?;
|
||||
let fixed = 0;
|
||||
(messages, fixed)
|
||||
};
|
||||
|
||||
Ok(Diagnostics { messages, fixed })
|
||||
}
|
||||
|
||||
fn lint(
|
||||
mut contents: String,
|
||||
/// Generate a list of `Check` violations (optionally including any autofix
|
||||
/// patches) from source code content.
|
||||
fn lint_only(
|
||||
contents: &str,
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
settings: &Settings,
|
||||
autofix: flags::Autofix,
|
||||
) -> Result<Vec<Message>> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(contents);
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = SourceCodeStyleDetector::from_contents(contents, &locator);
|
||||
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
directives::Flags::from_settings(settings),
|
||||
);
|
||||
|
||||
// Generate checks.
|
||||
let checks = check_path(
|
||||
path,
|
||||
package,
|
||||
contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&directives,
|
||||
settings,
|
||||
autofix,
|
||||
flags::Noqa::Enabled,
|
||||
)?;
|
||||
|
||||
// Convert from checks to messages.
|
||||
let path_lossy = path.to_string_lossy();
|
||||
Ok(checks
|
||||
.into_iter()
|
||||
.map(|check| {
|
||||
let source = if settings.show_source {
|
||||
Some(Source::from_check(&check, &locator))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Message::from_check(check, path_lossy.to_string(), source)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Generate a list of `Check` violations from source code content, iteratively
|
||||
/// autofixing any violations until stable.
|
||||
fn lint_fix(
|
||||
contents: &str,
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
settings: &Settings,
|
||||
autofix: fixer::Mode,
|
||||
) -> Result<(String, usize, Vec<Message>)> {
|
||||
let mut contents = contents.to_string();
|
||||
|
||||
// Track the number of fixed errors across iterations.
|
||||
let mut fixed = 0;
|
||||
|
||||
@@ -314,13 +434,16 @@ fn lint(
|
||||
let mut iterations = 0;
|
||||
|
||||
// Continuously autofix until the source code stabilizes.
|
||||
let messages = loop {
|
||||
loop {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
|
||||
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
@@ -335,15 +458,16 @@ fn lint(
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&directives,
|
||||
settings,
|
||||
autofix.into(),
|
||||
flags::Autofix::Enabled,
|
||||
flags::Noqa::Enabled,
|
||||
)?;
|
||||
|
||||
// Apply autofix.
|
||||
if matches!(autofix, fixer::Mode::Apply) && iterations < MAX_ITERATIONS {
|
||||
if let Some((fixed_contents, applied)) = fix_file(&checks, &locator) {
|
||||
if let Some((fixed_contents, applied)) = fix_file(&checks, &locator) {
|
||||
if iterations < MAX_ITERATIONS {
|
||||
// Count the number of fixed errors.
|
||||
fixed += applied;
|
||||
|
||||
@@ -356,11 +480,29 @@ fn lint(
|
||||
// Re-run the linter pass (by avoiding the break).
|
||||
continue;
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"
|
||||
{}: Failed to converge after {} iterations.
|
||||
|
||||
This likely indicates a bug in `{}`. If you could open an issue at:
|
||||
|
||||
{}/issues
|
||||
|
||||
quoting the contents of `{}`, along with the `pyproject.toml` settings and executed command, we'd \
|
||||
be very appreciative!
|
||||
",
|
||||
"warning".yellow().bold(),
|
||||
MAX_ITERATIONS,
|
||||
CARGO_PKG_NAME,
|
||||
CARGO_PKG_REPOSITORY,
|
||||
fs::relativize_path(path),
|
||||
);
|
||||
}
|
||||
|
||||
// Convert to messages.
|
||||
let filename = path.to_string_lossy().to_string();
|
||||
break checks
|
||||
let path_lossy = path.to_string_lossy();
|
||||
let messages = checks
|
||||
.into_iter()
|
||||
.map(|check| {
|
||||
let source = if settings.show_source {
|
||||
@@ -368,12 +510,11 @@ fn lint(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Message::from_check(check, filename.clone(), source)
|
||||
Message::from_check(check, path_lossy.to_string(), source)
|
||||
})
|
||||
.collect();
|
||||
};
|
||||
|
||||
Ok((contents, fixed, messages))
|
||||
return Ok((contents, fixed, messages));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -381,6 +522,7 @@ pub fn test_path(path: &Path, settings: &Settings) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
@@ -392,6 +534,7 @@ pub fn test_path(path: &Path, settings: &Settings) -> Result<Vec<Check>> {
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&directives,
|
||||
settings,
|
||||
flags::Autofix::Enabled,
|
||||
|
||||
@@ -123,19 +123,6 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
|
||||
(settings.fix, settings.fix_only, settings.format)
|
||||
}
|
||||
};
|
||||
let autofix = if fix || fix_only {
|
||||
fixer::Mode::Apply
|
||||
} else if matches!(format, SerializationFormat::Json) {
|
||||
fixer::Mode::Generate
|
||||
} else {
|
||||
fixer::Mode::None
|
||||
};
|
||||
let violations = if fix_only {
|
||||
Violations::Hide
|
||||
} else {
|
||||
Violations::Show
|
||||
};
|
||||
let cache = !cli.no_cache;
|
||||
|
||||
if let Some(code) = cli.explain {
|
||||
commands::explain(&code, &format)?;
|
||||
@@ -150,9 +137,35 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
// Autofix rules are as follows:
|
||||
// - If `--fix` or `--fix-only` is set, always apply fixes to the filesystem (or
|
||||
// print them to stdout, if we're reading from stdin).
|
||||
// - Otherwise, if `--format json` is set, generate the fixes (so we print them
|
||||
// out as part of the JSON payload), but don't write them to disk.
|
||||
// - If `--diff` or `--fix-only` are set, don't print any violations (only
|
||||
// fixes).
|
||||
// TODO(charlie): Consider adding ESLint's `--fix-dry-run`, which would generate
|
||||
// but not apply fixes. That would allow us to avoid special-casing JSON
|
||||
// here.
|
||||
let autofix = if cli.diff {
|
||||
fixer::Mode::Diff
|
||||
} else if fix || fix_only {
|
||||
fixer::Mode::Apply
|
||||
} else if matches!(format, SerializationFormat::Json) {
|
||||
fixer::Mode::Generate
|
||||
} else {
|
||||
fixer::Mode::None
|
||||
};
|
||||
let violations = if cli.diff || fix_only {
|
||||
Violations::Hide
|
||||
} else {
|
||||
Violations::Show
|
||||
};
|
||||
let cache = !cli.no_cache;
|
||||
|
||||
let printer = Printer::new(&format, &log_level, &autofix, &violations);
|
||||
if cli.watch {
|
||||
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if !matches!(autofix, fixer::Mode::None) {
|
||||
eprintln!("Warning: --fix is not enabled in watch mode.");
|
||||
}
|
||||
if cli.add_noqa {
|
||||
@@ -251,7 +264,7 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
|
||||
// Always try to print violations (the printer itself may suppress output),
|
||||
// unless we're writing fixes via stdin (in which case, the transformed
|
||||
// source code goes to stdout).
|
||||
if !(is_stdin && matches!(autofix, fixer::Mode::Apply)) {
|
||||
if !(is_stdin && matches!(autofix, fixer::Mode::Apply | fixer::Mode::Diff)) {
|
||||
printer.write_once(&diagnostics)?;
|
||||
}
|
||||
|
||||
@@ -261,8 +274,14 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
|
||||
drop(updates::check_for_updates());
|
||||
}
|
||||
|
||||
if !diagnostics.messages.is_empty() && !cli.exit_zero && !fix_only {
|
||||
return Ok(ExitCode::FAILURE);
|
||||
if !cli.exit_zero {
|
||||
if cli.diff || fix_only {
|
||||
if diagnostics.fixed > 0 {
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
} else if !diagnostics.messages.is_empty() {
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ mod tests {
|
||||
use crate::linter::check_path;
|
||||
use crate::settings::flags;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
use crate::{directives, rustpython_helpers, settings};
|
||||
|
||||
fn check_code(contents: &str, expected: &[CheckCode]) -> Result<()> {
|
||||
@@ -21,6 +22,7 @@ mod tests {
|
||||
let settings = settings::Settings::for_rules(CheckCodePrefix::PD.codes());
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
@@ -32,6 +34,7 @@ mod tests {
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&directives,
|
||||
&settings,
|
||||
flags::Autofix::Enabled,
|
||||
|
||||
@@ -8,6 +8,7 @@ use colored::Colorize;
|
||||
use itertools::iterate;
|
||||
use rustpython_parser::ast::Location;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::autofix::{fixer, Fix};
|
||||
use crate::checks::CheckCode;
|
||||
@@ -89,7 +90,11 @@ impl<'a> Printer<'a> {
|
||||
Violations::Hide => {
|
||||
let fixed = diagnostics.fixed;
|
||||
if fixed > 0 {
|
||||
println!("Fixed {fixed} error(s).");
|
||||
if matches!(self.autofix, fixer::Mode::Apply) {
|
||||
println!("Fixed {fixed} error(s).");
|
||||
} else if matches!(self.autofix, fixer::Mode::Diff) {
|
||||
println!("Would fix {fixed} error(s).");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,17 +139,8 @@ impl<'a> Printer<'a> {
|
||||
SerializationFormat::Junit => {
|
||||
use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite};
|
||||
|
||||
// Group by filename.
|
||||
let mut grouped_messages = BTreeMap::default();
|
||||
for message in &diagnostics.messages {
|
||||
grouped_messages
|
||||
.entry(&message.filename)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(message);
|
||||
}
|
||||
|
||||
let mut report = Report::new("ruff");
|
||||
for (filename, messages) in grouped_messages {
|
||||
for (filename, messages) in group_messages_by_filename(&diagnostics.messages) {
|
||||
let mut test_suite = TestSuite::new(filename);
|
||||
test_suite
|
||||
.extra
|
||||
@@ -183,16 +179,7 @@ impl<'a> Printer<'a> {
|
||||
self.post_text(diagnostics);
|
||||
}
|
||||
SerializationFormat::Grouped => {
|
||||
// Group by filename.
|
||||
let mut grouped_messages = BTreeMap::default();
|
||||
for message in &diagnostics.messages {
|
||||
grouped_messages
|
||||
.entry(&message.filename)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(message);
|
||||
}
|
||||
|
||||
for (filename, messages) in grouped_messages {
|
||||
for (filename, messages) in group_messages_by_filename(&diagnostics.messages) {
|
||||
// Compute the maximum number of digits in the row and column, for messages in
|
||||
// this file.
|
||||
let row_length = num_digits(
|
||||
@@ -239,6 +226,34 @@ impl<'a> Printer<'a> {
|
||||
);
|
||||
});
|
||||
}
|
||||
SerializationFormat::Gitlab => {
|
||||
// Generate JSON with errors in GitLab CI format
|
||||
// https://docs.gitlab.com/ee/ci/testing/code_quality.html#implementing-a-custom-tool
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(
|
||||
&diagnostics
|
||||
.messages
|
||||
.iter()
|
||||
.map(|message| {
|
||||
json!({
|
||||
"description": format!("({}) {}", message.kind.code(), message.kind.body()),
|
||||
"severity": "major",
|
||||
"fingerprint": message.kind.code(),
|
||||
"location": {
|
||||
"path": relativize_path(Path::new(&message.filename)),
|
||||
"lines": {
|
||||
"begin": message.location.row(),
|
||||
"end": message.end_location.row()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
)?
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -275,6 +290,17 @@ impl<'a> Printer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn group_messages_by_filename(messages: &Vec<Message>) -> BTreeMap<&String, Vec<&Message>> {
|
||||
let mut grouped_messages = BTreeMap::default();
|
||||
for message in messages {
|
||||
grouped_messages
|
||||
.entry(&message.filename)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(message);
|
||||
}
|
||||
grouped_messages
|
||||
}
|
||||
|
||||
fn num_digits(n: usize) -> usize {
|
||||
iterate(n, |&n| n / 10)
|
||||
.take_while(|&n| n > 0)
|
||||
|
||||
@@ -12,9 +12,15 @@ use crate::ast::whitespace::leading_space;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind, RejectedCmpop};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
|
||||
fn compare(left: &Expr, ops: &[Cmpop], comparators: &[Expr]) -> Option<String> {
|
||||
fn compare(
|
||||
left: &Expr,
|
||||
ops: &[Cmpop],
|
||||
comparators: &[Expr],
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Option<String> {
|
||||
let cmp = Expr::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
@@ -24,7 +30,7 @@ fn compare(left: &Expr, ops: &[Cmpop], comparators: &[Expr]) -> Option<String> {
|
||||
comparators: comparators.to_vec(),
|
||||
},
|
||||
);
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_expr(&cmp, 0);
|
||||
generator.generate().ok()
|
||||
}
|
||||
@@ -194,7 +200,7 @@ pub fn literal_comparisons(
|
||||
.map(|(idx, op)| bad_ops.get(&idx).unwrap_or(op))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if let Some(content) = compare(left, &ops, comparators) {
|
||||
if let Some(content) = compare(left, &ops, comparators, checker.style) {
|
||||
for check in &mut checks {
|
||||
check.amend(Fix::replacement(
|
||||
content.to_string(),
|
||||
@@ -233,7 +239,9 @@ pub fn not_tests(
|
||||
let mut check =
|
||||
Check::new(CheckKind::NotInTest, Range::from_located(operand));
|
||||
if checker.patch(check.kind.code()) && should_fix {
|
||||
if let Some(content) = compare(left, &[Cmpop::NotIn], comparators) {
|
||||
if let Some(content) =
|
||||
compare(left, &[Cmpop::NotIn], comparators, checker.style)
|
||||
{
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
@@ -249,7 +257,9 @@ pub fn not_tests(
|
||||
let mut check =
|
||||
Check::new(CheckKind::NotIsTest, Range::from_located(operand));
|
||||
if checker.patch(check.kind.code()) && should_fix {
|
||||
if let Some(content) = compare(left, &[Cmpop::IsNot], comparators) {
|
||||
if let Some(content) =
|
||||
compare(left, &[Cmpop::IsNot], comparators, checker.style)
|
||||
{
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
@@ -267,7 +277,12 @@ pub fn not_tests(
|
||||
}
|
||||
}
|
||||
|
||||
fn function(name: &str, args: &Arguments, body: &Expr) -> Result<String> {
|
||||
fn function(
|
||||
name: &str,
|
||||
args: &Arguments,
|
||||
body: &Expr,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<String> {
|
||||
let body = Stmt::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
@@ -287,7 +302,7 @@ fn function(name: &str, args: &Arguments, body: &Expr) -> Result<String> {
|
||||
type_comment: None,
|
||||
},
|
||||
);
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_stmt(&func);
|
||||
Ok(generator.generate()?)
|
||||
}
|
||||
@@ -301,7 +316,7 @@ pub fn do_not_assign_lambda(checker: &mut Checker, target: &Expr, value: &Expr,
|
||||
if !match_leading_content(stmt, checker.locator)
|
||||
&& !match_trailing_content(stmt, checker.locator)
|
||||
{
|
||||
match function(id, args, body) {
|
||||
match function(id, args, body, checker.style) {
|
||||
Ok(content) => {
|
||||
let first_line = checker.locator.slice_source_code_range(&Range {
|
||||
location: Location::new(stmt.location.row(), 0),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod helpers;
|
||||
pub mod plugins;
|
||||
pub mod settings;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -11,6 +12,7 @@ mod tests {
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::pydocstyle::settings::{Convention, Settings};
|
||||
use crate::settings;
|
||||
|
||||
#[test_case(CheckCode::D100, Path::new("D.py"); "D100")]
|
||||
@@ -72,4 +74,54 @@ mod tests {
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d417_unspecified() -> Result<()> {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/pydocstyle/D417.py"),
|
||||
&settings::Settings {
|
||||
// When inferring the convention, we'll see a few false negatives.
|
||||
// See: https://github.com/PyCQA/pydocstyle/issues/459.
|
||||
pydocstyle: Settings { convention: None },
|
||||
..settings::Settings::for_rule(CheckCode::D417)
|
||||
},
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d417_google() -> Result<()> {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/pydocstyle/D417.py"),
|
||||
&settings::Settings {
|
||||
// With explicit Google convention, we should flag every function.
|
||||
pydocstyle: Settings {
|
||||
convention: Some(Convention::Google),
|
||||
},
|
||||
..settings::Settings::for_rule(CheckCode::D417)
|
||||
},
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d417_numpy() -> Result<()> {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/pydocstyle/D417.py"),
|
||||
&settings::Settings {
|
||||
// With explicit Google convention, we shouldn't flag anything.
|
||||
pydocstyle: Settings {
|
||||
convention: Some(Convention::Numpy),
|
||||
},
|
||||
..settings::Settings::for_rule(CheckCode::D417)
|
||||
},
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::docstrings::definition::{Definition, DefinitionKind, Docstring};
|
||||
use crate::docstrings::sections::{section_contexts, SectionContext};
|
||||
use crate::docstrings::styles::SectionStyle;
|
||||
use crate::pydocstyle::helpers::{leading_quote, logical_line};
|
||||
use crate::pydocstyle::settings::Convention;
|
||||
use crate::visibility::{is_init, is_magic, is_overload, is_override, is_staticmethod, Visibility};
|
||||
|
||||
/// D100, D101, D102, D103, D104, D105, D106, D107
|
||||
@@ -855,7 +856,7 @@ pub fn not_empty(checker: &mut Checker, docstring: &Docstring) -> bool {
|
||||
|
||||
/// D212, D214, D215, D405, D406, D407, D408, D409, D410, D411, D412, D413,
|
||||
/// D414, D416, D417
|
||||
pub fn sections(checker: &mut Checker, docstring: &Docstring) {
|
||||
pub fn sections(checker: &mut Checker, docstring: &Docstring, convention: Option<&Convention>) {
|
||||
let body = docstring.body;
|
||||
|
||||
let lines: Vec<&str> = LinesWithTrailingNewline::from(body).collect();
|
||||
@@ -863,17 +864,31 @@ pub fn sections(checker: &mut Checker, docstring: &Docstring) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First, interpret as NumPy-style sections.
|
||||
let mut found_numpy_section = false;
|
||||
for context in §ion_contexts(&lines, &SectionStyle::NumPy) {
|
||||
found_numpy_section = true;
|
||||
numpy_section(checker, docstring, context);
|
||||
}
|
||||
match convention {
|
||||
Some(Convention::Google) => {
|
||||
for context in §ion_contexts(&lines, &SectionStyle::Google) {
|
||||
google_section(checker, docstring, context);
|
||||
}
|
||||
}
|
||||
Some(Convention::Numpy) => {
|
||||
for context in §ion_contexts(&lines, &SectionStyle::NumPy) {
|
||||
numpy_section(checker, docstring, context);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// First, interpret as NumPy-style sections.
|
||||
let mut found_numpy_section = false;
|
||||
for context in §ion_contexts(&lines, &SectionStyle::NumPy) {
|
||||
found_numpy_section = true;
|
||||
numpy_section(checker, docstring, context);
|
||||
}
|
||||
|
||||
// If no such sections were identified, interpret as Google-style sections.
|
||||
if !found_numpy_section {
|
||||
for context in §ion_contexts(&lines, &SectionStyle::Google) {
|
||||
google_section(checker, docstring, context);
|
||||
// If no such sections were identified, interpret as Google-style sections.
|
||||
if !found_numpy_section {
|
||||
for context in §ion_contexts(&lines, &SectionStyle::Google) {
|
||||
google_section(checker, docstring, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
src/pydocstyle/settings.rs
Normal file
44
src/pydocstyle/settings.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
//! Settings for the `pydocstyle` plugin.
|
||||
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub enum Convention {
|
||||
Google,
|
||||
Numpy,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
|
||||
)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case", rename = "Pydocstyle")]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
default = r#""convention""#,
|
||||
value_type = "Convention",
|
||||
example = r#"
|
||||
# Use Google-style docstrings.
|
||||
convention = "google"
|
||||
"#
|
||||
)]
|
||||
/// Whether to use Google-style or Numpy-style conventions when detecting
|
||||
/// docstring sections. By default, conventions will be inferred from
|
||||
/// the available sections.
|
||||
pub convention: Option<Convention>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Hash)]
|
||||
pub struct Settings {
|
||||
pub convention: Option<Convention>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn from_options(options: Options) -> Self {
|
||||
Self {
|
||||
convention: options.convention,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
source: src/pydocstyle/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
DocumentAllArguments:
|
||||
- y
|
||||
- z
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
DocumentAllArguments:
|
||||
- x
|
||||
- y
|
||||
- z
|
||||
location:
|
||||
row: 14
|
||||
column: 0
|
||||
end_location:
|
||||
row: 24
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
DocumentAllArguments:
|
||||
- x
|
||||
- y
|
||||
- z
|
||||
location:
|
||||
row: 27
|
||||
column: 0
|
||||
end_location:
|
||||
row: 36
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
DocumentAllArguments:
|
||||
- y
|
||||
- z
|
||||
location:
|
||||
row: 39
|
||||
column: 0
|
||||
end_location:
|
||||
row: 49
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
DocumentAllArguments:
|
||||
- y
|
||||
location:
|
||||
row: 52
|
||||
column: 0
|
||||
end_location:
|
||||
row: 62
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
DocumentAllArguments:
|
||||
- y
|
||||
location:
|
||||
row: 65
|
||||
column: 0
|
||||
end_location:
|
||||
row: 74
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
DocumentAllArguments:
|
||||
- y
|
||||
location:
|
||||
row: 77
|
||||
column: 0
|
||||
end_location:
|
||||
row: 84
|
||||
column: 12
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/pydocstyle/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
source: src/pydocstyle/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
DocumentAllArguments:
|
||||
- x
|
||||
- y
|
||||
- z
|
||||
location:
|
||||
row: 27
|
||||
column: 0
|
||||
end_location:
|
||||
row: 36
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
DocumentAllArguments:
|
||||
- y
|
||||
location:
|
||||
row: 65
|
||||
column: 0
|
||||
end_location:
|
||||
row: 74
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
DocumentAllArguments:
|
||||
- y
|
||||
location:
|
||||
row: 77
|
||||
column: 0
|
||||
end_location:
|
||||
row: 84
|
||||
column: 12
|
||||
fix: ~
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::str::FromStr;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::vendored::cformat::{
|
||||
use crate::vendor::cformat::{
|
||||
CFormatError, CFormatPart, CFormatQuantity, CFormatSpec, CFormatString,
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::fmt;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::vendored::format::{
|
||||
use crate::vendor::format::{
|
||||
FieldName, FieldType, FormatParseError, FormatPart, FormatString, FromTemplate,
|
||||
};
|
||||
|
||||
@@ -82,7 +82,7 @@ impl TryFrom<&str> for FormatSummary {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::vendored::format::FromTemplate;
|
||||
use crate::vendor::format::FromTemplate;
|
||||
|
||||
#[test]
|
||||
fn test_format_summary() {
|
||||
|
||||
@@ -20,6 +20,7 @@ mod tests {
|
||||
use crate::linter::{check_path, test_path};
|
||||
use crate::settings::flags;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
use crate::{directives, rustpython_helpers, settings};
|
||||
|
||||
#[test_case(CheckCode::F401, Path::new("F401_0.py"); "F401_0")]
|
||||
@@ -171,6 +172,7 @@ mod tests {
|
||||
let settings = settings::Settings::for_rules(CheckCodePrefix::F.codes());
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
@@ -182,6 +184,7 @@ mod tests {
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&directives,
|
||||
&settings,
|
||||
flags::Autofix::Enabled,
|
||||
|
||||
@@ -7,7 +7,6 @@ use rustpython_ast::{Expr, Keyword, Location, Stmt};
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::Tok;
|
||||
|
||||
use crate::ast::helpers;
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::{self, Fix};
|
||||
use crate::cst::matchers::match_module;
|
||||
@@ -28,10 +27,10 @@ pub fn remove_class_def_base(
|
||||
let mut fix_start = None;
|
||||
let mut fix_end = None;
|
||||
let mut count: usize = 0;
|
||||
for (start, tok, end) in lexer::make_tokenizer(&contents).flatten() {
|
||||
for (start, tok, end) in lexer::make_tokenizer_located(&contents, stmt_at).flatten() {
|
||||
if matches!(tok, Tok::Lpar) {
|
||||
if count == 0 {
|
||||
fix_start = Some(helpers::to_absolute(start, stmt_at));
|
||||
fix_start = Some(start);
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
@@ -39,7 +38,7 @@ pub fn remove_class_def_base(
|
||||
if matches!(tok, Tok::Rpar) {
|
||||
count -= 1;
|
||||
if count == 0 {
|
||||
fix_end = Some(helpers::to_absolute(end, stmt_at));
|
||||
fix_end = Some(end);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -61,8 +60,7 @@ pub fn remove_class_def_base(
|
||||
let mut fix_start: Option<Location> = None;
|
||||
let mut fix_end: Option<Location> = None;
|
||||
let mut seen_comma = false;
|
||||
for (start, tok, end) in lexer::make_tokenizer(&contents).flatten() {
|
||||
let start = helpers::to_absolute(start, stmt_at);
|
||||
for (start, tok, end) in lexer::make_tokenizer_located(&contents, stmt_at).flatten() {
|
||||
if seen_comma {
|
||||
if matches!(tok, Tok::Newline) {
|
||||
fix_end = Some(end);
|
||||
@@ -88,9 +86,7 @@ pub fn remove_class_def_base(
|
||||
// isn't a comma.
|
||||
let mut fix_start: Option<Location> = None;
|
||||
let mut fix_end: Option<Location> = None;
|
||||
for (start, tok, end) in lexer::make_tokenizer(&contents).flatten() {
|
||||
let start = helpers::to_absolute(start, stmt_at);
|
||||
let end = helpers::to_absolute(end, stmt_at);
|
||||
for (start, tok, end) in lexer::make_tokenizer_located(&contents, stmt_at).flatten() {
|
||||
if start == expr_at {
|
||||
fix_end = Some(end);
|
||||
break;
|
||||
|
||||
@@ -39,6 +39,9 @@ mod tests {
|
||||
#[test_case(CheckCode::UP016, Path::new("UP016.py"); "UP016")]
|
||||
#[test_case(CheckCode::UP018, Path::new("UP018.py"); "UP018")]
|
||||
#[test_case(CheckCode::UP019, Path::new("UP019.py"); "UP019")]
|
||||
#[test_case(CheckCode::UP021, Path::new("UP021.py"); "UP021")]
|
||||
#[test_case(CheckCode::UP022, Path::new("UP022.py"); "UP022")]
|
||||
#[test_case(CheckCode::UP023, Path::new("UP023.py"); "UP023")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let mut checks = test_path(
|
||||
|
||||
@@ -7,9 +7,10 @@ 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::python::identifiers::IDENTIFIER_REGEX;
|
||||
use crate::python::keyword::KWLIST;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
|
||||
/// Return the typename, args, keywords and mother class
|
||||
fn match_named_tuple_assign<'a>(
|
||||
@@ -163,8 +164,9 @@ fn convert_to_class(
|
||||
typename: &str,
|
||||
body: Vec<Stmt>,
|
||||
base_class: &ExprKind,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Fix> {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_stmt(&create_class_def_stmt(typename, body, base_class));
|
||||
let content = generator.generate()?;
|
||||
Ok(Fix::replacement(
|
||||
@@ -194,7 +196,7 @@ pub fn convert_named_tuple_functional_to_class(
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
match convert_to_class(stmt, typename, properties, base_class) {
|
||||
match convert_to_class(stmt, typename, properties, base_class, checker.style) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(err) => error!("Failed to convert `NamedTuple`: {err}"),
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@ 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::python::identifiers::IDENTIFIER_REGEX;
|
||||
use crate::python::keyword::KWLIST;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
|
||||
/// Return the class name, arguments, keywords and base class for a `TypedDict`
|
||||
/// assignment.
|
||||
@@ -196,8 +197,9 @@ fn convert_to_class(
|
||||
body: Vec<Stmt>,
|
||||
total_keyword: Option<KeywordData>,
|
||||
base_class: &ExprKind,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Fix> {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_stmt(&create_class_def_stmt(
|
||||
class_name,
|
||||
body,
|
||||
@@ -236,7 +238,14 @@ pub fn convert_typed_dict_functional_to_class(
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
match convert_to_class(stmt, class_name, body, total_keyword, base_class) {
|
||||
match convert_to_class(
|
||||
stmt,
|
||||
class_name,
|
||||
body,
|
||||
total_keyword,
|
||||
base_class,
|
||||
checker.style,
|
||||
) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(err) => error!("Failed to convert TypedDict: {err}"),
|
||||
};
|
||||
|
||||
@@ -3,8 +3,12 @@ pub use convert_typed_dict_functional_to_class::convert_typed_dict_functional_to
|
||||
pub use datetime_utc_alias::datetime_utc_alias;
|
||||
pub use deprecated_unittest_alias::deprecated_unittest_alias;
|
||||
pub use native_literals::native_literals;
|
||||
pub use open_alias::open_alias;
|
||||
pub use redundant_open_modes::redundant_open_modes;
|
||||
pub use remove_six_compat::remove_six_compat;
|
||||
pub use replace_stdout_stderr::replace_stdout_stderr;
|
||||
pub use replace_universal_newlines::replace_universal_newlines;
|
||||
pub use rewrite_c_element_tree::replace_c_element_tree;
|
||||
pub use super_call_with_parameters::super_call_with_parameters;
|
||||
pub use type_of_primitive::type_of_primitive;
|
||||
pub use typing_text_str_alias::typing_text_str_alias;
|
||||
@@ -21,8 +25,12 @@ mod convert_typed_dict_functional_to_class;
|
||||
mod datetime_utc_alias;
|
||||
mod deprecated_unittest_alias;
|
||||
mod native_literals;
|
||||
mod open_alias;
|
||||
mod redundant_open_modes;
|
||||
mod remove_six_compat;
|
||||
mod replace_stdout_stderr;
|
||||
mod replace_universal_newlines;
|
||||
mod rewrite_c_element_tree;
|
||||
mod super_call_with_parameters;
|
||||
mod type_of_primitive;
|
||||
mod typing_text_str_alias;
|
||||
|
||||
24
src/pyupgrade/plugins/open_alias.rs
Normal file
24
src/pyupgrade/plugins/open_alias.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use rustpython_ast::Expr;
|
||||
|
||||
use crate::ast::helpers::{collect_call_paths, dealias_call_path, match_call_path};
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
|
||||
/// UP020
|
||||
pub fn open_alias(checker: &mut Checker, expr: &Expr, func: &Expr) {
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), &checker.import_aliases);
|
||||
|
||||
if match_call_path(&call_path, "io", "open", &checker.from_imports) {
|
||||
let mut check = Check::new(CheckKind::OpenAlias, Range::from_located(expr));
|
||||
if checker.patch(&CheckCode::UP020) {
|
||||
check.amend(Fix::replacement(
|
||||
"open".to_string(),
|
||||
func.location,
|
||||
func.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ use rustpython_ast::{Constant, Expr, ExprKind, Keyword, KeywordData, Location};
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::token::Tok;
|
||||
|
||||
use crate::ast::helpers;
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -112,9 +111,7 @@ fn create_remove_param_fix(
|
||||
let mut fix_end: Option<Location> = None;
|
||||
let mut is_first_arg: bool = false;
|
||||
let mut delete_first_arg: bool = false;
|
||||
for (start, tok, end) in lexer::make_tokenizer(&content).flatten() {
|
||||
let start = helpers::to_absolute(start, expr.location);
|
||||
let end = helpers::to_absolute(end, expr.location);
|
||||
for (start, tok, end) in lexer::make_tokenizer_located(&content, expr.location).flatten() {
|
||||
if start == mode_param.location {
|
||||
if is_first_arg {
|
||||
delete_first_arg = true;
|
||||
|
||||
@@ -7,7 +7,8 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
use crate::SourceCodeLocator;
|
||||
|
||||
/// Return `true` if the `Expr` is a reference to `${module}.${any}`.
|
||||
@@ -87,13 +88,14 @@ fn replace_call_on_arg_by_arg_attribute(
|
||||
arg: &Expr,
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Check> {
|
||||
let attribute = ExprKind::Attribute {
|
||||
value: Box::new(arg.clone()),
|
||||
attr: attr.to_string(),
|
||||
ctx: ExprContext::Load,
|
||||
};
|
||||
replace_by_expr_kind(attribute, expr, patch)
|
||||
replace_by_expr_kind(attribute, expr, patch, stylist)
|
||||
}
|
||||
|
||||
// `func(arg, **args)` => `arg.method(**args)`
|
||||
@@ -102,6 +104,7 @@ fn replace_call_on_arg_by_arg_method_call(
|
||||
args: &[Expr],
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Option<Check>> {
|
||||
if args.is_empty() {
|
||||
bail!("Expected at least one argument");
|
||||
@@ -119,7 +122,7 @@ fn replace_call_on_arg_by_arg_method_call(
|
||||
.collect(),
|
||||
keywords: vec![],
|
||||
};
|
||||
let expr = replace_by_expr_kind(call, expr, patch)?;
|
||||
let expr = replace_by_expr_kind(call, expr, patch, stylist)?;
|
||||
Ok(Some(expr))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -127,10 +130,15 @@ fn replace_call_on_arg_by_arg_method_call(
|
||||
}
|
||||
|
||||
// `expr` => `Expr(expr_kind)`
|
||||
fn replace_by_expr_kind(node: ExprKind, expr: &Expr, patch: bool) -> Result<Check> {
|
||||
fn replace_by_expr_kind(
|
||||
node: ExprKind,
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Check> {
|
||||
let mut check = Check::new(CheckKind::RemoveSixCompat, Range::from_located(expr));
|
||||
if patch {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_expr(&create_expr(node), 0);
|
||||
let content = generator.generate()?;
|
||||
check.amend(Fix::replacement(
|
||||
@@ -142,10 +150,15 @@ fn replace_by_expr_kind(node: ExprKind, expr: &Expr, patch: bool) -> Result<Chec
|
||||
Ok(check)
|
||||
}
|
||||
|
||||
fn replace_by_stmt_kind(node: StmtKind, expr: &Expr, patch: bool) -> Result<Check> {
|
||||
fn replace_by_stmt_kind(
|
||||
node: StmtKind,
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Check> {
|
||||
let mut check = Check::new(CheckKind::RemoveSixCompat, Range::from_located(expr));
|
||||
if patch {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_stmt(&create_stmt(node));
|
||||
let content = generator.generate()?;
|
||||
check.amend(Fix::replacement(
|
||||
@@ -163,12 +176,13 @@ fn replace_by_raise_from(
|
||||
cause: Option<ExprKind>,
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Check> {
|
||||
let stmt_kind = StmtKind::Raise {
|
||||
exc: exc.map(|exc| Box::new(create_expr(exc))),
|
||||
cause: cause.map(|cause| Box::new(create_expr(cause))),
|
||||
};
|
||||
replace_by_stmt_kind(stmt_kind, expr, patch)
|
||||
replace_by_stmt_kind(stmt_kind, expr, patch, stylist)
|
||||
}
|
||||
|
||||
fn replace_by_index_on_arg(
|
||||
@@ -176,16 +190,22 @@ fn replace_by_index_on_arg(
|
||||
index: &ExprKind,
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Check> {
|
||||
let index = ExprKind::Subscript {
|
||||
value: Box::new(create_expr(arg.node.clone())),
|
||||
slice: Box::new(create_expr(index.clone())),
|
||||
ctx: ExprContext::Load,
|
||||
};
|
||||
replace_by_expr_kind(index, expr, patch)
|
||||
replace_by_expr_kind(index, expr, patch, stylist)
|
||||
}
|
||||
|
||||
fn handle_reraise(args: &[Expr], expr: &Expr, patch: bool) -> Result<Option<Check>> {
|
||||
fn handle_reraise(
|
||||
args: &[Expr],
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Option<Check>> {
|
||||
if let [_, exc, tb] = args {
|
||||
let check = replace_by_raise_from(
|
||||
Some(ExprKind::Call {
|
||||
@@ -200,6 +220,7 @@ fn handle_reraise(args: &[Expr], expr: &Expr, patch: bool) -> Result<Option<Chec
|
||||
None,
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?;
|
||||
Ok(Some(check))
|
||||
} else if let [arg] = args {
|
||||
@@ -208,7 +229,7 @@ fn handle_reraise(args: &[Expr], expr: &Expr, patch: bool) -> Result<Option<Chec
|
||||
if let ExprKind::Attribute { value, attr, .. } = &func.node {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
if id == "sys" && attr == "exc_info" {
|
||||
let check = replace_by_raise_from(None, None, expr, patch)?;
|
||||
let check = replace_by_raise_from(None, None, expr, patch, stylist)?;
|
||||
return Ok(Some(check));
|
||||
};
|
||||
};
|
||||
@@ -227,6 +248,7 @@ fn handle_func(
|
||||
keywords: &[Keyword],
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
locator: &SourceCodeLocator,
|
||||
) -> Result<Option<Check>> {
|
||||
let func_name = match &func.node {
|
||||
@@ -241,72 +263,82 @@ fn handle_func(
|
||||
("ensure_str", [arg], []) => replace_by_str_literal(arg, false, expr, patch, locator),
|
||||
("ensure_text", [arg], []) => replace_by_str_literal(arg, false, expr, patch, locator),
|
||||
("iteritems", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("items", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("items", args, expr, patch, stylist)?
|
||||
}
|
||||
("viewitems", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("items", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("items", args, expr, patch, stylist)?
|
||||
}
|
||||
("iterkeys", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("keys", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("keys", args, expr, patch, stylist)?
|
||||
}
|
||||
("viewkeys", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("keys", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("keys", args, expr, patch, stylist)?
|
||||
}
|
||||
("itervalues", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("values", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("values", args, expr, patch, stylist)?
|
||||
}
|
||||
("viewvalues", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("values", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("values", args, expr, patch, stylist)?
|
||||
}
|
||||
("get_method_function", [arg], []) => Some(replace_call_on_arg_by_arg_attribute(
|
||||
"__func__", arg, expr, patch,
|
||||
"__func__", arg, expr, patch, stylist,
|
||||
)?),
|
||||
("get_method_self", [arg], []) => Some(replace_call_on_arg_by_arg_attribute(
|
||||
"__self__", arg, expr, patch,
|
||||
"__self__", arg, expr, patch, stylist,
|
||||
)?),
|
||||
("get_function_closure", [arg], []) => Some(replace_call_on_arg_by_arg_attribute(
|
||||
"__closure__",
|
||||
arg,
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("get_function_code", [arg], []) => Some(replace_call_on_arg_by_arg_attribute(
|
||||
"__code__", arg, expr, patch,
|
||||
"__code__", arg, expr, patch, stylist,
|
||||
)?),
|
||||
("get_function_defaults", [arg], []) => Some(replace_call_on_arg_by_arg_attribute(
|
||||
"__defaults__",
|
||||
arg,
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("get_function_globals", [arg], []) => Some(replace_call_on_arg_by_arg_attribute(
|
||||
"__globals__",
|
||||
arg,
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("create_unbound_method", [arg, _], _) => Some(replace_by_expr_kind(
|
||||
arg.node.clone(),
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("get_unbound_function", [arg], []) => Some(replace_by_expr_kind(
|
||||
arg.node.clone(),
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("create_unbound_method", [arg, _], _) => {
|
||||
Some(replace_by_expr_kind(arg.node.clone(), expr, patch)?)
|
||||
}
|
||||
("get_unbound_function", [arg], []) => {
|
||||
Some(replace_by_expr_kind(arg.node.clone(), expr, patch)?)
|
||||
}
|
||||
("assertCountEqual", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("assertCountEqual", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("assertCountEqual", args, expr, patch, stylist)?
|
||||
}
|
||||
("assertRaisesRegex", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("assertRaisesRegex", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("assertRaisesRegex", args, expr, patch, stylist)?
|
||||
}
|
||||
("assertRegex", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("assertRegex", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("assertRegex", args, expr, patch, stylist)?
|
||||
}
|
||||
("raise_from", [exc, cause], []) => Some(replace_by_raise_from(
|
||||
Some(exc.node.clone()),
|
||||
Some(cause.node.clone()),
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("reraise", args, []) => handle_reraise(args, expr, patch)?,
|
||||
("reraise", args, []) => handle_reraise(args, expr, patch, stylist)?,
|
||||
("byte2int", [arg], []) => Some(replace_by_index_on_arg(
|
||||
arg,
|
||||
&ExprKind::Constant {
|
||||
@@ -315,10 +347,15 @@ fn handle_func(
|
||||
},
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("indexbytes", [arg, index], []) => Some(replace_by_index_on_arg(
|
||||
arg,
|
||||
&index.node,
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("indexbytes", [arg, index], []) => {
|
||||
Some(replace_by_index_on_arg(arg, &index.node, expr, patch)?)
|
||||
}
|
||||
("int2byte", [arg], []) => Some(replace_by_expr_kind(
|
||||
ExprKind::Call {
|
||||
func: Box::new(create_expr(ExprKind::Name {
|
||||
@@ -333,6 +370,7 @@ fn handle_func(
|
||||
},
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
_ => None,
|
||||
};
|
||||
@@ -382,6 +420,7 @@ fn handle_next_on_six_dict(expr: &Expr, patch: bool, checker: &Checker) -> Resul
|
||||
},
|
||||
arg,
|
||||
patch,
|
||||
checker.style,
|
||||
) {
|
||||
Ok(check) => Ok(Some(check)),
|
||||
Err(err) => Err(err),
|
||||
@@ -409,7 +448,15 @@ pub fn remove_six_compat(checker: &mut Checker, expr: &Expr) {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
} => match handle_func(func, args, keywords, expr, patch, checker.locator) {
|
||||
} => match handle_func(
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
expr,
|
||||
patch,
|
||||
checker.style,
|
||||
checker.locator,
|
||||
) {
|
||||
Ok(check) => check,
|
||||
Err(err) => {
|
||||
error!("Failed to remove `six` reference: {err}");
|
||||
|
||||
112
src/pyupgrade/plugins/replace_stdout_stderr.rs
Normal file
112
src/pyupgrade/plugins/replace_stdout_stderr.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
use rustpython_ast::{Expr, Keyword};
|
||||
|
||||
use crate::ast::helpers::{find_keyword, match_module_member};
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::whitespace::indentation;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MiddleContent<'a> {
|
||||
contents: &'a str,
|
||||
multi_line: bool,
|
||||
}
|
||||
|
||||
/// Return the number of "dirty" characters.
|
||||
fn dirty_count(iter: impl Iterator<Item = char>) -> usize {
|
||||
let mut the_count = 0;
|
||||
for current_char in iter {
|
||||
if current_char == ' ' || current_char == ',' || current_char == '\n' {
|
||||
the_count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
the_count
|
||||
}
|
||||
|
||||
/// Extract the `Middle` content between two arguments.
|
||||
fn extract_middle(contents: &str) -> Option<MiddleContent> {
|
||||
let multi_line = contents.contains('\n');
|
||||
let start_gap = dirty_count(contents.chars());
|
||||
if contents.len() == start_gap {
|
||||
return None;
|
||||
}
|
||||
let end_gap = dirty_count(contents.chars().rev());
|
||||
Some(MiddleContent {
|
||||
contents: &contents[start_gap..contents.len() - end_gap],
|
||||
multi_line,
|
||||
})
|
||||
}
|
||||
|
||||
/// UP022
|
||||
pub fn replace_stdout_stderr(checker: &mut Checker, expr: &Expr, kwargs: &[Keyword]) {
|
||||
if match_module_member(
|
||||
expr,
|
||||
"subprocess",
|
||||
"run",
|
||||
&checker.from_imports,
|
||||
&checker.import_aliases,
|
||||
) {
|
||||
// Find `stdout` and `stderr` kwargs.
|
||||
let Some(stdout) = find_keyword(kwargs, "stdout") else {
|
||||
return;
|
||||
};
|
||||
let Some(stderr) = find_keyword(kwargs, "stderr") else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Verify that they're both set to `subprocess.PIPE`.
|
||||
if !match_module_member(
|
||||
&stdout.node.value,
|
||||
"subprocess",
|
||||
"PIPE",
|
||||
&checker.from_imports,
|
||||
&checker.import_aliases,
|
||||
) || !match_module_member(
|
||||
&stderr.node.value,
|
||||
"subprocess",
|
||||
"PIPE",
|
||||
&checker.from_imports,
|
||||
&checker.import_aliases,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut check = Check::new(CheckKind::ReplaceStdoutStderr, Range::from_located(expr));
|
||||
if checker.patch(check.kind.code()) {
|
||||
let first = if stdout.location < stderr.location {
|
||||
stdout
|
||||
} else {
|
||||
stderr
|
||||
};
|
||||
let last = if stdout.location > stderr.location {
|
||||
stdout
|
||||
} else {
|
||||
stderr
|
||||
};
|
||||
let mut contents = String::from("capture_output=True");
|
||||
if let Some(middle) = extract_middle(&checker.locator.slice_source_code_range(&Range {
|
||||
location: first.end_location.unwrap(),
|
||||
end_location: last.location,
|
||||
})) {
|
||||
if middle.multi_line {
|
||||
contents.push(',');
|
||||
contents.push('\n');
|
||||
contents.push_str(&indentation(checker, first));
|
||||
} else {
|
||||
contents.push(',');
|
||||
contents.push(' ');
|
||||
}
|
||||
contents.push_str(middle.contents);
|
||||
}
|
||||
check.amend(Fix::replacement(
|
||||
contents,
|
||||
first.location,
|
||||
last.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
36
src/pyupgrade/plugins/replace_universal_newlines.rs
Normal file
36
src/pyupgrade/plugins/replace_universal_newlines.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use rustpython_ast::{Expr, Keyword, Location};
|
||||
|
||||
use crate::ast::helpers::{find_keyword, match_module_member};
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// UP021
|
||||
pub fn replace_universal_newlines(checker: &mut Checker, expr: &Expr, kwargs: &[Keyword]) {
|
||||
if match_module_member(
|
||||
expr,
|
||||
"subprocess",
|
||||
"run",
|
||||
&checker.from_imports,
|
||||
&checker.import_aliases,
|
||||
) {
|
||||
let Some(kwarg) = find_keyword(kwargs, "universal_newlines") else { return; };
|
||||
let range = Range {
|
||||
location: kwarg.location,
|
||||
end_location: Location::new(
|
||||
kwarg.location.row(),
|
||||
kwarg.location.column() + "universal_newlines".len(),
|
||||
),
|
||||
};
|
||||
let mut check = Check::new(CheckKind::ReplaceUniversalNewlines, range);
|
||||
if checker.patch(check.kind.code()) {
|
||||
check.amend(Fix::replacement(
|
||||
"text".to_string(),
|
||||
range.location,
|
||||
range.end_location,
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
57
src/pyupgrade/plugins/rewrite_c_element_tree.rs
Normal file
57
src/pyupgrade/plugins/rewrite_c_element_tree.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use rustpython_ast::{Located, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
fn add_check_for_node<T>(checker: &mut Checker, node: &Located<T>) {
|
||||
let mut check = Check::new(CheckKind::RewriteCElementTree, Range::from_located(node));
|
||||
if checker.patch(check.kind.code()) {
|
||||
let contents = checker
|
||||
.locator
|
||||
.slice_source_code_range(&Range::from_located(node));
|
||||
check.amend(Fix::replacement(
|
||||
contents.replacen("cElementTree", "ElementTree", 1),
|
||||
node.location,
|
||||
node.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
|
||||
/// UP023
|
||||
pub fn replace_c_element_tree(checker: &mut Checker, stmt: &Stmt) {
|
||||
match &stmt.node {
|
||||
StmtKind::Import { names } => {
|
||||
// Ex) `import xml.etree.cElementTree as ET`
|
||||
for name in names {
|
||||
if name.node.name == "xml.etree.cElementTree" && name.node.asname.is_some() {
|
||||
add_check_for_node(checker, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::ImportFrom {
|
||||
module,
|
||||
names,
|
||||
level,
|
||||
} => {
|
||||
if level.map_or(false, |level| level > 0) {
|
||||
// Ex) `import .xml.etree.cElementTree as ET`
|
||||
} else if let Some(module) = module {
|
||||
if module == "xml.etree.cElementTree" {
|
||||
// Ex) `from xml.etree.cElementTree import XML`
|
||||
add_check_for_node(checker, stmt);
|
||||
} else if module == "xml.etree" {
|
||||
// Ex) `from xml.etree import cElementTree as ET`
|
||||
for name in names {
|
||||
if name.node.name == "cElementTree" && name.node.asname.is_some() {
|
||||
add_check_for_node(checker, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!("Expected StmtKind::Import | StmtKind::ImportFrom"),
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ 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::source_code_generator::SourceCodeGenerator;
|
||||
|
||||
fn optional(expr: &Expr) -> Expr {
|
||||
Expr::new(
|
||||
@@ -65,7 +65,8 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
|
||||
if checker.match_typing_call_path(&call_path, "Optional") {
|
||||
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
|
||||
if checker.patch(check.kind.code()) {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator =
|
||||
SourceCodeGenerator::new(checker.style.indentation(), checker.style.quote());
|
||||
generator.unparse_expr(&optional(slice), 0);
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
@@ -84,7 +85,10 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
|
||||
// Invalid type annotation.
|
||||
}
|
||||
ExprKind::Tuple { elts, .. } => {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(
|
||||
checker.style.indentation(),
|
||||
checker.style.quote(),
|
||||
);
|
||||
generator.unparse_expr(&union(elts), 0);
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
@@ -96,7 +100,10 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
|
||||
}
|
||||
_ => {
|
||||
// Single argument.
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(
|
||||
checker.style.indentation(),
|
||||
checker.style.quote(),
|
||||
);
|
||||
generator.unparse_expr(slice, 0);
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
|
||||
@@ -75,7 +75,7 @@ expression: checks
|
||||
row: 17
|
||||
column: 46
|
||||
fix:
|
||||
content: "class MyType5(TypedDict):\n a: 'hello'"
|
||||
content: "class MyType5(TypedDict):\n a: \"hello\""
|
||||
location:
|
||||
row: 17
|
||||
column: 0
|
||||
@@ -91,7 +91,7 @@ expression: checks
|
||||
row: 18
|
||||
column: 41
|
||||
fix:
|
||||
content: "class MyType6(TypedDict):\n a: 'hello'"
|
||||
content: "class MyType6(TypedDict):\n a: \"hello\""
|
||||
location:
|
||||
row: 18
|
||||
column: 0
|
||||
@@ -139,7 +139,7 @@ expression: checks
|
||||
row: 30
|
||||
column: 59
|
||||
fix:
|
||||
content: "class MyType10(TypedDict):\n key: Literal['value']"
|
||||
content: "class MyType10(TypedDict):\n key: Literal[\"value\"]"
|
||||
location:
|
||||
row: 30
|
||||
column: 0
|
||||
|
||||
@@ -27,7 +27,7 @@ expression: checks
|
||||
row: 12
|
||||
column: 1
|
||||
fix:
|
||||
content: "class NT2(NamedTuple):\n a: int\n b: str = 'foo'\n c: list[bool] = [True]"
|
||||
content: "class NT2(NamedTuple):\n a: int\n b: str = \"foo\"\n c: list[bool] = [True]"
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
source: src/pyupgrade/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: ReplaceUniversalNewlines
|
||||
location:
|
||||
row: 6
|
||||
column: 24
|
||||
end_location:
|
||||
row: 6
|
||||
column: 42
|
||||
fix:
|
||||
content: text
|
||||
location:
|
||||
row: 6
|
||||
column: 24
|
||||
end_location:
|
||||
row: 6
|
||||
column: 42
|
||||
- kind: ReplaceUniversalNewlines
|
||||
location:
|
||||
row: 7
|
||||
column: 22
|
||||
end_location:
|
||||
row: 7
|
||||
column: 40
|
||||
fix:
|
||||
content: text
|
||||
location:
|
||||
row: 7
|
||||
column: 22
|
||||
end_location:
|
||||
row: 7
|
||||
column: 40
|
||||
- kind: ReplaceUniversalNewlines
|
||||
location:
|
||||
row: 9
|
||||
column: 13
|
||||
end_location:
|
||||
row: 9
|
||||
column: 31
|
||||
fix:
|
||||
content: text
|
||||
location:
|
||||
row: 9
|
||||
column: 13
|
||||
end_location:
|
||||
row: 9
|
||||
column: 31
|
||||
- kind: ReplaceUniversalNewlines
|
||||
location:
|
||||
row: 10
|
||||
column: 21
|
||||
end_location:
|
||||
row: 10
|
||||
column: 39
|
||||
fix:
|
||||
content: text
|
||||
location:
|
||||
row: 10
|
||||
column: 21
|
||||
end_location:
|
||||
row: 10
|
||||
column: 39
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
---
|
||||
source: src/pyupgrade/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: ReplaceStdoutStderr
|
||||
location:
|
||||
row: 4
|
||||
column: 9
|
||||
end_location:
|
||||
row: 4
|
||||
column: 69
|
||||
fix:
|
||||
content: capture_output=True
|
||||
location:
|
||||
row: 4
|
||||
column: 22
|
||||
end_location:
|
||||
row: 4
|
||||
column: 68
|
||||
- kind: ReplaceStdoutStderr
|
||||
location:
|
||||
row: 6
|
||||
column: 9
|
||||
end_location:
|
||||
row: 6
|
||||
column: 80
|
||||
fix:
|
||||
content: capture_output=True
|
||||
location:
|
||||
row: 6
|
||||
column: 33
|
||||
end_location:
|
||||
row: 6
|
||||
column: 79
|
||||
- kind: ReplaceStdoutStderr
|
||||
location:
|
||||
row: 8
|
||||
column: 9
|
||||
end_location:
|
||||
row: 8
|
||||
column: 86
|
||||
fix:
|
||||
content: "capture_output=True, args=[\"foo\"]"
|
||||
location:
|
||||
row: 8
|
||||
column: 24
|
||||
end_location:
|
||||
row: 8
|
||||
column: 85
|
||||
- kind: ReplaceStdoutStderr
|
||||
location:
|
||||
row: 10
|
||||
column: 9
|
||||
end_location:
|
||||
row: 12
|
||||
column: 1
|
||||
fix:
|
||||
content: "capture_output=True, check=True"
|
||||
location:
|
||||
row: 11
|
||||
column: 13
|
||||
end_location:
|
||||
row: 11
|
||||
column: 71
|
||||
- kind: ReplaceStdoutStderr
|
||||
location:
|
||||
row: 14
|
||||
column: 9
|
||||
end_location:
|
||||
row: 16
|
||||
column: 1
|
||||
fix:
|
||||
content: "capture_output=True, check=True"
|
||||
location:
|
||||
row: 15
|
||||
column: 13
|
||||
end_location:
|
||||
row: 15
|
||||
column: 71
|
||||
- kind: ReplaceStdoutStderr
|
||||
location:
|
||||
row: 18
|
||||
column: 9
|
||||
end_location:
|
||||
row: 26
|
||||
column: 1
|
||||
fix:
|
||||
content: "capture_output=True,\n check=True"
|
||||
location:
|
||||
row: 20
|
||||
column: 4
|
||||
end_location:
|
||||
row: 22
|
||||
column: 26
|
||||
- kind: ReplaceStdoutStderr
|
||||
location:
|
||||
row: 29
|
||||
column: 13
|
||||
end_location:
|
||||
row: 36
|
||||
column: 5
|
||||
fix:
|
||||
content: "capture_output=True,\n check=True"
|
||||
location:
|
||||
row: 31
|
||||
column: 8
|
||||
end_location:
|
||||
row: 33
|
||||
column: 30
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
---
|
||||
source: src/pyupgrade/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: RewriteCElementTree
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 2
|
||||
column: 59
|
||||
fix:
|
||||
content: "from xml.etree.ElementTree import XML, Element, SubElement"
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 2
|
||||
column: 59
|
||||
- kind: RewriteCElementTree
|
||||
location:
|
||||
row: 3
|
||||
column: 7
|
||||
end_location:
|
||||
row: 3
|
||||
column: 35
|
||||
fix:
|
||||
content: xml.etree.ElementTree as ET
|
||||
location:
|
||||
row: 3
|
||||
column: 7
|
||||
end_location:
|
||||
row: 3
|
||||
column: 35
|
||||
- kind: RewriteCElementTree
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 44
|
||||
fix:
|
||||
content: from xml.etree.ElementTree import XML
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 44
|
||||
- kind: RewriteCElementTree
|
||||
location:
|
||||
row: 7
|
||||
column: 10
|
||||
end_location:
|
||||
row: 7
|
||||
column: 49
|
||||
fix:
|
||||
content: xml.etree.ElementTree as ET
|
||||
location:
|
||||
row: 7
|
||||
column: 10
|
||||
end_location:
|
||||
row: 7
|
||||
column: 49
|
||||
- kind: RewriteCElementTree
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
column: 1
|
||||
fix:
|
||||
content: "from xml.etree.ElementTree import (\n XML,\n Element,\n SubElement,\n)"
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
column: 1
|
||||
- kind: RewriteCElementTree
|
||||
location:
|
||||
row: 16
|
||||
column: 11
|
||||
end_location:
|
||||
row: 16
|
||||
column: 39
|
||||
fix:
|
||||
content: xml.etree.ElementTree as ET
|
||||
location:
|
||||
row: 16
|
||||
column: 11
|
||||
end_location:
|
||||
row: 16
|
||||
column: 39
|
||||
- kind: RewriteCElementTree
|
||||
location:
|
||||
row: 17
|
||||
column: 26
|
||||
end_location:
|
||||
row: 17
|
||||
column: 45
|
||||
fix:
|
||||
content: ElementTree as CET
|
||||
location:
|
||||
row: 17
|
||||
column: 26
|
||||
end_location:
|
||||
row: 17
|
||||
column: 45
|
||||
- kind: RewriteCElementTree
|
||||
location:
|
||||
row: 19
|
||||
column: 22
|
||||
end_location:
|
||||
row: 19
|
||||
column: 40
|
||||
fix:
|
||||
content: ElementTree as ET
|
||||
location:
|
||||
row: 19
|
||||
column: 22
|
||||
end_location:
|
||||
row: 19
|
||||
column: 40
|
||||
- kind: RewriteCElementTree
|
||||
location:
|
||||
row: 21
|
||||
column: 19
|
||||
end_location:
|
||||
row: 21
|
||||
column: 47
|
||||
fix:
|
||||
content: xml.etree.ElementTree as ET
|
||||
location:
|
||||
row: 21
|
||||
column: 19
|
||||
end_location:
|
||||
row: 21
|
||||
column: 47
|
||||
- kind: RewriteCElementTree
|
||||
location:
|
||||
row: 24
|
||||
column: 31
|
||||
end_location:
|
||||
row: 24
|
||||
column: 59
|
||||
fix:
|
||||
content: xml.etree.ElementTree as ET
|
||||
location:
|
||||
row: 24
|
||||
column: 31
|
||||
end_location:
|
||||
row: 24
|
||||
column: 59
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_ast::{Expr, ExprKind, Keyword, KeywordData, Location};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
@@ -1680,3 +1680,25 @@ pub fn ambiguous_unicode_character(
|
||||
|
||||
checks
|
||||
}
|
||||
|
||||
/// RUF004
|
||||
pub fn keyword_argument_before_star_argument(args: &[Expr], keywords: &[Keyword]) -> Vec<Check> {
|
||||
let mut checks = vec![];
|
||||
if let Some(arg) = args
|
||||
.iter()
|
||||
.rfind(|arg| matches!(arg.node, ExprKind::Starred { .. }))
|
||||
{
|
||||
for keyword in keywords {
|
||||
if keyword.location < arg.location {
|
||||
let KeywordData { arg, .. } = &keyword.node;
|
||||
if let Some(arg) = arg {
|
||||
checks.push(Check::new(
|
||||
CheckKind::KeywordArgumentBeforeStarArgument(arg.to_string()),
|
||||
Range::from_located(keyword),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
checks
|
||||
}
|
||||
|
||||
@@ -8,10 +8,24 @@ mod tests {
|
||||
|
||||
use anyhow::Result;
|
||||
use rustc_hash::FxHashSet;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::settings;
|
||||
#[test_case(CheckCode::RUF004, Path::new("RUF004.py"); "RUF004")]
|
||||
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/ruff")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confusables() -> Result<()> {
|
||||
|
||||
32
src/ruff/snapshots/ruff__ruff__tests__RUF004_RUF004.py.snap
Normal file
32
src/ruff/snapshots/ruff__ruff__tests__RUF004_RUF004.py.snap
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: src/ruff/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
KeywordArgumentBeforeStarArgument: kw
|
||||
location:
|
||||
row: 13
|
||||
column: 2
|
||||
end_location:
|
||||
row: 13
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind:
|
||||
KeywordArgumentBeforeStarArgument: kw
|
||||
location:
|
||||
row: 14
|
||||
column: 2
|
||||
end_location:
|
||||
row: 14
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind:
|
||||
KeywordArgumentBeforeStarArgument: kw
|
||||
location:
|
||||
row: 15
|
||||
column: 6
|
||||
end_location:
|
||||
row: 15
|
||||
column: 10
|
||||
fix: ~
|
||||
|
||||
@@ -21,7 +21,8 @@ use crate::settings::types::{
|
||||
};
|
||||
use crate::{
|
||||
flake8_annotations, flake8_bugbear, flake8_errmsg, flake8_import_conventions, flake8_quotes,
|
||||
flake8_tidy_imports, flake8_unused_arguments, fs, isort, mccabe, pep8_naming, pyupgrade,
|
||||
flake8_tidy_imports, flake8_unused_arguments, fs, isort, mccabe, pep8_naming, pydocstyle,
|
||||
pyupgrade,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -62,6 +63,7 @@ pub struct Configuration {
|
||||
pub isort: Option<isort::settings::Options>,
|
||||
pub mccabe: Option<mccabe::settings::Options>,
|
||||
pub pep8_naming: Option<pep8_naming::settings::Options>,
|
||||
pub pydocstyle: Option<pydocstyle::settings::Options>,
|
||||
pub pyupgrade: Option<pyupgrade::settings::Options>,
|
||||
}
|
||||
|
||||
@@ -156,6 +158,7 @@ impl Configuration {
|
||||
isort: options.isort,
|
||||
mccabe: options.mccabe,
|
||||
pep8_naming: options.pep8_naming,
|
||||
pydocstyle: options.pydocstyle,
|
||||
pyupgrade: options.pyupgrade,
|
||||
})
|
||||
}
|
||||
@@ -217,6 +220,7 @@ impl Configuration {
|
||||
isort: self.isort.or(config.isort),
|
||||
mccabe: self.mccabe.or(config.mccabe),
|
||||
pep8_naming: self.pep8_naming.or(config.pep8_naming),
|
||||
pydocstyle: self.pydocstyle.or(config.pydocstyle),
|
||||
pyupgrade: self.pyupgrade.or(config.pyupgrade),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/// Simple flags used to drive program behavior.
|
||||
use crate::autofix::fixer;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Hash)]
|
||||
pub enum Autofix {
|
||||
Enabled,
|
||||
Disabled,
|
||||
@@ -20,13 +20,13 @@ impl From<bool> for Autofix {
|
||||
impl From<fixer::Mode> for Autofix {
|
||||
fn from(value: fixer::Mode) -> Self {
|
||||
match value {
|
||||
fixer::Mode::Generate | fixer::Mode::Apply => Autofix::Enabled,
|
||||
fixer::Mode::Generate | fixer::Mode::Diff | fixer::Mode::Apply => Autofix::Enabled,
|
||||
fixer::Mode::None => Autofix::Disabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Hash)]
|
||||
pub enum Noqa {
|
||||
Enabled,
|
||||
Disabled,
|
||||
@@ -42,7 +42,7 @@ impl From<bool> for Noqa {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Hash)]
|
||||
pub enum Cache {
|
||||
Enabled,
|
||||
Disabled,
|
||||
|
||||
@@ -22,7 +22,8 @@ use crate::settings::types::{
|
||||
};
|
||||
use crate::{
|
||||
flake8_annotations, flake8_bugbear, flake8_errmsg, flake8_import_conventions, flake8_quotes,
|
||||
flake8_tidy_imports, flake8_unused_arguments, isort, mccabe, pep8_naming, pyupgrade,
|
||||
flake8_tidy_imports, flake8_unused_arguments, isort, mccabe, pep8_naming, pydocstyle,
|
||||
pyupgrade,
|
||||
};
|
||||
|
||||
pub mod configuration;
|
||||
@@ -68,6 +69,7 @@ pub struct Settings {
|
||||
pub isort: isort::settings::Settings,
|
||||
pub mccabe: mccabe::settings::Settings,
|
||||
pub pep8_naming: pep8_naming::settings::Settings,
|
||||
pub pydocstyle: pydocstyle::settings::Settings,
|
||||
pub pyupgrade: pyupgrade::settings::Settings,
|
||||
}
|
||||
|
||||
@@ -136,7 +138,7 @@ impl Settings {
|
||||
}]
|
||||
.into_iter(),
|
||||
),
|
||||
format: config.format.unwrap_or(SerializationFormat::Text),
|
||||
format: config.format.unwrap_or_default(),
|
||||
force_exclude: config.force_exclude.unwrap_or(false),
|
||||
ignore_init_module_imports: config.ignore_init_module_imports.unwrap_or_default(),
|
||||
line_length: config.line_length.unwrap_or(88),
|
||||
@@ -193,6 +195,10 @@ impl Settings {
|
||||
.pep8_naming
|
||||
.map(pep8_naming::settings::Settings::from_options)
|
||||
.unwrap_or_default(),
|
||||
pydocstyle: config
|
||||
.pydocstyle
|
||||
.map(pydocstyle::settings::Settings::from_options)
|
||||
.unwrap_or_default(),
|
||||
pyupgrade: config
|
||||
.pyupgrade
|
||||
.as_ref()
|
||||
@@ -233,6 +239,7 @@ impl Settings {
|
||||
isort: isort::settings::Settings::default(),
|
||||
mccabe: mccabe::settings::Settings::default(),
|
||||
pep8_naming: pep8_naming::settings::Settings::default(),
|
||||
pydocstyle: pydocstyle::settings::Settings::default(),
|
||||
pyupgrade: pyupgrade::settings::Settings::default(),
|
||||
}
|
||||
}
|
||||
@@ -269,6 +276,7 @@ impl Settings {
|
||||
isort: isort::settings::Settings::default(),
|
||||
mccabe: mccabe::settings::Settings::default(),
|
||||
pep8_naming: pep8_naming::settings::Settings::default(),
|
||||
pydocstyle: pydocstyle::settings::Settings::default(),
|
||||
pyupgrade: pyupgrade::settings::Settings::default(),
|
||||
}
|
||||
}
|
||||
@@ -326,6 +334,7 @@ impl Hash for Settings {
|
||||
self.isort.hash(state);
|
||||
self.mccabe.hash(state);
|
||||
self.pep8_naming.hash(state);
|
||||
self.pydocstyle.hash(state);
|
||||
self.pyupgrade.hash(state);
|
||||
}
|
||||
}
|
||||
@@ -370,6 +379,7 @@ fn resolve_codes<'a>(specs: impl Iterator<Item = CheckCodeSpec<'a>>) -> FxHashSe
|
||||
let mut codes: FxHashSet<CheckCode> = FxHashSet::default();
|
||||
for spec in specs {
|
||||
for specificity in [
|
||||
SuffixLength::None,
|
||||
SuffixLength::Zero,
|
||||
SuffixLength::One,
|
||||
SuffixLength::Two,
|
||||
|
||||
@@ -9,7 +9,8 @@ use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::settings::types::{PythonVersion, SerializationFormat, Version};
|
||||
use crate::{
|
||||
flake8_annotations, flake8_bugbear, flake8_errmsg, flake8_import_conventions, flake8_quotes,
|
||||
flake8_tidy_imports, flake8_unused_arguments, isort, mccabe, pep8_naming, pyupgrade,
|
||||
flake8_tidy_imports, flake8_unused_arguments, isort, mccabe, pep8_naming, pydocstyle,
|
||||
pyupgrade,
|
||||
};
|
||||
|
||||
#[derive(
|
||||
@@ -155,8 +156,9 @@ pub struct Options {
|
||||
)]
|
||||
/// 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).
|
||||
/// (machine-readable), `"junit"` (machine-readable XML), `"github"`
|
||||
/// (GitHub Actions annotations) or `"gitlab"`
|
||||
/// (GitLab CI code quality report).
|
||||
pub format: Option<SerializationFormat>,
|
||||
#[option(
|
||||
default = r#"false"#,
|
||||
@@ -337,7 +339,6 @@ pub struct Options {
|
||||
/// This setting will override even the `RUFF_CACHE_DIR` environment
|
||||
/// variable, if set.
|
||||
pub cache_dir: Option<String>,
|
||||
/// Plugins
|
||||
#[option_group]
|
||||
/// Options for the `flake8-annotations` plugin.
|
||||
pub flake8_annotations: Option<flake8_annotations::settings::Options>,
|
||||
@@ -369,6 +370,9 @@ pub struct Options {
|
||||
/// Options for the `pep8-naming` plugin.
|
||||
pub pep8_naming: Option<pep8_naming::settings::Options>,
|
||||
#[option_group]
|
||||
/// Options for the `pydocstyle` plugin.
|
||||
pub pydocstyle: Option<pydocstyle::settings::Options>,
|
||||
#[option_group]
|
||||
/// Options for the `pyupgrade` plugin.
|
||||
pub pyupgrade: Option<pyupgrade::settings::Options>,
|
||||
// Tables are required to go last.
|
||||
|
||||
@@ -198,6 +198,7 @@ mod tests {
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pydocstyle: None,
|
||||
pyupgrade: None,
|
||||
})
|
||||
})
|
||||
@@ -249,6 +250,7 @@ line-length = 79
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pydocstyle: None,
|
||||
pyupgrade: None,
|
||||
})
|
||||
})
|
||||
@@ -300,6 +302,7 @@ exclude = ["foo.py"]
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pydocstyle: None,
|
||||
pyupgrade: None,
|
||||
})
|
||||
})
|
||||
@@ -351,6 +354,7 @@ select = ["E501"]
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pydocstyle: None,
|
||||
pyupgrade: None,
|
||||
})
|
||||
})
|
||||
@@ -403,6 +407,7 @@ ignore = ["E501"]
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pydocstyle: None,
|
||||
pyupgrade: None,
|
||||
})
|
||||
})
|
||||
@@ -541,6 +546,7 @@ other-attribute = 1
|
||||
]),
|
||||
staticmethod_decorators: Some(vec!["staticmethod".to_string()]),
|
||||
}),
|
||||
pydocstyle: None,
|
||||
pyupgrade: None,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -154,6 +154,7 @@ pub enum SerializationFormat {
|
||||
Junit,
|
||||
Grouped,
|
||||
Github,
|
||||
Gitlab,
|
||||
}
|
||||
|
||||
impl Default for SerializationFormat {
|
||||
@@ -163,6 +164,12 @@ impl Default for SerializationFormat {
|
||||
return Self::Github;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(gitlab_ci) = env::var("GITLAB_CI") {
|
||||
if gitlab_ci == "true" {
|
||||
return Self::Gitlab;
|
||||
}
|
||||
}
|
||||
Self::Text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
//! Generate Python source code from an abstract syntax tree (AST).
|
||||
|
||||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
use anyhow::Result;
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Suite, Withitem};
|
||||
use rustpython_common::str;
|
||||
use rustpython_parser::ast::{
|
||||
Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, ConversionFlag, Expr, ExprKind,
|
||||
Operator, Stmt, StmtKind,
|
||||
};
|
||||
|
||||
use crate::source_code_style::{Indentation, Quote};
|
||||
use crate::vendor::{bytes, str};
|
||||
|
||||
mod precedence {
|
||||
macro_rules! precedence {
|
||||
($($op:ident,)*) => {
|
||||
@@ -27,25 +32,27 @@ mod precedence {
|
||||
pub const EXPR: u8 = BOR;
|
||||
}
|
||||
|
||||
pub struct SourceGenerator {
|
||||
pub struct SourceCodeGenerator<'a> {
|
||||
/// The indentation style to use.
|
||||
indent: &'a Indentation,
|
||||
/// The quote style to use for string literals.
|
||||
quote: &'a Quote,
|
||||
buffer: Vec<u8>,
|
||||
indentation: usize,
|
||||
new_lines: usize,
|
||||
indent_depth: usize,
|
||||
num_newlines: usize,
|
||||
initial: bool,
|
||||
}
|
||||
|
||||
impl Default for SourceGenerator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceGenerator {
|
||||
pub fn new() -> Self {
|
||||
SourceGenerator {
|
||||
impl<'a> SourceCodeGenerator<'a> {
|
||||
pub fn new(indent: &'a Indentation, quote: &'a Quote) -> Self {
|
||||
SourceCodeGenerator {
|
||||
// Style preferences.
|
||||
indent,
|
||||
quote,
|
||||
// Internal state.
|
||||
buffer: vec![],
|
||||
indentation: 0,
|
||||
new_lines: 0,
|
||||
indent_depth: 0,
|
||||
num_newlines: 0,
|
||||
initial: true,
|
||||
}
|
||||
}
|
||||
@@ -56,30 +63,30 @@ impl SourceGenerator {
|
||||
|
||||
fn newline(&mut self) {
|
||||
if !self.initial {
|
||||
self.new_lines = std::cmp::max(self.new_lines, 1);
|
||||
self.num_newlines = std::cmp::max(self.num_newlines, 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn newlines(&mut self, extra: usize) {
|
||||
if !self.initial {
|
||||
self.new_lines = std::cmp::max(self.new_lines, 1 + extra);
|
||||
self.num_newlines = std::cmp::max(self.num_newlines, 1 + extra);
|
||||
}
|
||||
}
|
||||
|
||||
fn body<U>(&mut self, stmts: &[Stmt<U>]) {
|
||||
self.indentation += 1;
|
||||
self.indent_depth += 1;
|
||||
for stmt in stmts {
|
||||
self.unparse_stmt(stmt);
|
||||
}
|
||||
self.indentation -= 1;
|
||||
self.indent_depth -= 1;
|
||||
}
|
||||
|
||||
fn p(&mut self, s: &str) {
|
||||
if self.new_lines > 0 {
|
||||
for _ in 0..self.new_lines {
|
||||
if self.num_newlines > 0 {
|
||||
for _ in 0..self.num_newlines {
|
||||
self.buffer.extend("\n".as_bytes());
|
||||
}
|
||||
self.new_lines = 0;
|
||||
self.num_newlines = 0;
|
||||
}
|
||||
self.buffer.extend(s.as_bytes());
|
||||
}
|
||||
@@ -108,7 +115,7 @@ impl SourceGenerator {
|
||||
macro_rules! statement {
|
||||
($body:block) => {{
|
||||
self.newline();
|
||||
self.p(&" ".repeat(self.indentation));
|
||||
self.p(&self.indent.deref().repeat(self.indent_depth));
|
||||
$body
|
||||
self.initial = false;
|
||||
}};
|
||||
@@ -123,7 +130,7 @@ impl SourceGenerator {
|
||||
..
|
||||
} => {
|
||||
// TODO(charlie): Handle decorators.
|
||||
self.newlines(if self.indentation == 0 { 2 } else { 1 });
|
||||
self.newlines(if self.indent_depth == 0 { 2 } else { 1 });
|
||||
statement!({
|
||||
self.p("def ");
|
||||
self.p(name);
|
||||
@@ -137,7 +144,7 @@ impl SourceGenerator {
|
||||
self.p(":");
|
||||
});
|
||||
self.body(body);
|
||||
if self.indentation == 0 {
|
||||
if self.indent_depth == 0 {
|
||||
self.newlines(2);
|
||||
}
|
||||
}
|
||||
@@ -149,7 +156,7 @@ impl SourceGenerator {
|
||||
..
|
||||
} => {
|
||||
// TODO(charlie): Handle decorators.
|
||||
self.newlines(if self.indentation == 0 { 2 } else { 1 });
|
||||
self.newlines(if self.indent_depth == 0 { 2 } else { 1 });
|
||||
statement!({
|
||||
self.p("async def ");
|
||||
self.p(name);
|
||||
@@ -163,7 +170,7 @@ impl SourceGenerator {
|
||||
self.p(":");
|
||||
});
|
||||
self.body(body);
|
||||
if self.indentation == 0 {
|
||||
if self.indent_depth == 0 {
|
||||
self.newlines(2);
|
||||
}
|
||||
}
|
||||
@@ -175,7 +182,7 @@ impl SourceGenerator {
|
||||
..
|
||||
} => {
|
||||
// TODO(charlie): Handle decorators.
|
||||
self.newlines(if self.indentation == 0 { 2 } else { 1 });
|
||||
self.newlines(if self.indent_depth == 0 { 2 } else { 1 });
|
||||
statement!({
|
||||
self.p("class ");
|
||||
self.p(name);
|
||||
@@ -200,7 +207,7 @@ impl SourceGenerator {
|
||||
self.p(":");
|
||||
});
|
||||
self.body(body);
|
||||
if self.indentation == 0 {
|
||||
if self.indent_depth == 0 {
|
||||
self.newlines(2);
|
||||
}
|
||||
}
|
||||
@@ -788,6 +795,12 @@ impl SourceGenerator {
|
||||
{
|
||||
self.p(&value.to_string().replace("inf", inf_str));
|
||||
}
|
||||
Constant::Bytes(b) => {
|
||||
self.p(&bytes::repr(b, self.quote.into()));
|
||||
}
|
||||
Constant::Str(s) => {
|
||||
self.p(&format!("{}", str::repr(s, self.quote.into())));
|
||||
}
|
||||
_ => self.p(&format!("{value}")),
|
||||
}
|
||||
}
|
||||
@@ -931,7 +944,7 @@ impl SourceGenerator {
|
||||
}
|
||||
|
||||
fn unparse_formatted<U>(&mut self, val: &Expr<U>, conversion: usize, spec: Option<&Expr<U>>) {
|
||||
let mut generator = SourceGenerator::default();
|
||||
let mut generator = SourceCodeGenerator::new(self.indent, self.quote);
|
||||
generator.unparse_expr(val, precedence::TEST + 1);
|
||||
let brace = if generator.buffer.starts_with("{".as_bytes()) {
|
||||
// put a space to avoid escaping the bracket
|
||||
@@ -987,10 +1000,10 @@ impl SourceGenerator {
|
||||
self.unparse_fstring_body(values, is_spec);
|
||||
} else {
|
||||
self.p("f");
|
||||
let mut generator = SourceGenerator::default();
|
||||
let mut generator = SourceCodeGenerator::new(self.indent, self.quote);
|
||||
generator.unparse_fstring_body(values, is_spec);
|
||||
let body = std::str::from_utf8(&generator.buffer).unwrap();
|
||||
self.p(&format!("{}", str::repr(body)));
|
||||
self.p(&format!("{}", str::repr(body, self.quote.into())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1010,3 +1023,139 @@ impl SourceGenerator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use anyhow::Result;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_style::{Indentation, Quote};
|
||||
|
||||
fn round_trip(contents: &str) -> Result<String> {
|
||||
let indentation = Indentation::default();
|
||||
let quote = Quote::default();
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let mut generator = SourceCodeGenerator::new(&indentation, "e);
|
||||
generator.unparse_stmt(stmt);
|
||||
generator.generate().map_err(std::convert::Into::into)
|
||||
}
|
||||
|
||||
fn round_trip_with(indentation: &Indentation, quote: &Quote, contents: &str) -> Result<String> {
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let mut generator = SourceCodeGenerator::new(indentation, quote);
|
||||
generator.unparse_stmt(stmt);
|
||||
generator.generate().map_err(std::convert::Into::into)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quote() -> Result<()> {
|
||||
assert_eq!(round_trip(r#""hello""#)?, r#""hello""#);
|
||||
assert_eq!(round_trip(r#"'hello'"#)?, r#""hello""#);
|
||||
assert_eq!(round_trip(r#"u'hello'"#)?, r#"u"hello""#);
|
||||
assert_eq!(round_trip(r#"r'hello'"#)?, r#""hello""#);
|
||||
assert_eq!(round_trip(r#"b'hello'"#)?, r#"b"hello""#);
|
||||
assert_eq!(round_trip(r#"("abc" "def" "ghi")"#)?, r#""abcdefghi""#);
|
||||
assert_eq!(round_trip(r#""he\"llo""#)?, r#"'he"llo'"#);
|
||||
assert_eq!(round_trip(r#"f'abc{"def"}{1}'"#)?, r#"f'abc{"def"}{1}'"#);
|
||||
assert_eq!(round_trip(r#"f"abc{'def'}{1}""#)?, r#"f'abc{"def"}{1}'"#);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indent() -> Result<()> {
|
||||
assert_eq!(
|
||||
round_trip(
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim(),
|
||||
)?,
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_quote() -> Result<()> {
|
||||
assert_eq!(
|
||||
round_trip_with(&Indentation::default(), &Quote::Double, r#""hello""#)?,
|
||||
r#""hello""#
|
||||
);
|
||||
assert_eq!(
|
||||
round_trip_with(&Indentation::default(), &Quote::Single, r#""hello""#)?,
|
||||
r#"'hello'"#
|
||||
);
|
||||
assert_eq!(
|
||||
round_trip_with(&Indentation::default(), &Quote::Double, r#"'hello'"#)?,
|
||||
r#""hello""#
|
||||
);
|
||||
assert_eq!(
|
||||
round_trip_with(&Indentation::default(), &Quote::Single, r#"'hello'"#)?,
|
||||
r#"'hello'"#
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_indent() -> Result<()> {
|
||||
assert_eq!(
|
||||
round_trip_with(
|
||||
&Indentation::new(" ".to_string()),
|
||||
&Quote::default(),
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim(),
|
||||
)?,
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
assert_eq!(
|
||||
round_trip_with(
|
||||
&Indentation::new(" ".to_string()),
|
||||
&Quote::default(),
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim(),
|
||||
)?,
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
assert_eq!(
|
||||
round_trip_with(
|
||||
&Indentation::new("\t".to_string()),
|
||||
&Quote::default(),
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim(),
|
||||
)?,
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
200
src/source_code_style.rs
Normal file
200
src/source_code_style.rs
Normal file
@@ -0,0 +1,200 @@
|
||||
//! Detect code style from Python source code.
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
use once_cell::unsync::OnceCell;
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::Tok;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::pydocstyle::helpers::leading_quote;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::vendor;
|
||||
|
||||
pub struct SourceCodeStyleDetector<'a> {
|
||||
contents: &'a str,
|
||||
locator: &'a SourceCodeLocator<'a>,
|
||||
indentation: OnceCell<Indentation>,
|
||||
quote: OnceCell<Quote>,
|
||||
}
|
||||
|
||||
impl<'a> SourceCodeStyleDetector<'a> {
|
||||
pub fn indentation(&'a self) -> &'a Indentation {
|
||||
self.indentation
|
||||
.get_or_init(|| detect_indentation(self.contents, self.locator).unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn quote(&'a self) -> &'a Quote {
|
||||
self.quote
|
||||
.get_or_init(|| detect_quote(self.contents, self.locator).unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn from_contents(contents: &'a str, locator: &'a SourceCodeLocator<'a>) -> Self {
|
||||
Self {
|
||||
contents,
|
||||
locator,
|
||||
indentation: OnceCell::default(),
|
||||
quote: OnceCell::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The quotation style used in Python source code.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Quote {
|
||||
Single,
|
||||
Double,
|
||||
}
|
||||
|
||||
impl Default for Quote {
|
||||
fn default() -> Self {
|
||||
Quote::Double
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Quote> for vendor::str::Quote {
|
||||
fn from(val: &Quote) -> Self {
|
||||
match val {
|
||||
Quote::Single => vendor::str::Quote::Single,
|
||||
Quote::Double => vendor::str::Quote::Double,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The indentation style used in Python source code.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Indentation(String);
|
||||
|
||||
impl Indentation {
|
||||
pub fn new(indentation: String) -> Self {
|
||||
Self(indentation)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Indentation {
|
||||
fn default() -> Self {
|
||||
Indentation(" ".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Indentation {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect the indentation style of the given tokens.
|
||||
fn detect_indentation(contents: &str, locator: &SourceCodeLocator) -> Option<Indentation> {
|
||||
for (_start, tok, end) in lexer::make_tokenizer(contents).flatten() {
|
||||
if let Tok::Indent { .. } = tok {
|
||||
let start = Location::new(end.row(), 0);
|
||||
let whitespace = locator.slice_source_code_range(&Range {
|
||||
location: start,
|
||||
end_location: end,
|
||||
});
|
||||
return Some(Indentation(whitespace.to_string()));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Detect the quotation style of the given tokens.
|
||||
fn detect_quote(contents: &str, locator: &SourceCodeLocator) -> Option<Quote> {
|
||||
for (start, tok, end) in lexer::make_tokenizer(contents).flatten() {
|
||||
if let Tok::String { .. } = tok {
|
||||
let content = locator.slice_source_code_range(&Range {
|
||||
location: start,
|
||||
end_location: end,
|
||||
});
|
||||
if let Some(pattern) = leading_quote(&content) {
|
||||
if pattern.contains('\'') {
|
||||
return Some(Quote::Single);
|
||||
} else if pattern.contains('"') {
|
||||
return Some(Quote::Double);
|
||||
}
|
||||
unreachable!("Expected string to start with a valid quote prefix")
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::source_code_style::{detect_indentation, detect_quote, Indentation, Quote};
|
||||
use crate::SourceCodeLocator;
|
||||
|
||||
#[test]
|
||||
fn indentation() {
|
||||
let contents = r#"x = 1"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(detect_indentation(contents, &locator), None);
|
||||
|
||||
let contents = r#"
|
||||
if True:
|
||||
pass
|
||||
"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
detect_indentation(contents, &locator),
|
||||
Some(Indentation(" ".to_string()))
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
if True:
|
||||
pass
|
||||
"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
detect_indentation(contents, &locator),
|
||||
Some(Indentation(" ".to_string()))
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
if True:
|
||||
pass
|
||||
"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
detect_indentation(contents, &locator),
|
||||
Some(Indentation("\t".to_string()))
|
||||
);
|
||||
|
||||
// TODO(charlie): Should non-significant whitespace be detected?
|
||||
let contents = r#"
|
||||
x = (
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
)
|
||||
"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(detect_indentation(contents, &locator), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quote() {
|
||||
let contents = r#"x = 1"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(detect_quote(contents, &locator), None);
|
||||
|
||||
let contents = r#"x = '1'"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(detect_quote(contents, &locator), Some(Quote::Single));
|
||||
|
||||
let contents = r#"x = "1""#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(detect_quote(contents, &locator), Some(Quote::Double));
|
||||
|
||||
let contents = r#"
|
||||
def f():
|
||||
"""Docstring."""
|
||||
pass
|
||||
"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(detect_quote(contents, &locator), Some(Quote::Double));
|
||||
}
|
||||
}
|
||||
68
src/vendor/bytes.rs
vendored
Normal file
68
src/vendor/bytes.rs
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
//! Vendored from [bytes.rs in rustpython-common](https://github.com/RustPython/RustPython/blob/1d8269fb729c91fc56064e975172d3a11bd62d07/common/src/bytes.rs).
|
||||
//! The only changes we make are to remove dead code and make the default quote
|
||||
//! type configurable.
|
||||
|
||||
use crate::vendor;
|
||||
use crate::vendor::str::Quote;
|
||||
|
||||
pub fn repr(b: &[u8], quote: Quote) -> String {
|
||||
repr_with(b, &[], "", quote)
|
||||
}
|
||||
|
||||
pub fn repr_with(b: &[u8], prefixes: &[&str], suffix: &str, quote: Quote) -> String {
|
||||
use std::fmt::Write;
|
||||
|
||||
let mut out_len = 0usize;
|
||||
let mut squote = 0;
|
||||
let mut dquote = 0;
|
||||
|
||||
for &ch in b {
|
||||
let incr = match ch {
|
||||
b'\'' => {
|
||||
squote += 1;
|
||||
1
|
||||
}
|
||||
b'"' => {
|
||||
dquote += 1;
|
||||
1
|
||||
}
|
||||
b'\\' | b'\t' | b'\r' | b'\n' => 2,
|
||||
0x20..=0x7e => 1,
|
||||
_ => 4, // \xHH
|
||||
};
|
||||
// TODO: OverflowError
|
||||
out_len = out_len.checked_add(incr).unwrap();
|
||||
}
|
||||
|
||||
let (quote, num_escaped_quotes) = vendor::str::choose_quotes_for_repr(squote, dquote, quote);
|
||||
// we'll be adding backslashes in front of the existing inner quotes
|
||||
out_len += num_escaped_quotes;
|
||||
|
||||
// 3 is for b prefix + outer quotes
|
||||
out_len += 3 + prefixes.iter().map(|s| s.len()).sum::<usize>() + suffix.len();
|
||||
|
||||
let mut res = String::with_capacity(out_len);
|
||||
res.extend(prefixes.iter().copied());
|
||||
res.push('b');
|
||||
res.push(quote);
|
||||
for &ch in b {
|
||||
match ch {
|
||||
b'\t' => res.push_str("\\t"),
|
||||
b'\n' => res.push_str("\\n"),
|
||||
b'\r' => res.push_str("\\r"),
|
||||
// printable ascii range
|
||||
0x20..=0x7e => {
|
||||
let ch = ch as char;
|
||||
if ch == quote || ch == '\\' {
|
||||
res.push('\\');
|
||||
}
|
||||
res.push(ch);
|
||||
}
|
||||
_ => write!(res, "\\x{ch:02x}").unwrap(),
|
||||
}
|
||||
}
|
||||
res.push(quote);
|
||||
res.push_str(suffix);
|
||||
|
||||
res
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Implementation of Printf-Style string formatting
|
||||
//! as per the [Python Docs](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting).
|
||||
//! Vendored from [cformat.rs in rustpython-vm](https://github.com/RustPython/RustPython/blob/f54b5556e28256763c5506813ea977c9e1445af0/vm/src/cformat.rs).
|
||||
//! The only changes we make are to remove dead code and code involving the vm.
|
||||
//! The only changes we make are to remove dead code and code involving the VM.
|
||||
use std::fmt;
|
||||
use std::iter::{Enumerate, Peekable};
|
||||
use std::str::FromStr;
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Vendored from [format.rs in rustpython-vm](https://github.com/RustPython/RustPython/blob/f54b5556e28256763c5506813ea977c9e1445af0/vm/src/format.rs).
|
||||
//! The only changes we make are to remove dead code and code involving the vm.
|
||||
//! The only changes we make are to remove dead code and code involving the VM.
|
||||
use itertools::{Itertools, PeekingNext};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user