Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e00bcd19f5 | ||
|
|
4550581be2 | ||
|
|
b42d77a4c6 | ||
|
|
e473df1fe9 | ||
|
|
d448281b33 | ||
|
|
94597fefc1 | ||
|
|
add0bdeeb7 | ||
|
|
6a180b95d1 | ||
|
|
416c338237 | ||
|
|
9948be0145 | ||
|
|
f50ff61056 | ||
|
|
017fec2bc5 | ||
|
|
f9def0a139 | ||
|
|
d4fc485a76 | ||
|
|
0827d0beef | ||
|
|
b4a46ab6f0 | ||
|
|
f6e14edc3e | ||
|
|
878a94f9cb | ||
|
|
79ca66ace5 | ||
|
|
c68c6b5424 | ||
|
|
bad5723d80 | ||
|
|
2d83f99dbf | ||
|
|
5123b38758 | ||
|
|
e9a4c8ba13 | ||
|
|
927d716edd | ||
|
|
df6a48fced | ||
|
|
91a8277ac0 | ||
|
|
5797884262 | ||
|
|
5aa8455258 | ||
|
|
8fd713739b | ||
|
|
5de1fcd653 | ||
|
|
032f4f3f12 | ||
|
|
621db96e7f | ||
|
|
05867ef260 | ||
|
|
0cd8b75f06 | ||
|
|
5f07e1d6b5 | ||
|
|
1ce4585c88 | ||
|
|
f3f010cdf5 | ||
|
|
7e5e03fb15 |
28
.github/workflows/ci.yaml
vendored
28
.github/workflows/ci.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
- uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-cargo
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo build --release
|
||||
- run: cargo build --all --release
|
||||
|
||||
cargo_fmt:
|
||||
name: "cargo fmt"
|
||||
@@ -36,7 +36,10 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
toolchain: nightly-2022-11-01
|
||||
override: true
|
||||
components: rustfmt
|
||||
- uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-cargo
|
||||
@@ -49,7 +52,7 @@ jobs:
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo fmt --check
|
||||
- run: cargo fmt --all --check
|
||||
|
||||
cargo_clippy:
|
||||
name: "cargo clippy"
|
||||
@@ -58,7 +61,10 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
toolchain: nightly-2022-11-01
|
||||
override: true
|
||||
components: clippy
|
||||
- uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-cargo
|
||||
@@ -71,7 +77,7 @@ jobs:
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo clippy -- -D warnings
|
||||
- run: cargo clippy --all -- -D warnings
|
||||
|
||||
cargo_test:
|
||||
name: "cargo test"
|
||||
@@ -80,7 +86,9 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
toolchain: nightly-2022-11-01
|
||||
override: true
|
||||
- uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-cargo
|
||||
@@ -93,7 +101,7 @@ jobs:
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo test
|
||||
- run: cargo test --all
|
||||
|
||||
maturin_build:
|
||||
name: "maturin build"
|
||||
@@ -102,7 +110,9 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
toolchain: nightly-2022-11-01
|
||||
override: true
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
298
.github/workflows/flake8-to-ruff.yaml
vendored
Normal file
298
.github/workflows/flake8-to-ruff.yaml
vendored
Normal file
@@ -0,0 +1,298 @@
|
||||
name: "[flake8-to-ruff] Release"
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PACKAGE_NAME: flake8-to-ruff
|
||||
CRATE_NAME: flake8_to_ruff
|
||||
PYTHON_VERSION: "3.7" # to build abi3 wheels
|
||||
|
||||
jobs:
|
||||
macos-x86_64:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
default: true
|
||||
- name: Build wheels - x86_64
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
target: x86_64
|
||||
args: --release --out dist --sdist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
maturin-version: "v0.13.0"
|
||||
- name: Install built wheel - x86_64
|
||||
run: |
|
||||
pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
macos-universal:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
default: true
|
||||
- name: Build wheels - universal2
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
args: --release --universal2 --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
maturin-version: "v0.13.0"
|
||||
- name: Install built wheel - universal2
|
||||
run: |
|
||||
pip install dist/${{ env.CRATE_NAME }}-*universal2.whl --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x64, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: ${{ matrix.target }}
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
default: true
|
||||
- name: Build wheels
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
maturin-version: "v0.13.0"
|
||||
- name: Install built wheel
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x86_64, i686]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- name: Build wheels
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: auto
|
||||
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
maturin-version: "v0.13.0"
|
||||
- name: Install built wheel
|
||||
if: matrix.target == 'x86_64'
|
||||
run: |
|
||||
pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
linux-cross:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [aarch64, armv7, s390x, ppc64le, ppc64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: Build wheels
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: auto
|
||||
args: --no-default-features --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
maturin-version: "v0.13.0"
|
||||
- uses: uraimo/run-on-arch-action@v2.0.5
|
||||
if: matrix.target != 'ppc64'
|
||||
name: Install built wheel
|
||||
with:
|
||||
arch: ${{ matrix.target }}
|
||||
distro: ubuntu20.04
|
||||
githubToken: ${{ github.token }}
|
||||
install: |
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends python3 python3-pip
|
||||
pip3 install -U pip
|
||||
run: |
|
||||
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
musllinux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- x86_64-unknown-linux-musl
|
||||
- i686-unknown-linux-musl
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- name: Build wheels
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: musllinux_1_2
|
||||
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
maturin-version: "v0.13.0"
|
||||
- name: Install built wheel
|
||||
if: matrix.target == 'x86_64-unknown-linux-musl'
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
image: alpine:latest
|
||||
options: -v ${{ github.workspace }}:/io -w /io
|
||||
run: |
|
||||
apk add py3-pip
|
||||
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links /io/dist/ --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
musllinux-cross:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- target: aarch64-unknown-linux-musl
|
||||
arch: aarch64
|
||||
- target: armv7-unknown-linux-musleabihf
|
||||
arch: armv7
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: Build wheels
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
manylinux: musllinux_1_2
|
||||
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
maturin-version: "v0.13.0"
|
||||
- uses: uraimo/run-on-arch-action@master
|
||||
name: Install built wheel
|
||||
with:
|
||||
arch: ${{ matrix.platform.arch }}
|
||||
distro: alpine_latest
|
||||
githubToken: ${{ github.token }}
|
||||
install: |
|
||||
apk add py3-pip
|
||||
run: |
|
||||
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
pypy:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
target: [x86_64, aarch64]
|
||||
python-version:
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
exclude:
|
||||
- os: macos-latest
|
||||
target: aarch64
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: pypy${{ matrix.python-version }}
|
||||
- name: Build wheels
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
maturin-version: "v0.13.0"
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: auto
|
||||
args: --release --out dist -i pypy${{ matrix.python-version }} -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
- name: Install built wheel
|
||||
if: matrix.target == 'x86_64'
|
||||
run: |
|
||||
pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- macos-universal
|
||||
- macos-x86_64
|
||||
- windows
|
||||
- linux
|
||||
- linux-cross
|
||||
- musllinux
|
||||
- musllinux-cross
|
||||
- pypy
|
||||
steps:
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
- uses: actions/setup-python@v4
|
||||
- name: Publish to PyPi
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.FLAKE8_TO_RUFF_TOKEN }}
|
||||
run: |
|
||||
pip install --upgrade twine
|
||||
twine upload --skip-existing *
|
||||
@@ -1,8 +1,6 @@
|
||||
name: Release
|
||||
name: "[ruff] Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
create:
|
||||
tags:
|
||||
- v*
|
||||
@@ -297,7 +295,7 @@ jobs:
|
||||
- name: Publish to PyPi
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
||||
TWINE_PASSWORD: ${{ secrets.RUFF_TOKEN }}
|
||||
run: |
|
||||
pip install --upgrade twine
|
||||
twine upload --skip-existing *
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.93
|
||||
rev: v0.0.97
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
23
Cargo.lock
generated
23
Cargo.lock
generated
@@ -571,6 +571,12 @@ dependencies = [
|
||||
"cache-padded",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "configparser"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5458d9d1a587efaf5091602c59d299696a3877a439c8f6d461a2d3cce11df87a"
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.15.2"
|
||||
@@ -912,6 +918,21 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.97-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.15",
|
||||
"configparser",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"ruff",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.24"
|
||||
@@ -2190,7 +2211,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.93"
|
||||
version = "0.0.97"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/flake8_to_ruff",
|
||||
]
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.93"
|
||||
version = "0.0.97"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
42
README.md
42
README.md
@@ -89,7 +89,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.93
|
||||
rev: v0.0.97
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -108,10 +108,7 @@ For example, you could configure Ruff to only enforce a subset of rules with:
|
||||
line-length = 88
|
||||
select = ["E", "F"]
|
||||
ignore = ["E501"]
|
||||
per-file-ignores = [
|
||||
"__init__.py:F401",
|
||||
"path/to/file.py:F401"
|
||||
]
|
||||
per-file-ignores = {"__init__.py" = ["F401"], "path/to/file.py" = ["F401"]}
|
||||
```
|
||||
|
||||
Plugin configurations should be expressed as subsections, e.g.:
|
||||
@@ -395,17 +392,17 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| C400 | UnnecessaryGeneratorList | Unnecessary generator (rewrite as a `list` comprehension) | |
|
||||
| C401 | UnnecessaryGeneratorSet | Unnecessary generator (rewrite as a `set` comprehension) | |
|
||||
| C402 | UnnecessaryGeneratorDict | Unnecessary generator (rewrite as a `dict` comprehension) | |
|
||||
| C403 | UnnecessaryListComprehensionSet | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | |
|
||||
| C400 | UnnecessaryGeneratorList | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
|
||||
| C401 | UnnecessaryGeneratorSet | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
|
||||
| C402 | UnnecessaryGeneratorDict | Unnecessary generator (rewrite as a `dict` comprehension) | 🛠 |
|
||||
| C403 | UnnecessaryListComprehensionSet | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | 🛠 |
|
||||
| C404 | UnnecessaryListComprehensionDict | Unnecessary `list` comprehension (rewrite as a `dict` comprehension) | |
|
||||
| C405 | UnnecessaryLiteralSet | Unnecessary `(list\|tuple)` literal (rewrite as a `set` literal) | |
|
||||
| C405 | UnnecessaryLiteralSet | Unnecessary `(list\|tuple)` literal (rewrite as a `set` literal) | 🛠 |
|
||||
| C406 | UnnecessaryLiteralDict | Unnecessary `(list\|tuple)` literal (rewrite as a `dict` literal) | |
|
||||
| C408 | UnnecessaryCollectionCall | Unnecessary `(dict\|list\|tuple)` call (rewrite as a literal) | |
|
||||
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary `(list\|tuple)` literal passed to `tuple()` (remove the outer call to `tuple()`) | |
|
||||
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary `(list\|tuple)` literal passed to `list()` (rewrite as a `list` literal) | |
|
||||
| C411 | UnnecessaryListCall | Unnecessary `list` call (remove the outer call to `list()`) | |
|
||||
| C408 | UnnecessaryCollectionCall | Unnecessary `(dict\|list\|tuple)` call (rewrite as a literal) | 🛠 |
|
||||
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary `(list\|tuple)` literal passed to `tuple()` (remove the outer call to `tuple()`) | 🛠 |
|
||||
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary `(list\|tuple)` literal passed to `list()` (rewrite as a `list` literal) | 🛠 |
|
||||
| C411 | UnnecessaryListCall | Unnecessary `list` call (remove the outer call to `list()`) | 🛠 |
|
||||
| C413 | UnnecessaryCallAroundSorted | Unnecessary `(list\|reversed)` call around `sorted()` | |
|
||||
| C414 | UnnecessaryDoubleCastOrProcess | Unnecessary `(list\|reversed\|set\|sorted\|tuple)` call within `(list\|set\|sorted\|tuple)()` | |
|
||||
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within `(reversed\|set\|sorted)()` | |
|
||||
@@ -524,7 +521,7 @@ including:
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (10/34)
|
||||
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
|
||||
|
||||
Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis Flake8:
|
||||
@@ -548,7 +545,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32)
|
||||
|
||||
Ruff also implements the functionality that you get from [`yesqa`](https://github.com/asottile/yesqa),
|
||||
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34).
|
||||
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (10/34).
|
||||
|
||||
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
|
||||
|
||||
@@ -640,16 +637,21 @@ extend-ignore = [
|
||||
|
||||
## Development
|
||||
|
||||
Ruff is written in Rust (1.63.0). You'll need to install the [Rust toolchain](https://www.rust-lang.org/tools/install)
|
||||
Ruff is written in Rust (1.64.0). You'll need to install the [Rust toolchain](https://www.rust-lang.org/tools/install)
|
||||
for development.
|
||||
|
||||
Assuming you have `cargo` installed, you can run:
|
||||
|
||||
```shell
|
||||
cargo run resources/test/fixtures
|
||||
cargo fmt
|
||||
cargo clippy
|
||||
cargo test
|
||||
```
|
||||
|
||||
For development, we use [nightly Rust](https://rust-lang.github.io/rustup/concepts/channels.html#working-with-nightly-rust):
|
||||
|
||||
```shell
|
||||
cargo +nightly fmt
|
||||
cargo +nightly clippy
|
||||
cargo +nightly test
|
||||
```
|
||||
|
||||
## Releases
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
use ruff::fs;
|
||||
use ruff::source_code_locator::compute_offsets;
|
||||
|
||||
|
||||
2964
crates/flake8_to_ruff/Cargo.lock
generated
Normal file
2964
crates/flake8_to_ruff/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
crates/flake8_to_ruff/Cargo.toml
Normal file
22
crates/flake8_to_ruff/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.97-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "flake8_to_ruff"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.60" }
|
||||
clap = { version = "4.0.1", features = ["derive"] }
|
||||
configparser = { version = "3.0.2" }
|
||||
once_cell = { version = "1.13.1" }
|
||||
regex = { version = "1.6.0" }
|
||||
ruff = { path = "../..", default-features = false }
|
||||
serde = { version = "1.0.143", features = ["derive"] }
|
||||
serde_json = { version = "1.0.83" }
|
||||
toml = { version = "0.5.9" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
[features]
|
||||
47
crates/flake8_to_ruff/README.md
Normal file
47
crates/flake8_to_ruff/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# flake8-to-ruff
|
||||
|
||||
Convert existing Flake8 configuration files (`setup.cfg`, `tox.ini`, or `.flake8`) for use with
|
||||
[Ruff](https://github.com/charliermarsh/ruff).
|
||||
|
||||
Generates a Ruff-compatible `pyproject.toml` section.
|
||||
|
||||
## Installation and Usage
|
||||
|
||||
### Installation
|
||||
|
||||
Available as [`flake8-to-ruff`](https://pypi.org/project/flake8-to-ruff/) on PyPI:
|
||||
|
||||
```shell
|
||||
pip install flake8-to-ruff
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
To run Ruff, try any of the following:
|
||||
|
||||
```shell
|
||||
flake8-to-ruff path/to/setup.cfg
|
||||
flake8-to-ruff path/to/tox.ini
|
||||
flake8-to-ruff path/to/.flake8
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
1. Ruff only supports a subset of the Flake configuration options. `flake8-to-ruff` will warn on and
|
||||
ignore unsupported options in the `.flake8` file (or equivalent). (Similarly, Ruff has a few
|
||||
configuration options that don't exist in Flake8.)
|
||||
2. Ruff will omit any error codes that are unimplemented or unsupported by Ruff, including error
|
||||
codes from unsupported plugins. (See the [Ruff README](https://github.com/charliermarsh/ruff#user-content-how-does-ruff-compare-to-flake8)
|
||||
for the complete list of supported plugins.)
|
||||
3. `flake8-to-ruff` does not auto-detect your Flake8 plugins, so any reliance on Flake8 plugins that
|
||||
implicitly enable third-party checks will be ignored. Instead, add those error codes to your
|
||||
`select` or `extend-select` fields, so that `flake8-to-ruff` can pick them up.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome and hugely appreciated. To get started, check out the
|
||||
[contributing guidelines](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTING.md).
|
||||
33
crates/flake8_to_ruff/pyproject.toml
Normal file
33
crates/flake8_to_ruff/pyproject.toml
Normal file
@@ -0,0 +1,33 @@
|
||||
[project]
|
||||
name = "flake8-to-ruff"
|
||||
keywords = ["automation", "flake8", "pycodestyle", "pyflakes", "pylint", "clippy"]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Topic :: Software Development :: Quality Assurance",
|
||||
]
|
||||
author = "Charlie Marsh"
|
||||
author_email = "charlie.r.marsh@gmail.com"
|
||||
description = "Convert existing Flake8 configuration to Ruff."
|
||||
requires-python = ">=3.7"
|
||||
|
||||
[project.urls]
|
||||
repository = "https://github.com/charliermarsh/ruff#subdirectory=crates/flake8_to_ruff"
|
||||
|
||||
[build-system]
|
||||
requires = ["maturin>=0.13,<0.14"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[tool.maturin]
|
||||
bindings = "bin"
|
||||
strip = true
|
||||
243
crates/flake8_to_ruff/src/converter.rs
Normal file
243
crates/flake8_to_ruff/src/converter.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use ruff::flake8_quotes::settings::Quote;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::pyproject::Pyproject;
|
||||
use ruff::{flake8_quotes, pep8_naming};
|
||||
|
||||
use crate::parser;
|
||||
|
||||
pub fn convert(config: HashMap<String, HashMap<String, Option<String>>>) -> Result<Pyproject> {
|
||||
// Extract the Flake8 section.
|
||||
let flake8 = config
|
||||
.get("flake8")
|
||||
.expect("Unable to find flake8 section in INI file.");
|
||||
|
||||
// Parse each supported option.
|
||||
let mut options: Options = Default::default();
|
||||
let mut flake8_quotes: flake8_quotes::settings::Options = Default::default();
|
||||
let mut pep8_naming: pep8_naming::settings::Options = Default::default();
|
||||
for (key, value) in flake8 {
|
||||
if let Some(value) = value {
|
||||
match key.as_str() {
|
||||
// flake8
|
||||
"max-line-length" | "max_line_length" => match value.clone().parse::<usize>() {
|
||||
Ok(line_length) => options.line_length = Some(line_length),
|
||||
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
|
||||
},
|
||||
"select" => {
|
||||
options.select = Some(parser::parse_prefix_codes(value.as_ref()));
|
||||
}
|
||||
"extend-select" | "extend_select" => {
|
||||
options.extend_select = Some(parser::parse_prefix_codes(value.as_ref()));
|
||||
}
|
||||
"ignore" => {
|
||||
options.ignore = Some(parser::parse_prefix_codes(value.as_ref()));
|
||||
}
|
||||
"extend-ignore" | "extend_ignore" => {
|
||||
options.extend_ignore = Some(parser::parse_prefix_codes(value.as_ref()));
|
||||
}
|
||||
"exclude" => {
|
||||
options.exclude = Some(parser::parse_strings(value.as_ref()));
|
||||
}
|
||||
"extend-exclude" | "extend_exclude" => {
|
||||
options.extend_exclude = Some(parser::parse_strings(value.as_ref()));
|
||||
}
|
||||
"per-file-ignores" | "per_file_ignores" => {
|
||||
match parser::parse_files_to_codes_mapping(value.as_ref()) {
|
||||
Ok(per_file_ignores) => {
|
||||
options.per_file_ignores =
|
||||
Some(parser::collect_per_file_ignores(per_file_ignores))
|
||||
}
|
||||
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
|
||||
}
|
||||
}
|
||||
// flake8-quotes
|
||||
"quotes" | "inline-quotes" | "inline_quotes" => match value.trim() {
|
||||
"'" | "single" => flake8_quotes.inline_quotes = Some(Quote::Single),
|
||||
"\"" | "double" => flake8_quotes.inline_quotes = Some(Quote::Single),
|
||||
_ => eprintln!("Unexpected '{key}' value: {value}"),
|
||||
},
|
||||
"multiline-quotes" | "multiline_quotes" => match value.trim() {
|
||||
"'" | "single" => flake8_quotes.multiline_quotes = Some(Quote::Single),
|
||||
"\"" | "double" => flake8_quotes.multiline_quotes = Some(Quote::Single),
|
||||
_ => eprintln!("Unexpected '{key}' value: {value}"),
|
||||
},
|
||||
"docstring-quotes" | "docstring_quotes" => match value.trim() {
|
||||
"'" | "single" => flake8_quotes.docstring_quotes = Some(Quote::Single),
|
||||
"\"" | "double" => flake8_quotes.docstring_quotes = Some(Quote::Single),
|
||||
_ => eprintln!("Unexpected '{key}' value: {value}"),
|
||||
},
|
||||
"avoid-escape" | "avoid_escape" => match value.trim() {
|
||||
"true" => flake8_quotes.avoid_escape = Some(true),
|
||||
"false" => flake8_quotes.avoid_escape = Some(false),
|
||||
_ => eprintln!("Unexpected '{key}' value: {value}"),
|
||||
},
|
||||
// pep8-naming
|
||||
"ignore-names" | "ignore_names" => {
|
||||
pep8_naming.ignore_names = Some(parser::parse_strings(value.as_ref()));
|
||||
}
|
||||
"classmethod-decorators" | "classmethod_decorators" => {
|
||||
pep8_naming.classmethod_decorators =
|
||||
Some(parser::parse_strings(value.as_ref()));
|
||||
}
|
||||
"staticmethod-decorators" | "staticmethod_decorators" => {
|
||||
pep8_naming.staticmethod_decorators =
|
||||
Some(parser::parse_strings(value.as_ref()));
|
||||
}
|
||||
// Unknown
|
||||
_ => eprintln!("Skipping unsupported property: {key}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if flake8_quotes != Default::default() {
|
||||
options.flake8_quotes = Some(flake8_quotes);
|
||||
}
|
||||
if pep8_naming != Default::default() {
|
||||
options.pep8_naming = Some(pep8_naming);
|
||||
}
|
||||
|
||||
// Create the pyproject.toml.
|
||||
Ok(Pyproject::new(options))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use ruff::flake8_quotes;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::pyproject::Pyproject;
|
||||
|
||||
use crate::converter::convert;
|
||||
|
||||
#[test]
|
||||
fn it_converts_empty() -> Result<()> {
|
||||
let actual = convert(HashMap::from([("flake8".to_string(), HashMap::from([]))]))?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_converts_dashes() -> Result<()> {
|
||||
let actual = convert(HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]),
|
||||
)]))?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: Some(100),
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_converts_underscores() -> Result<()> {
|
||||
let actual = convert(HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]),
|
||||
)]))?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: Some(100),
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_ignores_parse_errors() -> Result<()> {
|
||||
let actual = convert(HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]),
|
||||
)]))?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_converts_extensions() -> Result<()> {
|
||||
let actual = convert(HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||
)]))?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: Some(flake8_quotes::settings::Options {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
docstring_quotes: None,
|
||||
avoid_escape: None,
|
||||
}),
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
4
crates/flake8_to_ruff/src/lib.rs
Normal file
4
crates/flake8_to_ruff/src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#![allow(clippy::collapsible_if, clippy::collapsible_else_if)]
|
||||
|
||||
pub mod converter;
|
||||
mod parser;
|
||||
35
crates/flake8_to_ruff/src/main.rs
Normal file
35
crates/flake8_to_ruff/src/main.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
//! Utility to generate Ruff's pyproject.toml section from a Flake8 INI file.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use configparser::ini::Ini;
|
||||
use flake8_to_ruff::converter;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(
|
||||
about = "Convert existing Flake8 configuration to Ruff.",
|
||||
long_about = None
|
||||
)]
|
||||
struct Cli {
|
||||
/// Path to the Flake8 configuration file (e.g., 'setup.cfg', 'tox.ini', or
|
||||
/// '.flake8').
|
||||
#[arg(required = true)]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
// Read the INI file.
|
||||
let mut ini = Ini::new_cs();
|
||||
ini.set_multiline(true);
|
||||
let config = ini.load(cli.file).map_err(|msg| anyhow::anyhow!(msg))?;
|
||||
|
||||
// Create the pyproject.toml.
|
||||
let pyproject = converter::convert(config)?;
|
||||
println!("{}", toml::to_string_pretty(&pyproject)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
373
crates/flake8_to_ruff/src/parser.rs
Normal file
373
crates/flake8_to_ruff/src/parser.rs
Normal file
@@ -0,0 +1,373 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
use ruff::settings::types::PatternPrefixPair;
|
||||
|
||||
static COMMA_SEPARATED_LIST_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").unwrap());
|
||||
|
||||
/// Parse a comma-separated list of `CheckCodePrefix` values (e.g.,
|
||||
/// "F401,E501").
|
||||
pub fn parse_prefix_codes(value: &str) -> Vec<CheckCodePrefix> {
|
||||
let mut codes: Vec<CheckCodePrefix> = vec![];
|
||||
for code in COMMA_SEPARATED_LIST_RE.split(value) {
|
||||
let code = code.trim();
|
||||
if code.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Ok(code) = CheckCodePrefix::from_str(code) {
|
||||
codes.push(code);
|
||||
} else {
|
||||
eprintln!("Unsupported prefix code: {code}");
|
||||
}
|
||||
}
|
||||
codes
|
||||
}
|
||||
|
||||
/// Parse a comma-separated list of strings (e.g., "__init__.py,__main__.py").
|
||||
pub fn parse_strings(value: &str) -> Vec<String> {
|
||||
COMMA_SEPARATED_LIST_RE
|
||||
.split(value)
|
||||
.map(|part| part.trim())
|
||||
.filter(|part| !part.is_empty())
|
||||
.map(String::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Token {
|
||||
token_name: TokenType,
|
||||
src: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum TokenType {
|
||||
Code,
|
||||
File,
|
||||
Colon,
|
||||
Comma,
|
||||
Ws,
|
||||
Eof,
|
||||
}
|
||||
|
||||
struct State {
|
||||
seen_sep: bool,
|
||||
seen_colon: bool,
|
||||
filenames: Vec<String>,
|
||||
codes: Vec<String>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
seen_sep: true,
|
||||
seen_colon: false,
|
||||
filenames: vec![],
|
||||
codes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the list of `StrCheckCodePair` pairs for the current state.
|
||||
fn parse(&self) -> Vec<PatternPrefixPair> {
|
||||
let mut codes: Vec<PatternPrefixPair> = vec![];
|
||||
for code in &self.codes {
|
||||
match CheckCodePrefix::from_str(code) {
|
||||
Ok(code) => {
|
||||
for filename in &self.filenames {
|
||||
codes.push(PatternPrefixPair {
|
||||
pattern: filename.clone(),
|
||||
prefix: code.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(_) => eprintln!("Skipping unrecognized prefix: {}", code),
|
||||
}
|
||||
}
|
||||
codes
|
||||
}
|
||||
}
|
||||
|
||||
/// Tokenize the raw 'files-to-codes' mapping.
|
||||
fn tokenize_files_to_codes_mapping(value: &str) -> Vec<Token> {
|
||||
let mut tokens = vec![];
|
||||
let mut i = 0;
|
||||
while i < value.len() {
|
||||
for (token_re, token_name) in [
|
||||
(
|
||||
Regex::new(r"([A-Z]+[0-9]*)(?:$|\s|,)").unwrap(),
|
||||
TokenType::Code,
|
||||
),
|
||||
(Regex::new(r"([^\s:,]+)").unwrap(), TokenType::File),
|
||||
(Regex::new(r"(\s*:\s*)").unwrap(), TokenType::Colon),
|
||||
(Regex::new(r"(\s*,\s*)").unwrap(), TokenType::Comma),
|
||||
(Regex::new(r"(\s+)").unwrap(), TokenType::Ws),
|
||||
] {
|
||||
if let Some(cap) = token_re.captures(&value[i..]) {
|
||||
let mat = cap.get(1).unwrap();
|
||||
if mat.start() == 0 {
|
||||
tokens.push(Token {
|
||||
token_name,
|
||||
src: mat.as_str().to_string().trim().to_string(),
|
||||
});
|
||||
i += mat.end();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tokens.push(Token {
|
||||
token_name: TokenType::Eof,
|
||||
src: "".to_string(),
|
||||
});
|
||||
tokens
|
||||
}
|
||||
|
||||
/// Parse a 'files-to-codes' mapping, mimicking Flake8's internal logic.
|
||||
///
|
||||
/// See: https://github.com/PyCQA/flake8/blob/7dfe99616fc2f07c0017df2ba5fa884158f3ea8a/src/flake8/utils.py#L45
|
||||
pub fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<PatternPrefixPair>> {
|
||||
if value.trim().is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
let mut codes: Vec<PatternPrefixPair> = vec![];
|
||||
let mut state = State::new();
|
||||
for token in tokenize_files_to_codes_mapping(value) {
|
||||
if matches!(token.token_name, TokenType::Comma | TokenType::Ws) {
|
||||
state.seen_sep = true;
|
||||
} else if !state.seen_colon {
|
||||
if matches!(token.token_name, TokenType::Colon) {
|
||||
state.seen_colon = true;
|
||||
state.seen_sep = true;
|
||||
} else if state.seen_sep && matches!(token.token_name, TokenType::File) {
|
||||
state.filenames.push(token.src);
|
||||
state.seen_sep = false;
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Unexpected token: {:?}", token.token_name));
|
||||
}
|
||||
} else {
|
||||
if matches!(token.token_name, TokenType::Eof) {
|
||||
codes.extend(state.parse());
|
||||
state = State::new();
|
||||
} else if state.seen_sep && matches!(token.token_name, TokenType::Code) {
|
||||
state.codes.push(token.src);
|
||||
state.seen_sep = false;
|
||||
} else if state.seen_sep && matches!(token.token_name, TokenType::File) {
|
||||
codes.extend(state.parse());
|
||||
state = State::new();
|
||||
state.filenames.push(token.src);
|
||||
state.seen_sep = false;
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Unexpected token: {:?}", token.token_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(codes)
|
||||
}
|
||||
|
||||
/// Collect a list of `PatternPrefixPair` structs as a `BTreeMap`.
|
||||
pub fn collect_per_file_ignores(
|
||||
pairs: Vec<PatternPrefixPair>,
|
||||
) -> BTreeMap<String, Vec<CheckCodePrefix>> {
|
||||
let mut per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> = BTreeMap::new();
|
||||
for pair in pairs {
|
||||
per_file_ignores
|
||||
.entry(pair.pattern)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(pair.prefix);
|
||||
}
|
||||
per_file_ignores
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
use ruff::settings::types::PatternPrefixPair;
|
||||
|
||||
use crate::parser::{parse_files_to_codes_mapping, parse_prefix_codes, parse_strings};
|
||||
|
||||
#[test]
|
||||
fn it_parses_prefix_codes() {
|
||||
let actual = parse_prefix_codes("");
|
||||
let expected: Vec<CheckCodePrefix> = vec![];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_prefix_codes(" ");
|
||||
let expected: Vec<CheckCodePrefix> = vec![];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_prefix_codes("F401");
|
||||
let expected = vec![CheckCodePrefix::F401];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_prefix_codes("F401,");
|
||||
let expected = vec![CheckCodePrefix::F401];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_prefix_codes("F401,E501");
|
||||
let expected = vec![CheckCodePrefix::F401, CheckCodePrefix::E501];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_prefix_codes("F401, E501");
|
||||
let expected = vec![CheckCodePrefix::F401, CheckCodePrefix::E501];
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_parses_strings() {
|
||||
let actual = parse_strings("");
|
||||
let expected: Vec<String> = vec![];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_strings(" ");
|
||||
let expected: Vec<String> = vec![];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_strings("__init__.py");
|
||||
let expected = vec!["__init__.py".to_string()];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_strings("__init__.py,");
|
||||
let expected = vec!["__init__.py".to_string()];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_strings("__init__.py,__main__.py");
|
||||
let expected = vec!["__init__.py".to_string(), "__main__.py".to_string()];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_strings("__init__.py, __main__.py");
|
||||
let expected = vec!["__init__.py".to_string(), "__main__.py".to_string()];
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_parse_files_to_codes_mapping() -> Result<()> {
|
||||
let actual = parse_files_to_codes_mapping("")?;
|
||||
let expected: Vec<PatternPrefixPair> = vec![];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_files_to_codes_mapping(" ")?;
|
||||
let expected: Vec<PatternPrefixPair> = vec![];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// Ex) locust
|
||||
let actual = parse_files_to_codes_mapping(
|
||||
"per-file-ignores =
|
||||
locust/test/*: F841
|
||||
examples/*: F841
|
||||
*.pyi: E302,E704"
|
||||
.strip_prefix("per-file-ignores =")
|
||||
.unwrap(),
|
||||
)?;
|
||||
let expected: Vec<PatternPrefixPair> = vec![
|
||||
PatternPrefixPair {
|
||||
pattern: "locust/test/*".to_string(),
|
||||
prefix: CheckCodePrefix::F841,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "examples/*".to_string(),
|
||||
prefix: CheckCodePrefix::F841,
|
||||
},
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// Ex) celery
|
||||
let actual = parse_files_to_codes_mapping(
|
||||
"per-file-ignores =
|
||||
t/*,setup.py,examples/*,docs/*,extra/*:
|
||||
D,"
|
||||
.strip_prefix("per-file-ignores =")
|
||||
.unwrap(),
|
||||
)?;
|
||||
let expected: Vec<PatternPrefixPair> = vec![
|
||||
PatternPrefixPair {
|
||||
pattern: "t/*".to_string(),
|
||||
prefix: CheckCodePrefix::D,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "setup.py".to_string(),
|
||||
prefix: CheckCodePrefix::D,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "examples/*".to_string(),
|
||||
prefix: CheckCodePrefix::D,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "docs/*".to_string(),
|
||||
prefix: CheckCodePrefix::D,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "extra/*".to_string(),
|
||||
prefix: CheckCodePrefix::D,
|
||||
},
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// Ex) scrapy
|
||||
let actual = parse_files_to_codes_mapping(
|
||||
"per-file-ignores =
|
||||
scrapy/__init__.py:E402
|
||||
scrapy/core/downloader/handlers/http.py:F401
|
||||
scrapy/http/__init__.py:F401
|
||||
scrapy/linkextractors/__init__.py:E402,F401
|
||||
scrapy/selector/__init__.py:F401
|
||||
scrapy/spiders/__init__.py:E402,F401
|
||||
scrapy/utils/url.py:F403,F405
|
||||
tests/test_loader.py:E741"
|
||||
.strip_prefix("per-file-ignores =")
|
||||
.unwrap(),
|
||||
)?;
|
||||
let expected: Vec<PatternPrefixPair> = vec![
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/__init__.py".to_string(),
|
||||
prefix: CheckCodePrefix::E402,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/core/downloader/handlers/http.py".to_string(),
|
||||
prefix: CheckCodePrefix::F401,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/http/__init__.py".to_string(),
|
||||
prefix: CheckCodePrefix::F401,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/linkextractors/__init__.py".to_string(),
|
||||
prefix: CheckCodePrefix::E402,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/linkextractors/__init__.py".to_string(),
|
||||
prefix: CheckCodePrefix::F401,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/selector/__init__.py".to_string(),
|
||||
prefix: CheckCodePrefix::F401,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/spiders/__init__.py".to_string(),
|
||||
prefix: CheckCodePrefix::E402,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/spiders/__init__.py".to_string(),
|
||||
prefix: CheckCodePrefix::F401,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/utils/url.py".to_string(),
|
||||
prefix: CheckCodePrefix::F403,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/utils/url.py".to_string(),
|
||||
prefix: CheckCodePrefix::F405,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "tests/test_loader.py".to_string(),
|
||||
prefix: CheckCodePrefix::E741,
|
||||
},
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,8 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use codegen::{Scope, Type, Variant};
|
||||
use itertools::Itertools;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use ruff::checks::CheckCode;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
fn main() {
|
||||
// Build up a map from prefix to matching CheckCodes.
|
||||
@@ -96,7 +95,7 @@ fn main() {
|
||||
|
||||
println!("//! File automatically generated by examples/generate_check_code_prefix.rs.");
|
||||
println!();
|
||||
println!("use serde::{{Deserialize, Serialize}};");
|
||||
println!("use serde::{{Serialize, Deserialize}};");
|
||||
println!("use strum_macros::EnumString;");
|
||||
println!();
|
||||
println!("use crate::checks::CheckCode;");
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
//! Generate a Markdown-compatible table of supported lint rules.
|
||||
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use ruff::checks::{CheckCategory, CheckCode};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
fn main() {
|
||||
for check_category in CheckCategory::iter() {
|
||||
|
||||
@@ -2,10 +2,9 @@ use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use ruff::code_gen::SourceGenerator;
|
||||
use ruff::fs;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
|
||||
@@ -4,9 +4,8 @@ use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use ruff::fs;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
|
||||
@@ -4,9 +4,8 @@ use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use rustpython_parser::lexer;
|
||||
|
||||
use ruff::fs;
|
||||
use rustpython_parser::lexer;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
|
||||
3
resources/test/fixtures/C400.py
vendored
3
resources/test/fixtures/C400.py
vendored
@@ -1 +1,4 @@
|
||||
x = list(x for x in range(3))
|
||||
x = list(
|
||||
x for x in range(3)
|
||||
)
|
||||
|
||||
3
resources/test/fixtures/C401.py
vendored
3
resources/test/fixtures/C401.py
vendored
@@ -1 +1,4 @@
|
||||
x = set(x for x in range(3))
|
||||
x = set(
|
||||
x for x in range(3)
|
||||
)
|
||||
|
||||
3
resources/test/fixtures/C402.py
vendored
3
resources/test/fixtures/C402.py
vendored
@@ -1,2 +1,5 @@
|
||||
dict((x, x) for x in range(3))
|
||||
dict(
|
||||
(x, x) for x in range(3)
|
||||
)
|
||||
dict(((x, x) for x in range(3)), z=3)
|
||||
|
||||
3
resources/test/fixtures/C403.py
vendored
3
resources/test/fixtures/C403.py
vendored
@@ -1 +1,4 @@
|
||||
s = set([x for x in range(3)])
|
||||
s = set(
|
||||
[x for x in range(3)]
|
||||
)
|
||||
|
||||
13
resources/test/fixtures/C409.py
vendored
13
resources/test/fixtures/C409.py
vendored
@@ -1,3 +1,10 @@
|
||||
t1 = tuple([1, 2])
|
||||
t2 = tuple((1, 2))
|
||||
t3 = tuple([])
|
||||
t1 = tuple([])
|
||||
t2 = tuple([1, 2])
|
||||
t3 = tuple((1, 2))
|
||||
t4 = tuple([
|
||||
1,
|
||||
2
|
||||
])
|
||||
t5 = tuple(
|
||||
(1, 2)
|
||||
)
|
||||
|
||||
30
resources/test/fixtures/F821_1.py
vendored
Normal file
30
resources/test/fixtures/F821_1.py
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
"""Test: typing module imports."""
|
||||
from foo import cast
|
||||
|
||||
# OK
|
||||
x = cast("Model")
|
||||
|
||||
from typing import cast
|
||||
|
||||
|
||||
# F821 Undefined name `Model`
|
||||
x = cast("Model", x)
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
|
||||
# F821 Undefined name `Model`
|
||||
x = typing.cast("Model", x)
|
||||
|
||||
|
||||
from typing import Pattern
|
||||
|
||||
# F821 Undefined name `Model`
|
||||
x = Pattern["Model"]
|
||||
|
||||
|
||||
from typing.re import Match
|
||||
|
||||
# F821 Undefined name `Model`
|
||||
x = Match["Model"]
|
||||
18
resources/test/fixtures/F821_2.py
vendored
Normal file
18
resources/test/fixtures/F821_2.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
"""Test: typing_extensions module imports."""
|
||||
from foo import Literal
|
||||
|
||||
# F821 Undefined name `Model`
|
||||
x: Literal["Model"]
|
||||
|
||||
from typing_extensions import Literal
|
||||
|
||||
|
||||
# OK
|
||||
x: Literal["Model"]
|
||||
|
||||
|
||||
import typing_extensions
|
||||
|
||||
|
||||
# OK
|
||||
x: typing_extensions.Literal["Model"]
|
||||
1
resources/test/fixtures/N806.py
vendored
1
resources/test/fixtures/N806.py
vendored
@@ -2,3 +2,4 @@ def f():
|
||||
lower = 0
|
||||
Camel = 0
|
||||
CONSTANT = 0
|
||||
_ = 0
|
||||
|
||||
38
resources/test/fixtures/pyproject.toml
vendored
38
resources/test/fixtures/pyproject.toml
vendored
@@ -1,13 +1,11 @@
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
extend-exclude = [
|
||||
"excluded.py",
|
||||
"migrations",
|
||||
"directory/also_excluded.py",
|
||||
]
|
||||
per-file-ignores = [
|
||||
"__init__.py:F401",
|
||||
"excluded.py",
|
||||
"migrations",
|
||||
"directory/also_excluded.py",
|
||||
]
|
||||
per-file-ignores = { "__init__.py" = ["F401"] }
|
||||
|
||||
[tool.ruff.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
@@ -17,22 +15,22 @@ avoid-escape = true
|
||||
|
||||
[tool.ruff.pep8-naming]
|
||||
ignore-names = [
|
||||
"setUp",
|
||||
"tearDown",
|
||||
"setUpClass",
|
||||
"tearDownClass",
|
||||
"setUpModule",
|
||||
"tearDownModule",
|
||||
"asyncSetUp",
|
||||
"asyncTearDown",
|
||||
"setUpTestData",
|
||||
"failureException",
|
||||
"longMessage",
|
||||
"maxDiff",
|
||||
"setUp",
|
||||
"tearDown",
|
||||
"setUpClass",
|
||||
"tearDownClass",
|
||||
"setUpModule",
|
||||
"tearDownModule",
|
||||
"asyncSetUp",
|
||||
"asyncTearDown",
|
||||
"setUpTestData",
|
||||
"failureException",
|
||||
"longMessage",
|
||||
"maxDiff",
|
||||
]
|
||||
classmethod-decorators = [
|
||||
"classmethod",
|
||||
"classmethod",
|
||||
]
|
||||
staticmethod-decorators = [
|
||||
"staticmethod",
|
||||
"staticmethod",
|
||||
]
|
||||
|
||||
1
rust-toolchain
Normal file
1
rust-toolchain
Normal file
@@ -0,0 +1 @@
|
||||
1.64.0
|
||||
15
rustfmt.toml
Normal file
15
rustfmt.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
condense_wildcard_suffixes = true
|
||||
edition = "2021"
|
||||
format_strings = true
|
||||
group_imports = "StdExternalCrate"
|
||||
hex_literal_case = "Lower"
|
||||
imports_granularity = "Module"
|
||||
max_width = 100
|
||||
normalize_comments = true
|
||||
normalize_doc_attributes = true
|
||||
reorder_impl_items = true
|
||||
reorder_imports = true
|
||||
reorder_modules = true
|
||||
unstable_features = true
|
||||
use_field_init_shorthand = true
|
||||
wrap_comments = true
|
||||
@@ -17,3 +17,5 @@ exclude =
|
||||
test_fstring.py,
|
||||
bad_coding2.py,
|
||||
badsyntax_*.py
|
||||
ignore_names = foo, bar
|
||||
quotes = double
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location, StmtKind};
|
||||
|
||||
use crate::python::typing;
|
||||
|
||||
fn compose_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
|
||||
match &expr.node {
|
||||
ExprKind::Call { func, .. } => {
|
||||
@@ -30,6 +30,7 @@ pub fn compose_call_path(expr: &Expr) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the `Expr` is a name or attribute reference to `${target}`.
|
||||
pub fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, .. } => target == attr,
|
||||
@@ -38,40 +39,36 @@ pub fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SubscriptKind {
|
||||
AnnotatedSubscript,
|
||||
PEP593AnnotatedSubscript,
|
||||
}
|
||||
|
||||
pub fn match_annotated_subscript(expr: &Expr) -> Option<SubscriptKind> {
|
||||
/// Return `true` if the `Expr` is a reference to `${module}.${target}`.
|
||||
///
|
||||
/// Useful for, e.g., ensuring that a `Union` reference represents
|
||||
/// `typing.Union`.
|
||||
pub fn match_name_or_attr_from_module(
|
||||
expr: &Expr,
|
||||
target: &str,
|
||||
module: &str,
|
||||
imports: Option<&BTreeSet<&str>>,
|
||||
) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, .. } => {
|
||||
if typing::is_annotated_subscript(attr) {
|
||||
Some(SubscriptKind::AnnotatedSubscript)
|
||||
} else if typing::is_pep593_annotated_subscript(attr) {
|
||||
Some(SubscriptKind::PEP593AnnotatedSubscript)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
ExprKind::Attribute { value, attr, .. } => match &value.node {
|
||||
ExprKind::Name { id, .. } => id == module && target == attr,
|
||||
_ => false,
|
||||
},
|
||||
ExprKind::Name { id, .. } => {
|
||||
if typing::is_annotated_subscript(id) {
|
||||
Some(SubscriptKind::AnnotatedSubscript)
|
||||
} else if typing::is_pep593_annotated_subscript(id) {
|
||||
Some(SubscriptKind::PEP593AnnotatedSubscript)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
target == id
|
||||
&& imports
|
||||
.map(|imports| imports.contains(&id.as_str()))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
_ => None,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
static DUNDER_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"__[^\s]+__").unwrap());
|
||||
|
||||
pub fn is_assignment_to_a_dunder(node: &StmtKind) -> bool {
|
||||
// Check whether it's an assignment to a dunder, with or without a type annotation.
|
||||
// This is what pycodestyle (as of 2.9.1) does.
|
||||
// Check whether it's an assignment to a dunder, with or without a type
|
||||
// annotation. This is what pycodestyle (as of 2.9.1) does.
|
||||
match node {
|
||||
StmtKind::Assign {
|
||||
targets,
|
||||
@@ -132,7 +129,8 @@ pub fn is_super_call_with_arguments(func: &Expr, args: &[Expr]) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a location within a file (relative to `base`) to an absolute position.
|
||||
/// Convert a location within a file (relative to `base`) to an absolute
|
||||
/// position.
|
||||
pub fn to_absolute(relative: &Location, base: &Location) -> Location {
|
||||
if relative.row() == 1 {
|
||||
Location::new(
|
||||
|
||||
@@ -8,7 +8,8 @@ fn relocate_keyword(keyword: &mut Keyword, location: Range) {
|
||||
relocate_expr(&mut keyword.node.value, location);
|
||||
}
|
||||
|
||||
/// Change an expression's location (recursively) to match a desired, fixed location.
|
||||
/// Change an expression's location (recursively) to match a desired, fixed
|
||||
/// location.
|
||||
pub fn relocate_expr(expr: &mut Expr, location: Range) {
|
||||
expr.location = location.location;
|
||||
expr.end_location = Some(location.end_location);
|
||||
|
||||
@@ -85,8 +85,8 @@ pub enum BindingKind {
|
||||
pub struct Binding {
|
||||
pub kind: BindingKind,
|
||||
pub range: Range,
|
||||
/// Tuple of (scope index, range) indicating the scope and range at which the binding was
|
||||
/// last used.
|
||||
/// Tuple of (scope index, range) indicating the scope and range at which
|
||||
/// the binding was last used.
|
||||
pub used: Option<(usize, Range)>,
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,11 @@ use std::collections::BTreeSet;
|
||||
use itertools::Itertools;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::autofix::Fix;
|
||||
use crate::autofix::Patch;
|
||||
use crate::autofix::{Fix, Patch};
|
||||
use crate::checks::Check;
|
||||
|
||||
// TODO(charlie): The model here is awkward because `Apply` is only relevant at
|
||||
// higher levels in the execution flow.
|
||||
#[derive(Hash)]
|
||||
pub enum Mode {
|
||||
Generate,
|
||||
@@ -55,14 +56,15 @@ fn apply_fixes<'a>(fixes: impl Iterator<Item = &'a mut Fix>, contents: &str) ->
|
||||
let mut applied: BTreeSet<&Patch> = Default::default();
|
||||
|
||||
for fix in fixes.sorted_by_key(|fix| fix.patch.location) {
|
||||
// If we already applied an identical fix as part of another correction, skip any
|
||||
// re-application.
|
||||
// If we already applied an identical fix as part of another correction, skip
|
||||
// any re-application.
|
||||
if applied.contains(&fix.patch) {
|
||||
fix.applied = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Best-effort approach: if this fix overlaps with a fix we've already applied, skip it.
|
||||
// Best-effort approach: if this fix overlaps with a fix we've already applied,
|
||||
// skip it.
|
||||
if last_pos > fix.patch.location {
|
||||
continue;
|
||||
}
|
||||
@@ -113,8 +115,7 @@ mod tests {
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::autofix::fixer::apply_fixes;
|
||||
use crate::autofix::Fix;
|
||||
use crate::autofix::Patch;
|
||||
use crate::autofix::{Fix, Patch};
|
||||
|
||||
#[test]
|
||||
fn empty_file() -> Result<()> {
|
||||
|
||||
@@ -4,7 +4,8 @@ use rustpython_parser::ast::{ExcepthandlerKind, Location, Stmt, StmtKind};
|
||||
|
||||
use crate::autofix::Fix;
|
||||
|
||||
/// Determine if a body contains only a single statement, taking into account deleted.
|
||||
/// Determine if a body contains only a single statement, taking into account
|
||||
/// deleted.
|
||||
fn has_single_child(body: &[Stmt], deleted: &[&Stmt]) -> bool {
|
||||
body.iter().filter(|child| !deleted.contains(child)).count() == 1
|
||||
}
|
||||
@@ -80,7 +81,8 @@ pub fn remove_stmt(stmt: &Stmt, parent: Option<&Stmt>, deleted: &[&Stmt]) -> Res
|
||||
))
|
||||
} else {
|
||||
// Otherwise, nuke the entire line.
|
||||
// TODO(charlie): This logic assumes that there are no multi-statement physical lines.
|
||||
// TODO(charlie): This logic assumes that there are no multi-statement physical
|
||||
// lines.
|
||||
Ok(Fix::deletion(
|
||||
Location::new(stmt.location.row(), 0),
|
||||
Location::new(stmt.end_location.unwrap().row() + 1, 0),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// cacache uses asyncd-std which has no wasm support, so currently no caching support on wasm
|
||||
// cacache uses asyncd-std which has no wasm support, so currently no caching
|
||||
// support on wasm
|
||||
#![cfg_attr(
|
||||
target_family = "wasm",
|
||||
allow(unused_imports, unused_variables, dead_code)
|
||||
@@ -127,7 +128,7 @@ pub fn get(
|
||||
}
|
||||
Err(e) => error!("Failed to deserialize encoded cache entry: {e:?}"),
|
||||
},
|
||||
Err(EntryNotFound(_, _)) => {}
|
||||
Err(EntryNotFound(..)) => {}
|
||||
Err(e) => error!("Failed to read from cache: {e:?}"),
|
||||
}
|
||||
None
|
||||
|
||||
209
src/check_ast.rs
209
src/check_ast.rs
@@ -11,7 +11,7 @@ use rustpython_parser::ast::{
|
||||
};
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::ast::helpers::{extract_handler_names, match_name_or_attr, SubscriptKind};
|
||||
use crate::ast::helpers::{extract_handler_names, match_name_or_attr_from_module};
|
||||
use crate::ast::operations::extract_all_names;
|
||||
use crate::ast::relocate::relocate_expr;
|
||||
use crate::ast::types::{
|
||||
@@ -25,6 +25,8 @@ use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
|
||||
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use crate::python::future::ALL_FEATURE_NAMES;
|
||||
use crate::python::typing;
|
||||
use crate::python::typing::SubscriptKind;
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::settings::Settings;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
@@ -34,7 +36,18 @@ use crate::{
|
||||
pycodestyle, pydocstyle, pyflakes, pyupgrade,
|
||||
};
|
||||
|
||||
pub const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
const TRACK_FROM_IMPORTS: [&str; 9] = [
|
||||
"collections",
|
||||
"collections.abc",
|
||||
"contextlib",
|
||||
"re",
|
||||
"typing",
|
||||
"typing.io",
|
||||
"typing.re",
|
||||
"typing_extensions",
|
||||
"weakref",
|
||||
];
|
||||
|
||||
pub struct Checker<'a> {
|
||||
// Input data.
|
||||
@@ -70,6 +83,7 @@ pub struct Checker<'a> {
|
||||
futures_allowed: bool,
|
||||
annotations_future_enabled: bool,
|
||||
except_handlers: Vec<Vec<String>>,
|
||||
from_imports: BTreeMap<&'a str, BTreeSet<&'a str>>,
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
@@ -101,19 +115,35 @@ impl<'a> Checker<'a> {
|
||||
modifier: Modifier::Module,
|
||||
visibility: module_visibility(path),
|
||||
},
|
||||
in_f_string: None,
|
||||
in_f_string: Default::default(),
|
||||
in_annotation: Default::default(),
|
||||
in_literal: Default::default(),
|
||||
seen_import_boundary: Default::default(),
|
||||
futures_allowed: true,
|
||||
annotations_future_enabled: Default::default(),
|
||||
except_handlers: Default::default(),
|
||||
from_imports: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if a patch should be generated under the given autofix `Mode`.
|
||||
/// Return `true` if a patch should be generated under the given autofix
|
||||
/// `Mode`.
|
||||
pub fn patch(&self) -> bool {
|
||||
self.autofix.patch()
|
||||
// TODO(charlie): We can't fix errors in f-strings until RustPython adds
|
||||
// location data.
|
||||
self.autofix.patch() && self.in_f_string.is_none()
|
||||
}
|
||||
|
||||
/// Return `true` if the `Expr` is a reference to `typing.${target}`.
|
||||
pub fn match_typing_module(&self, expr: &Expr, target: &str) -> bool {
|
||||
match_name_or_attr_from_module(expr, target, "typing", self.from_imports.get("typing"))
|
||||
|| (typing::in_extensions(target)
|
||||
&& match_name_or_attr_from_module(
|
||||
expr,
|
||||
target,
|
||||
"typing_extensions",
|
||||
self.from_imports.get("typing_extensions"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +300,8 @@ where
|
||||
|
||||
self.check_builtin_shadowing(name, Range::from_located(stmt), true);
|
||||
|
||||
// Visit the decorators and arguments, but avoid the body, which will be deferred.
|
||||
// Visit the decorators and arguments, but avoid the body, which will be
|
||||
// deferred.
|
||||
for expr in decorator_list {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
@@ -492,6 +523,24 @@ where
|
||||
module,
|
||||
level,
|
||||
} => {
|
||||
// Track `import from` statements, to ensure that we can correctly attribute
|
||||
// references like `from typing import Union`.
|
||||
if level.map(|level| level == 0).unwrap_or(true) {
|
||||
if let Some(module) = module {
|
||||
if TRACK_FROM_IMPORTS.contains(&module.as_str()) {
|
||||
self.from_imports
|
||||
.entry(module)
|
||||
.or_insert_with(BTreeSet::new)
|
||||
.extend(
|
||||
names
|
||||
.iter()
|
||||
.filter(|alias| alias.node.asname.is_none())
|
||||
.map(|alias| alias.node.name.as_str()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::E402) {
|
||||
if self.seen_import_boundary && stmt.location.column() == 0 {
|
||||
self.checks.push(Check::new(
|
||||
@@ -861,7 +910,7 @@ where
|
||||
pyupgrade::plugins::use_pep604_annotation(self, expr, value, slice);
|
||||
}
|
||||
|
||||
if match_name_or_attr(value, "Literal") {
|
||||
if self.match_typing_module(value, "Literal") {
|
||||
self.in_literal = true;
|
||||
}
|
||||
}
|
||||
@@ -886,6 +935,7 @@ where
|
||||
// Ex) List[...]
|
||||
if self.settings.enabled.contains(&CheckCode::U006)
|
||||
&& self.settings.target_version >= PythonVersion::Py39
|
||||
&& typing::is_pep585_builtin(expr, self.from_imports.get("typing"))
|
||||
{
|
||||
pyupgrade::plugins::use_pep585_annotation(self, expr, id);
|
||||
}
|
||||
@@ -908,16 +958,13 @@ where
|
||||
}
|
||||
ExprContext::Del => self.handle_node_delete(expr),
|
||||
},
|
||||
ExprKind::Attribute { value, attr, .. } => {
|
||||
ExprKind::Attribute { attr, .. } => {
|
||||
// Ex) typing.List[...]
|
||||
if self.settings.enabled.contains(&CheckCode::U006)
|
||||
&& self.settings.target_version >= PythonVersion::Py39
|
||||
&& typing::is_pep585_builtin(expr, self.from_imports.get("typing"))
|
||||
{
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
if id == "typing" {
|
||||
pyupgrade::plugins::use_pep585_annotation(self, expr, attr);
|
||||
}
|
||||
}
|
||||
pyupgrade::plugins::use_pep585_annotation(self, expr, attr);
|
||||
}
|
||||
}
|
||||
ExprKind::Call {
|
||||
@@ -944,7 +991,13 @@ where
|
||||
// flake8-comprehensions
|
||||
if self.settings.enabled.contains(&CheckCode::C400) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_list(
|
||||
expr, func, args, keywords,
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
@@ -952,7 +1005,13 @@ where
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C401) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_set(
|
||||
expr, func, args, keywords,
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
@@ -960,7 +1019,13 @@ where
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C402) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_dict(
|
||||
expr, func, args, keywords,
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
@@ -969,7 +1034,13 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::C403) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_list_comprehension_set(
|
||||
expr, func, args, keywords,
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
@@ -979,7 +1050,10 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::C404) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_list_comprehension_dict(
|
||||
expr, func, args, keywords,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
@@ -988,7 +1062,13 @@ where
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C405) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_set(
|
||||
expr, func, args, keywords,
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
@@ -996,7 +1076,10 @@ where
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C406) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_dict(
|
||||
expr, func, args, keywords,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
@@ -1004,7 +1087,13 @@ where
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C408) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_collection_call(
|
||||
expr, func, args, keywords,
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
@@ -1013,7 +1102,12 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::C409) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_literal_within_tuple_call(
|
||||
expr, func, args,
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
@@ -1023,7 +1117,12 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::C410) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_literal_within_list_call(
|
||||
expr, func, args,
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
@@ -1031,9 +1130,14 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C411) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_list_call(expr, func, args)
|
||||
{
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_list_call(
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
@@ -1041,7 +1145,9 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::C413) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_call_around_sorted(
|
||||
expr, func, args,
|
||||
func,
|
||||
args,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
@@ -1051,7 +1157,9 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::C414) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_double_cast_or_process(
|
||||
expr, func, args,
|
||||
func,
|
||||
args,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
@@ -1061,7 +1169,9 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::C415) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_subscript_reversal(
|
||||
expr, func, args,
|
||||
func,
|
||||
args,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
@@ -1069,9 +1179,11 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C417) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_map(expr, func, args)
|
||||
{
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_map(
|
||||
func,
|
||||
args,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
@@ -1248,7 +1360,10 @@ where
|
||||
ExprKind::ListComp { elt, generators } | ExprKind::SetComp { elt, generators } => {
|
||||
if self.settings.enabled.contains(&CheckCode::C416) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_comprehension(
|
||||
expr, elt, generators,
|
||||
expr,
|
||||
elt,
|
||||
generators,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
@@ -1276,12 +1391,12 @@ where
|
||||
args,
|
||||
keywords,
|
||||
} => {
|
||||
if match_name_or_attr(func, "ForwardRef") {
|
||||
if self.match_typing_module(func, "ForwardRef") {
|
||||
self.visit_expr(func);
|
||||
for expr in args {
|
||||
self.visit_annotation(expr);
|
||||
}
|
||||
} else if match_name_or_attr(func, "cast") {
|
||||
} else if self.match_typing_module(func, "cast") {
|
||||
self.visit_expr(func);
|
||||
if !args.is_empty() {
|
||||
self.visit_annotation(&args[0]);
|
||||
@@ -1289,12 +1404,12 @@ where
|
||||
for expr in args.iter().skip(1) {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
} else if match_name_or_attr(func, "NewType") {
|
||||
} else if self.match_typing_module(func, "NewType") {
|
||||
self.visit_expr(func);
|
||||
for expr in args.iter().skip(1) {
|
||||
self.visit_annotation(expr);
|
||||
}
|
||||
} else if match_name_or_attr(func, "TypeVar") {
|
||||
} else if self.match_typing_module(func, "TypeVar") {
|
||||
self.visit_expr(func);
|
||||
for expr in args.iter().skip(1) {
|
||||
self.visit_annotation(expr);
|
||||
@@ -1311,7 +1426,7 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if match_name_or_attr(func, "NamedTuple") {
|
||||
} else if self.match_typing_module(func, "NamedTuple") {
|
||||
self.visit_expr(func);
|
||||
|
||||
// Ex) NamedTuple("a", [("a", int)])
|
||||
@@ -1343,7 +1458,7 @@ where
|
||||
let KeywordData { value, .. } = &keyword.node;
|
||||
self.visit_annotation(value);
|
||||
}
|
||||
} else if match_name_or_attr(func, "TypedDict") {
|
||||
} else if self.match_typing_module(func, "TypedDict") {
|
||||
self.visit_expr(func);
|
||||
|
||||
// Ex) TypedDict("a", {"a": int})
|
||||
@@ -1370,7 +1485,7 @@ where
|
||||
}
|
||||
}
|
||||
ExprKind::Subscript { value, slice, ctx } => {
|
||||
match helpers::match_annotated_subscript(value) {
|
||||
match typing::match_annotated_subscript(value, &self.from_imports) {
|
||||
Some(subscript) => match subscript {
|
||||
// Ex) Optional[int]
|
||||
SubscriptKind::AnnotatedSubscript => {
|
||||
@@ -1507,7 +1622,8 @@ where
|
||||
flake8_bugbear::plugins::mutable_argument_default(self, arguments)
|
||||
}
|
||||
|
||||
// Bind, but intentionally avoid walking default expressions, as we handle them upstream.
|
||||
// Bind, but intentionally avoid walking default expressions, as we handle them
|
||||
// upstream.
|
||||
for arg in &arguments.posonlyargs {
|
||||
self.visit_arg(arg);
|
||||
}
|
||||
@@ -1526,7 +1642,8 @@ where
|
||||
}
|
||||
|
||||
fn visit_arg(&mut self, arg: &'b Arg) {
|
||||
// Bind, but intentionally avoid walking the annotation, as we handle it upstream.
|
||||
// Bind, but intentionally avoid walking the annotation, as we handle it
|
||||
// upstream.
|
||||
self.add_binding(
|
||||
arg.node.arg.to_string(),
|
||||
Binding {
|
||||
@@ -1568,8 +1685,9 @@ fn try_mark_used(scope: &mut Scope, scope_id: usize, id: &str, expr: &Expr) -> b
|
||||
// Mark the binding as used.
|
||||
binding.used = Some((scope_id, Range::from_located(expr)));
|
||||
|
||||
// If the name of the sub-importation is the same as an alias of another importation and the
|
||||
// alias is used, that sub-importation should be marked as used too.
|
||||
// If the name of the sub-importation is the same as an alias of another
|
||||
// importation and the alias is used, that sub-importation should be
|
||||
// marked as used too.
|
||||
//
|
||||
// This handles code like:
|
||||
// import pyarrow as pa
|
||||
@@ -1679,7 +1797,8 @@ impl<'a> Checker<'a> {
|
||||
fn add_binding(&mut self, name: String, binding: Binding) {
|
||||
let scope = &mut self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
|
||||
// TODO(charlie): Don't treat annotations as assignments if there is an existing value.
|
||||
// TODO(charlie): Don't treat annotations as assignments if there is an existing
|
||||
// value.
|
||||
let binding = match scope.values.get(&name) {
|
||||
None => binding,
|
||||
Some(existing) => {
|
||||
|
||||
@@ -5,8 +5,7 @@ use std::collections::BTreeMap;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::fixer;
|
||||
use crate::autofix::Fix;
|
||||
use crate::autofix::{fixer, Fix};
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::noqa;
|
||||
use crate::noqa::Directive;
|
||||
@@ -68,7 +67,7 @@ pub fn check_lines(
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
|
||||
match noqa {
|
||||
(Directive::All(_, _), matches) => {
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index)
|
||||
}
|
||||
@@ -100,7 +99,7 @@ pub fn check_lines(
|
||||
);
|
||||
|
||||
match noqa {
|
||||
(Directive::All(_, _), matches) => {
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
}
|
||||
(Directive::Codes(_, _, codes), matches) => {
|
||||
@@ -118,8 +117,8 @@ pub fn check_lines(
|
||||
|
||||
// Enforce newlines at end of files.
|
||||
if settings.enabled.contains(&CheckCode::W292) && !contents.ends_with('\n') {
|
||||
// Note: if `lines.last()` is `None`, then `contents` is empty (and so we don't want to
|
||||
// raise W292 anyway).
|
||||
// Note: if `lines.last()` is `None`, then `contents` is empty (and so we don't
|
||||
// want to raise W292 anyway).
|
||||
if let Some(line) = lines.last() {
|
||||
let lineno = lines.len() - 1;
|
||||
let noqa_lineno = noqa_line_for
|
||||
@@ -140,7 +139,7 @@ pub fn check_lines(
|
||||
);
|
||||
|
||||
match noqa {
|
||||
(Directive::All(_, _), matches) => {
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
}
|
||||
(Directive::Codes(_, _, codes), matches) => {
|
||||
@@ -227,12 +226,11 @@ pub fn check_lines(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::check_lines;
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::settings::Settings;
|
||||
|
||||
use super::check_lines;
|
||||
|
||||
#[test]
|
||||
fn e501_non_ascii_char() {
|
||||
let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8.
|
||||
|
||||
@@ -19,7 +19,7 @@ pub fn check_tokens(
|
||||
| settings.enabled.contains(&CheckCode::Q002)
|
||||
| settings.enabled.contains(&CheckCode::Q003);
|
||||
|
||||
let mut state_machine = StateMachine::new();
|
||||
let mut state_machine: StateMachine = Default::default();
|
||||
for (start, tok, end) in tokens.iter().flatten() {
|
||||
// W605
|
||||
if enforce_invalid_escape_sequence {
|
||||
|
||||
121
src/checks.rs
121
src/checks.rs
@@ -395,7 +395,8 @@ pub enum CheckKind {
|
||||
}
|
||||
|
||||
impl CheckCode {
|
||||
/// The source for the check (either the AST, the filesystem, or the physical lines).
|
||||
/// The source for the check (either the AST, the filesystem, or the
|
||||
/// physical lines).
|
||||
pub fn lint_source(&self) -> &'static LintSource {
|
||||
match self {
|
||||
CheckCode::E501 | CheckCode::W292 | CheckCode::M001 => &LintSource::Lines,
|
||||
@@ -779,14 +780,14 @@ impl CheckKind {
|
||||
CheckKind::FutureFeatureNotDefined(_) => &CheckCode::F407,
|
||||
CheckKind::IOError(_) => &CheckCode::E902,
|
||||
CheckKind::IfTuple => &CheckCode::F634,
|
||||
CheckKind::ImportShadowedByLoopVar(_, _) => &CheckCode::F402,
|
||||
CheckKind::ImportShadowedByLoopVar(..) => &CheckCode::F402,
|
||||
CheckKind::ImportStarNotPermitted(_) => &CheckCode::F406,
|
||||
CheckKind::ImportStarUsage(_, _) => &CheckCode::F405,
|
||||
CheckKind::ImportStarUsage(..) => &CheckCode::F405,
|
||||
CheckKind::ImportStarUsed(_) => &CheckCode::F403,
|
||||
CheckKind::InvalidPrintSyntax => &CheckCode::F633,
|
||||
CheckKind::IsLiteral => &CheckCode::F632,
|
||||
CheckKind::LateFutureImport => &CheckCode::F404,
|
||||
CheckKind::LineTooLong(_, _) => &CheckCode::E501,
|
||||
CheckKind::LineTooLong(..) => &CheckCode::E501,
|
||||
CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402,
|
||||
CheckKind::MultiValueRepeatedKeyLiteral => &CheckCode::F601,
|
||||
CheckKind::MultiValueRepeatedKeyVariable(_) => &CheckCode::F602,
|
||||
@@ -797,13 +798,13 @@ impl CheckKind {
|
||||
CheckKind::ReturnOutsideFunction => &CheckCode::F706,
|
||||
CheckKind::SyntaxError(_) => &CheckCode::E999,
|
||||
CheckKind::ExpressionsInStarAssignment => &CheckCode::F621,
|
||||
CheckKind::TrueFalseComparison(_, _) => &CheckCode::E712,
|
||||
CheckKind::TrueFalseComparison(..) => &CheckCode::E712,
|
||||
CheckKind::TwoStarredExpressions => &CheckCode::F622,
|
||||
CheckKind::TypeComparison => &CheckCode::E721,
|
||||
CheckKind::UndefinedExport(_) => &CheckCode::F822,
|
||||
CheckKind::UndefinedLocal(_) => &CheckCode::F823,
|
||||
CheckKind::UndefinedName(_) => &CheckCode::F821,
|
||||
CheckKind::UnusedImport(_, _) => &CheckCode::F401,
|
||||
CheckKind::UnusedImport(..) => &CheckCode::F401,
|
||||
CheckKind::UnusedVariable(_) => &CheckCode::F841,
|
||||
CheckKind::YieldOutsideFunction => &CheckCode::F704,
|
||||
// pycodestyle warnings
|
||||
@@ -851,7 +852,7 @@ impl CheckKind {
|
||||
CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
|
||||
CheckKind::UnnecessaryAbspath => &CheckCode::U002,
|
||||
CheckKind::UselessMetaclassType => &CheckCode::U001,
|
||||
CheckKind::DeprecatedUnittestAlias(_, _) => &CheckCode::U005,
|
||||
CheckKind::DeprecatedUnittestAlias(..) => &CheckCode::U005,
|
||||
CheckKind::UsePEP585Annotation(_) => &CheckCode::U006,
|
||||
CheckKind::UsePEP604Annotation => &CheckCode::U007,
|
||||
CheckKind::UselessObjectInheritance(_) => &CheckCode::U004,
|
||||
@@ -1059,7 +1060,9 @@ impl CheckKind {
|
||||
}
|
||||
// pycodestyle warnings
|
||||
CheckKind::NoNewLineAtEndOfFile => "No newline at end of file".to_string(),
|
||||
CheckKind::InvalidEscapeSequence(char) => format!("Invalid escape sequence: '\\{char}'"),
|
||||
CheckKind::InvalidEscapeSequence(char) => {
|
||||
format!("Invalid escape sequence: '\\{char}'")
|
||||
}
|
||||
// flake8-builtins
|
||||
CheckKind::BuiltinVariableShadowing(name) => {
|
||||
format!("Variable `{name}` is shadowing a python builtin")
|
||||
@@ -1071,15 +1074,25 @@ impl CheckKind {
|
||||
format!("Class attribute `{name}` is shadowing a python builtin")
|
||||
}
|
||||
// flake8-bugbear
|
||||
CheckKind::UnaryPrefixIncrement => "Python does not support the unary prefix increment. Writing `++n` is equivalent to `+(+(n))`, which equals `n`. You meant `n += 1`.".to_string(),
|
||||
CheckKind::MutableArgumentDefault => "Do not use mutable data structures for argument defaults.".to_string(),
|
||||
CheckKind::UnusedLoopControlVariable(name) => format!("Loop control variable `{name}` not used within the loop body. If this is intended, start the name with an underscore."),
|
||||
CheckKind::DoNotAssertFalse => {
|
||||
"Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`"
|
||||
.to_string()
|
||||
CheckKind::UnaryPrefixIncrement => "Python does not support the unary prefix \
|
||||
increment. Writing `++n` is equivalent to \
|
||||
`+(+(n))`, which equals `n`. You meant `n += 1`."
|
||||
.to_string(),
|
||||
CheckKind::MutableArgumentDefault => {
|
||||
"Do not use mutable data structures for argument defaults.".to_string()
|
||||
}
|
||||
CheckKind::UnusedLoopControlVariable(name) => format!(
|
||||
"Loop control variable `{name}` not used within the loop body. If this is \
|
||||
intended, start the name with an underscore."
|
||||
),
|
||||
CheckKind::DoNotAssertFalse => "Do not `assert False` (`python -O` removes these \
|
||||
calls), raise `AssertionError()`"
|
||||
.to_string(),
|
||||
CheckKind::RedundantTupleInExceptionHandler(name) => {
|
||||
format!("A length-one tuple literal is redundant. Write `except {name}:` instead of `except ({name},):`.")
|
||||
format!(
|
||||
"A length-one tuple literal is redundant. Write `except {name}:` instead of \
|
||||
`except ({name},):`."
|
||||
)
|
||||
}
|
||||
CheckKind::DuplicateHandlerException(names) => {
|
||||
if names.len() == 1 {
|
||||
@@ -1091,8 +1104,12 @@ impl CheckKind {
|
||||
}
|
||||
}
|
||||
CheckKind::NoAssertRaisesException => {
|
||||
"`assertRaises(Exception):` should be considered evil. It can lead to your test passing even if the code being tested is never executed due to a typo. Either assert for a more specific exception (builtin or custom), use `assertRaisesRegex`, or use the context manager form of `assertRaises`.".to_string()
|
||||
}
|
||||
"`assertRaises(Exception):` should be considered evil. It can lead to your test \
|
||||
passing even if the code being tested is never executed due to a typo. Either \
|
||||
assert for a more specific exception (builtin or custom), use \
|
||||
`assertRaisesRegex`, or use the context manager form of `assertRaises`."
|
||||
.to_string()
|
||||
}
|
||||
CheckKind::DuplicateTryBlockException(name) => {
|
||||
format!("try-except block with duplicate exception `{name}`")
|
||||
}
|
||||
@@ -1124,22 +1141,26 @@ impl CheckKind {
|
||||
CheckKind::UnnecessaryLiteralWithinTupleCall(literal) => {
|
||||
if literal == "list" {
|
||||
format!(
|
||||
"Unnecessary `{literal}` literal passed to `tuple()` (rewrite as a `tuple` literal)"
|
||||
"Unnecessary `{literal}` literal passed to `tuple()` (rewrite as a \
|
||||
`tuple` literal)"
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"Unnecessary `{literal}` literal passed to `tuple()` (remove the outer call to `tuple()`)"
|
||||
"Unnecessary `{literal}` literal passed to `tuple()` (remove the outer \
|
||||
call to `tuple()`)"
|
||||
)
|
||||
}
|
||||
}
|
||||
CheckKind::UnnecessaryLiteralWithinListCall(literal) => {
|
||||
if literal == "list" {
|
||||
format!(
|
||||
"Unnecessary `{literal}` literal passed to `list()` (remove the outer call to `list()`)"
|
||||
"Unnecessary `{literal}` literal passed to `list()` (remove the outer \
|
||||
call to `list()`)"
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"Unnecessary `{literal}` literal passed to `list()` (rewrite as a `list` literal)"
|
||||
"Unnecessary `{literal}` literal passed to `list()` (rewrite as a `list` \
|
||||
literal)"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1169,25 +1190,29 @@ impl CheckKind {
|
||||
CheckKind::PrintFound => "`print` found".to_string(),
|
||||
CheckKind::PPrintFound => "`pprint` found".to_string(),
|
||||
// flake8-quotes
|
||||
CheckKind::BadQuotesInlineString(quote) => {
|
||||
match quote {
|
||||
Quote::Single => "Double quotes found but single quotes preferred".to_string(),
|
||||
Quote::Double => "Single quotes found but double quotes preferred".to_string(),
|
||||
CheckKind::BadQuotesInlineString(quote) => match quote {
|
||||
Quote::Single => "Double quotes found but single quotes preferred".to_string(),
|
||||
Quote::Double => "Single quotes found but double quotes preferred".to_string(),
|
||||
},
|
||||
CheckKind::BadQuotesMultilineString(quote) => match quote {
|
||||
Quote::Single => {
|
||||
"Double quote multiline found but single quotes preferred".to_string()
|
||||
}
|
||||
Quote::Double => {
|
||||
"Single quote multiline found but double quotes preferred".to_string()
|
||||
}
|
||||
},
|
||||
CheckKind::BadQuotesMultilineString(quote) => {
|
||||
match quote {
|
||||
Quote::Single => "Double quote multiline found but single quotes preferred".to_string(),
|
||||
Quote::Double => "Single quote multiline found but double quotes preferred".to_string(),
|
||||
CheckKind::BadQuotesDocstring(quote) => match quote {
|
||||
Quote::Single => {
|
||||
"Double quote docstring found but single quotes preferred".to_string()
|
||||
}
|
||||
Quote::Double => {
|
||||
"Single quote docstring found but double quotes preferred".to_string()
|
||||
}
|
||||
},
|
||||
CheckKind::BadQuotesDocstring(quote) => {
|
||||
match quote {
|
||||
Quote::Single => "Double quote docstring found but single quotes preferred".to_string(),
|
||||
Quote::Double => "Single quote docstring found but double quotes preferred".to_string(),
|
||||
}
|
||||
},
|
||||
CheckKind::AvoidQuoteEscape => "Change outer quotes to avoid escaping inner quotes".to_string(),
|
||||
CheckKind::AvoidQuoteEscape => {
|
||||
"Change outer quotes to avoid escaping inner quotes".to_string()
|
||||
}
|
||||
// pyupgrade
|
||||
CheckKind::TypeOfPrimitive(primitive) => {
|
||||
format!("Use `{}` instead of `type(...)`", primitive.builtin())
|
||||
@@ -1226,10 +1251,9 @@ impl CheckKind {
|
||||
}
|
||||
CheckKind::EndsInPeriod => "First line should end with a period".to_string(),
|
||||
CheckKind::NonEmpty => "Docstring is empty".to_string(),
|
||||
CheckKind::EndsInPunctuation => {
|
||||
"First line should end with a period, question mark, or exclamation point"
|
||||
.to_string()
|
||||
}
|
||||
CheckKind::EndsInPunctuation => "First line should end with a period, question mark, \
|
||||
or exclamation point"
|
||||
.to_string(),
|
||||
CheckKind::FirstLineCapitalized => {
|
||||
"First word of the first line should be properly capitalized".to_string()
|
||||
}
|
||||
@@ -1291,7 +1315,10 @@ impl CheckKind {
|
||||
format!("Missing dashed underline after section (\"{name}\")")
|
||||
}
|
||||
CheckKind::SectionUnderlineAfterName(name) => {
|
||||
format!("Section underline should be in the line following the section's name (\"{name}\")")
|
||||
format!(
|
||||
"Section underline should be in the line following the section's name \
|
||||
(\"{name}\")"
|
||||
)
|
||||
}
|
||||
CheckKind::SectionUnderlineMatchesSectionLength(name) => {
|
||||
format!("Section underline should match the length of its name (\"{name}\")")
|
||||
@@ -1391,7 +1418,8 @@ impl CheckKind {
|
||||
}
|
||||
}
|
||||
|
||||
/// The summary text for the check. Typically a truncated form of the body text.
|
||||
/// The summary text for the check. Typically a truncated form of the body
|
||||
/// text.
|
||||
pub fn summary(&self) -> String {
|
||||
match self {
|
||||
CheckKind::UnaryPrefixIncrement => {
|
||||
@@ -1441,6 +1469,15 @@ impl CheckKind {
|
||||
| CheckKind::SuperCallWithParameters
|
||||
| CheckKind::TypeOfPrimitive(_)
|
||||
| CheckKind::UnnecessaryAbspath
|
||||
| CheckKind::UnnecessaryCollectionCall(_)
|
||||
| CheckKind::UnnecessaryGeneratorDict
|
||||
| CheckKind::UnnecessaryGeneratorList
|
||||
| CheckKind::UnnecessaryGeneratorSet
|
||||
| CheckKind::UnnecessaryListCall
|
||||
| CheckKind::UnnecessaryListComprehensionSet
|
||||
| CheckKind::UnnecessaryLiteralSet(_)
|
||||
| CheckKind::UnnecessaryLiteralWithinListCall(_)
|
||||
| CheckKind::UnnecessaryLiteralWithinTupleCall(_)
|
||||
| CheckKind::UnusedImport(_, false)
|
||||
| CheckKind::UnusedLoopControlVariable(_)
|
||||
| CheckKind::UnusedNOQA(_)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//! File automatically generated by examples/generate_check_code_prefix.rs.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::EnumString;
|
||||
use strum_macros::{AsRefStr, EnumString};
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
|
||||
#[derive(EnumString, Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||
#[derive(AsRefStr, EnumString, Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||
pub enum CheckCodePrefix {
|
||||
A,
|
||||
A0,
|
||||
@@ -270,12 +270,14 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B002 => vec![CheckCode::B002],
|
||||
CheckCodePrefix::B006 => vec![CheckCode::B006],
|
||||
CheckCodePrefix::B007 => vec![CheckCode::B007],
|
||||
CheckCodePrefix::B01 => vec![
|
||||
CheckCode::B011,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
CheckCode::B017,
|
||||
],
|
||||
CheckCodePrefix::B01 => {
|
||||
vec![
|
||||
CheckCode::B011,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
CheckCode::B017,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::B011 => vec![CheckCode::B011],
|
||||
CheckCodePrefix::B013 => vec![CheckCode::B013],
|
||||
CheckCodePrefix::B014 => vec![CheckCode::B014],
|
||||
@@ -582,12 +584,14 @@ impl CheckCodePrefix {
|
||||
CheckCode::E742,
|
||||
CheckCode::E743,
|
||||
],
|
||||
CheckCodePrefix::E71 => vec![
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
],
|
||||
CheckCodePrefix::E71 => {
|
||||
vec![
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::E711 => vec![CheckCode::E711],
|
||||
CheckCodePrefix::E712 => vec![CheckCode::E712],
|
||||
CheckCodePrefix::E713 => vec![CheckCode::E713],
|
||||
@@ -680,12 +684,14 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::F62 => vec![CheckCode::F621, CheckCode::F622],
|
||||
CheckCodePrefix::F621 => vec![CheckCode::F621],
|
||||
CheckCodePrefix::F622 => vec![CheckCode::F622],
|
||||
CheckCodePrefix::F63 => vec![
|
||||
CheckCode::F631,
|
||||
CheckCode::F632,
|
||||
CheckCode::F633,
|
||||
CheckCode::F634,
|
||||
],
|
||||
CheckCodePrefix::F63 => {
|
||||
vec![
|
||||
CheckCode::F631,
|
||||
CheckCode::F632,
|
||||
CheckCode::F633,
|
||||
CheckCode::F634,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::F631 => vec![CheckCode::F631],
|
||||
CheckCodePrefix::F632 => vec![CheckCode::F632],
|
||||
CheckCodePrefix::F633 => vec![CheckCode::F633],
|
||||
@@ -802,24 +808,30 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::N816 => vec![CheckCode::N816],
|
||||
CheckCodePrefix::N817 => vec![CheckCode::N817],
|
||||
CheckCodePrefix::N818 => vec![CheckCode::N818],
|
||||
CheckCodePrefix::Q => vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
],
|
||||
CheckCodePrefix::Q0 => vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
],
|
||||
CheckCodePrefix::Q00 => vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
],
|
||||
CheckCodePrefix::Q => {
|
||||
vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::Q0 => {
|
||||
vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::Q00 => {
|
||||
vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::Q000 => vec![CheckCode::Q000],
|
||||
CheckCodePrefix::Q001 => vec![CheckCode::Q001],
|
||||
CheckCodePrefix::Q002 => vec![CheckCode::Q002],
|
||||
|
||||
37
src/cli.rs
37
src/cli.rs
@@ -1,3 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -8,8 +9,7 @@ use regex::Regex;
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::printer::SerializationFormat;
|
||||
use crate::settings::configuration::Configuration;
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::settings::types::StrCheckCodePair;
|
||||
use crate::settings::types::{PatternPrefixPair, PythonVersion};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, about = "ruff: An extremely fast Python linter.")]
|
||||
@@ -26,7 +26,8 @@ pub struct Cli {
|
||||
/// Only log errors.
|
||||
#[arg(short, long, group = "verbosity")]
|
||||
pub quiet: bool,
|
||||
/// Disable all logging (but still exit with status code "1" upon detecting errors).
|
||||
/// Disable all logging (but still exit with status code "1" upon detecting
|
||||
/// errors).
|
||||
#[arg(short, long, group = "verbosity")]
|
||||
pub silent: bool,
|
||||
/// Exit with status code "0", even upon detecting errors.
|
||||
@@ -44,24 +45,27 @@ pub struct Cli {
|
||||
/// List of error codes to enable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub select: Vec<CheckCodePrefix>,
|
||||
/// Like --select, but adds additional error codes on top of the selected ones.
|
||||
/// Like --select, but adds additional error codes on top of the selected
|
||||
/// ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_select: Vec<CheckCodePrefix>,
|
||||
/// List of error codes to ignore.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub ignore: Vec<CheckCodePrefix>,
|
||||
/// Like --ignore, but adds additional error codes on top of the ignored ones.
|
||||
/// Like --ignore, but adds additional error codes on top of the ignored
|
||||
/// ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_ignore: Vec<CheckCodePrefix>,
|
||||
/// List of paths, used to exclude files and/or directories from checks.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub exclude: Vec<String>,
|
||||
/// Like --exclude, but adds additional files and directories on top of the excluded ones.
|
||||
/// Like --exclude, but adds additional files and directories on top of the
|
||||
/// excluded ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_exclude: Vec<String>,
|
||||
/// List of mappings from file pattern to code to exclude
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub per_file_ignores: Vec<StrCheckCodePair>,
|
||||
pub per_file_ignores: Vec<PatternPrefixPair>,
|
||||
/// Output serialization format for error messages.
|
||||
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
|
||||
pub format: SerializationFormat,
|
||||
@@ -138,8 +142,25 @@ pub fn warn_on(
|
||||
path.to_string_lossy()
|
||||
)
|
||||
} else {
|
||||
warn!("{code:?} was passed to {flag}, but ignored by the default `extend_ignore` field")
|
||||
warn!(
|
||||
"{code:?} was passed to {flag}, but ignored by the default `extend_ignore` \
|
||||
field"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect a list of `PatternPrefixPair` structs as a `BTreeMap`.
|
||||
pub fn collect_per_file_ignores(
|
||||
pairs: Vec<PatternPrefixPair>,
|
||||
) -> BTreeMap<String, Vec<CheckCodePrefix>> {
|
||||
let mut per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> = BTreeMap::new();
|
||||
for pair in pairs {
|
||||
per_file_ignores
|
||||
.entry(pair.pattern)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(pair.prefix);
|
||||
}
|
||||
per_file_ignores
|
||||
}
|
||||
|
||||
19
src/cst/matchers.rs
Normal file
19
src/cst/matchers.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use anyhow::Result;
|
||||
use libcst_native::Module;
|
||||
use rustpython_ast::Located;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
pub fn match_tree<'a, T>(
|
||||
locator: &'a SourceCodeLocator,
|
||||
located: &'a Located<T>,
|
||||
) -> Result<Module<'a>> {
|
||||
match libcst_native::parse_module(
|
||||
locator.slice_source_code_range(&Range::from_located(located)),
|
||||
None,
|
||||
) {
|
||||
Ok(module) => Ok(module),
|
||||
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod helpers;
|
||||
pub mod matchers;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::autofix::Fix;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
@@ -42,7 +42,10 @@ pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: &Optio
|
||||
..
|
||||
} = &test.node
|
||||
{
|
||||
let mut check = Check::new(CheckKind::DoNotAssertFalse, Range::from_located(test));
|
||||
let mut check = Check::new(
|
||||
CheckKind::DoNotAssertFalse,
|
||||
checker.locate_check(Range::from_located(test)),
|
||||
);
|
||||
if checker.patch() {
|
||||
let mut generator = SourceGenerator::new();
|
||||
if let Ok(()) = generator.unparse_stmt(&assertion_error(msg)) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use rustpython_ast::{ExprKind, Stmt, Withitem};
|
||||
|
||||
use crate::ast::helpers::match_name_or_attr;
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
@@ -17,7 +17,7 @@ pub fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items: &[With
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NoAssertRaisesException,
|
||||
Range::from_located(stmt),
|
||||
checker.locate_check(Range::from_located(stmt)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
pub use assert_false::assert_false;
|
||||
pub use assert_raises_exception::assert_raises_exception;
|
||||
pub use duplicate_exceptions::duplicate_exceptions;
|
||||
pub use duplicate_exceptions::duplicate_handler_exceptions;
|
||||
pub use duplicate_exceptions::{duplicate_exceptions, duplicate_handler_exceptions};
|
||||
pub use mutable_argument_default::mutable_argument_default;
|
||||
pub use redundant_tuple_in_exception_handler::redundant_tuple_in_exception_handler;
|
||||
pub use unary_prefix_increment::unary_prefix_increment;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use rustpython_ast::{Expr, ExprKind, Unaryop};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
@@ -11,7 +11,7 @@ pub fn unary_prefix_increment(checker: &mut Checker, expr: &Expr, op: &Unaryop,
|
||||
if matches!(op, Unaryop::UAdd) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::UnaryPrefixIncrement,
|
||||
Range::from_located(expr),
|
||||
checker.locate_check(Range::from_located(expr)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
|
||||
|
||||
use rustpython_ast::{Expr, ExprKind, Stmt};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::ast::visitor;
|
||||
use crate::ast::visitor::Visitor;
|
||||
use crate::autofix::Fix;
|
||||
@@ -64,7 +64,7 @@ pub fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, body:
|
||||
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedLoopControlVariable(name.to_string()),
|
||||
Range::from_located(expr),
|
||||
checker.locate_check(Range::from_located(expr)),
|
||||
);
|
||||
if checker.patch() {
|
||||
// Prefix the variable name with an underscore.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use log::error;
|
||||
use num_bigint::BigInt;
|
||||
use rustpython_ast::{
|
||||
Comprehension, Constant, Expr, ExprKind, Keyword, KeywordData, Located, Unaryop,
|
||||
@@ -5,6 +6,8 @@ use rustpython_ast::{
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::flake8_comprehensions::fixes;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
fn function_name(func: &Expr) -> Option<&str> {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
@@ -49,13 +52,20 @@ pub fn unnecessary_generator_list(
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
locator: &SourceCodeLocator,
|
||||
fix: bool,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("list", func, args, keywords)?;
|
||||
if let ExprKind::GeneratorExp { .. } = argument {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryGeneratorList,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
let mut check = Check::new(CheckKind::UnnecessaryGeneratorList, location);
|
||||
if fix {
|
||||
match fixes::fix_unnecessary_generator_list(locator, expr) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(e) => error!("Failed to generate fix: {}", e),
|
||||
}
|
||||
}
|
||||
return Some(check);
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -66,13 +76,20 @@ pub fn unnecessary_generator_set(
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
locator: &SourceCodeLocator,
|
||||
fix: bool,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?;
|
||||
if let ExprKind::GeneratorExp { .. } = argument {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryGeneratorSet,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
let mut check = Check::new(CheckKind::UnnecessaryGeneratorSet, location);
|
||||
if fix {
|
||||
match fixes::fix_unnecessary_generator_set(locator, expr) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(e) => error!("Failed to generate fix: {}", e),
|
||||
}
|
||||
}
|
||||
return Some(check);
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -83,15 +100,22 @@ pub fn unnecessary_generator_dict(
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
locator: &SourceCodeLocator,
|
||||
fix: bool,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("dict", func, args, keywords)?;
|
||||
if let ExprKind::GeneratorExp { elt, .. } = argument {
|
||||
match &elt.node {
|
||||
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryGeneratorDict,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
let mut check = Check::new(CheckKind::UnnecessaryGeneratorDict, location);
|
||||
if fix {
|
||||
match fixes::fix_unnecessary_generator_dict(locator, expr) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(e) => error!("Failed to generate fix: {}", e),
|
||||
}
|
||||
}
|
||||
return Some(check);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -105,23 +129,30 @@ pub fn unnecessary_list_comprehension_set(
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
locator: &SourceCodeLocator,
|
||||
fix: bool,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?;
|
||||
if let ExprKind::ListComp { .. } = &argument {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryListComprehensionSet,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
let mut check = Check::new(CheckKind::UnnecessaryListComprehensionSet, location);
|
||||
if fix {
|
||||
match fixes::fix_unnecessary_list_comprehension_set(locator, expr) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(e) => error!("Failed to generate fix: {}", e),
|
||||
}
|
||||
}
|
||||
return Some(check);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// C404 (`dict([...])`)
|
||||
pub fn unnecessary_list_comprehension_dict(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("dict", func, args, keywords)?;
|
||||
if let ExprKind::ListComp { elt, .. } = &argument {
|
||||
@@ -129,7 +160,7 @@ pub fn unnecessary_list_comprehension_dict(
|
||||
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryListComprehensionDict,
|
||||
Range::from_located(expr),
|
||||
location,
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
@@ -144,6 +175,9 @@ pub fn unnecessary_literal_set(
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
locator: &SourceCodeLocator,
|
||||
fix: bool,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?;
|
||||
let kind = match argument {
|
||||
@@ -151,18 +185,22 @@ pub fn unnecessary_literal_set(
|
||||
ExprKind::Tuple { .. } => "tuple",
|
||||
_ => return None,
|
||||
};
|
||||
Some(Check::new(
|
||||
CheckKind::UnnecessaryLiteralSet(kind.to_string()),
|
||||
Range::from_located(expr),
|
||||
))
|
||||
let mut check = Check::new(CheckKind::UnnecessaryLiteralSet(kind.to_string()), location);
|
||||
if fix {
|
||||
match fixes::fix_unnecessary_literal_set(locator, expr) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(e) => error!("Failed to generate fix: {}", e),
|
||||
}
|
||||
}
|
||||
Some(check)
|
||||
}
|
||||
|
||||
/// C406 (`dict([(1, 2)])`)
|
||||
pub fn unnecessary_literal_dict(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("dict", func, args, keywords)?;
|
||||
let (kind, elts) = match argument {
|
||||
@@ -180,7 +218,7 @@ pub fn unnecessary_literal_dict(
|
||||
|
||||
Some(Check::new(
|
||||
CheckKind::UnnecessaryLiteralDict(kind.to_string()),
|
||||
Range::from_located(expr),
|
||||
location,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -190,22 +228,37 @@ pub fn unnecessary_collection_call(
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Located<KeywordData>],
|
||||
locator: &SourceCodeLocator,
|
||||
fix: bool,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if !args.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let id = function_name(func)?;
|
||||
match id {
|
||||
"dict" if keywords.is_empty() || keywords.iter().all(|kw| kw.node.arg.is_some()) => (),
|
||||
"dict" if keywords.is_empty() || keywords.iter().all(|kw| kw.node.arg.is_some()) => {
|
||||
// `dict()` or `dict(a=1)` (as opposed to `dict(**a)`)
|
||||
}
|
||||
"list" | "tuple" => {
|
||||
// list() or tuple()
|
||||
// `list()` or `tuple()`
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
Some(Check::new(
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnnecessaryCollectionCall(id.to_string()),
|
||||
Range::from_located(expr),
|
||||
))
|
||||
location,
|
||||
);
|
||||
if fix {
|
||||
// TODO(charlie): Support fixing `dict(a=1)`.
|
||||
if keywords.is_empty() {
|
||||
match fixes::fix_unnecessary_collection_call(locator, expr) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(e) => error!("Failed to generate fix: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(check)
|
||||
}
|
||||
|
||||
/// C409
|
||||
@@ -213,6 +266,9 @@ pub fn unnecessary_literal_within_tuple_call(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
locator: &SourceCodeLocator,
|
||||
fix: bool,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let argument = first_argument_with_matching_function("tuple", func, args)?;
|
||||
let argument_kind = match argument {
|
||||
@@ -220,10 +276,17 @@ pub fn unnecessary_literal_within_tuple_call(
|
||||
ExprKind::List { .. } => "list",
|
||||
_ => return None,
|
||||
};
|
||||
Some(Check::new(
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnnecessaryLiteralWithinTupleCall(argument_kind.to_string()),
|
||||
Range::from_located(expr),
|
||||
))
|
||||
location,
|
||||
);
|
||||
if fix {
|
||||
match fixes::fix_unnecessary_literal_within_tuple_call(locator, expr) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(e) => error!("Failed to generate fix: {}", e),
|
||||
}
|
||||
}
|
||||
Some(check)
|
||||
}
|
||||
|
||||
/// C410
|
||||
@@ -231,6 +294,9 @@ pub fn unnecessary_literal_within_list_call(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
locator: &SourceCodeLocator,
|
||||
fix: bool,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let argument = first_argument_with_matching_function("list", func, args)?;
|
||||
let argument_kind = match argument {
|
||||
@@ -238,26 +304,48 @@ pub fn unnecessary_literal_within_list_call(
|
||||
ExprKind::List { .. } => "list",
|
||||
_ => return None,
|
||||
};
|
||||
Some(Check::new(
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnnecessaryLiteralWithinListCall(argument_kind.to_string()),
|
||||
Range::from_located(expr),
|
||||
))
|
||||
location,
|
||||
);
|
||||
if fix {
|
||||
match fixes::fix_unnecessary_literal_within_list_call(locator, expr) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(e) => error!("Failed to generate fix: {}", e),
|
||||
}
|
||||
}
|
||||
Some(check)
|
||||
}
|
||||
|
||||
/// C411
|
||||
pub fn unnecessary_list_call(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
|
||||
pub fn unnecessary_list_call(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
locator: &SourceCodeLocator,
|
||||
fix: bool,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let argument = first_argument_with_matching_function("list", func, args)?;
|
||||
if let ExprKind::ListComp { .. } = argument {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryListCall,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
let mut check = Check::new(CheckKind::UnnecessaryListCall, location);
|
||||
if fix {
|
||||
match fixes::fix_unnecessary_list_call(locator, expr) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(e) => error!("Failed to generate fix: {}", e),
|
||||
}
|
||||
}
|
||||
return Some(check);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// C413
|
||||
pub fn unnecessary_call_around_sorted(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
|
||||
pub fn unnecessary_call_around_sorted(
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let outer = function_name(func)?;
|
||||
if !(outer == "list" || outer == "reversed") {
|
||||
return None;
|
||||
@@ -266,7 +354,7 @@ pub fn unnecessary_call_around_sorted(expr: &Expr, func: &Expr, args: &[Expr]) -
|
||||
if function_name(func)? == "sorted" {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryCallAroundSorted(outer.to_string()),
|
||||
Range::from_located(expr),
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -275,19 +363,19 @@ pub fn unnecessary_call_around_sorted(expr: &Expr, func: &Expr, args: &[Expr]) -
|
||||
|
||||
/// C414
|
||||
pub fn unnecessary_double_cast_or_process(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let outer = function_name(func)?;
|
||||
if !["list", "tuple", "set", "reversed", "sorted"].contains(&outer) {
|
||||
return None;
|
||||
}
|
||||
|
||||
fn new_check(inner: &str, outer: &str, expr: &Expr) -> Check {
|
||||
fn new_check(inner: &str, outer: &str, location: Range) -> Check {
|
||||
Check::new(
|
||||
CheckKind::UnnecessaryDoubleCastOrProcess(inner.to_string(), outer.to_string()),
|
||||
Range::from_located(expr),
|
||||
location,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -297,24 +385,28 @@ pub fn unnecessary_double_cast_or_process(
|
||||
if (outer == "set" || outer == "sorted")
|
||||
&& (inner == "list" || inner == "tuple" || inner == "reversed" || inner == "sorted")
|
||||
{
|
||||
return Some(new_check(inner, outer, expr));
|
||||
return Some(new_check(inner, outer, location));
|
||||
}
|
||||
|
||||
// Ex) list(tuple(...))
|
||||
if (outer == "list" || outer == "tuple") && (inner == "list" || inner == "tuple") {
|
||||
return Some(new_check(inner, outer, expr));
|
||||
return Some(new_check(inner, outer, location));
|
||||
}
|
||||
|
||||
// Ex) set(set(...))
|
||||
if outer == "set" && inner == "set" {
|
||||
return Some(new_check(inner, outer, expr));
|
||||
return Some(new_check(inner, outer, location));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// C415
|
||||
pub fn unnecessary_subscript_reversal(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
|
||||
pub fn unnecessary_subscript_reversal(
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let first_arg = args.first()?;
|
||||
let id = function_name(func)?;
|
||||
if !["set", "sorted", "reversed"].contains(&id) {
|
||||
@@ -337,7 +429,7 @@ pub fn unnecessary_subscript_reversal(expr: &Expr, func: &Expr, args: &[Expr]) -
|
||||
if *val == BigInt::from(1) {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessarySubscriptReversal(id.to_string()),
|
||||
Range::from_located(expr),
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -354,6 +446,7 @@ pub fn unnecessary_comprehension(
|
||||
expr: &Expr,
|
||||
elt: &Expr,
|
||||
generators: &[Comprehension],
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if generators.len() != 1 {
|
||||
return None;
|
||||
@@ -374,30 +467,27 @@ pub fn unnecessary_comprehension(
|
||||
};
|
||||
Some(Check::new(
|
||||
CheckKind::UnnecessaryComprehension(expr_kind.to_string()),
|
||||
Range::from_located(expr),
|
||||
location,
|
||||
))
|
||||
}
|
||||
|
||||
/// C417
|
||||
pub fn unnecessary_map(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
|
||||
fn new_check(kind: &str, expr: &Expr) -> Check {
|
||||
Check::new(
|
||||
CheckKind::UnnecessaryMap(kind.to_string()),
|
||||
Range::from_located(expr),
|
||||
)
|
||||
pub fn unnecessary_map(func: &Expr, args: &[Expr], location: Range) -> Option<Check> {
|
||||
fn new_check(kind: &str, location: Range) -> Check {
|
||||
Check::new(CheckKind::UnnecessaryMap(kind.to_string()), location)
|
||||
}
|
||||
let id = function_name(func)?;
|
||||
match id {
|
||||
"map" => {
|
||||
if args.len() == 2 && matches!(&args[0].node, ExprKind::Lambda { .. }) {
|
||||
return Some(new_check("generator", expr));
|
||||
return Some(new_check("generator", location));
|
||||
}
|
||||
}
|
||||
"list" | "set" => {
|
||||
if let ExprKind::Call { func, args, .. } = &args.first()?.node {
|
||||
let argument = first_argument_with_matching_function("map", func, args)?;
|
||||
if let ExprKind::Lambda { .. } = argument {
|
||||
return Some(new_check(id, expr));
|
||||
return Some(new_check(id, location));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,7 +498,7 @@ pub fn unnecessary_map(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check>
|
||||
if let ExprKind::Lambda { body, .. } = &argument {
|
||||
if matches!(&body.node, ExprKind::Tuple { elts, .. } | ExprKind::List { elts, .. } if elts.len() == 2)
|
||||
{
|
||||
return Some(new_check(id, expr));
|
||||
return Some(new_check(id, location));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
473
src/flake8_comprehensions/fixes.rs
Normal file
473
src/flake8_comprehensions/fixes.rs
Normal file
@@ -0,0 +1,473 @@
|
||||
use anyhow::Result;
|
||||
use libcst_native::{
|
||||
Arg, Call, Codegen, Dict, DictComp, Element, Expr, Expression, LeftCurlyBrace, LeftParen,
|
||||
LeftSquareBracket, List, ListComp, Module, ParenthesizableWhitespace, RightCurlyBrace,
|
||||
RightParen, RightSquareBracket, Set, SetComp, SimpleWhitespace, SmallStatement, Statement,
|
||||
Tuple,
|
||||
};
|
||||
|
||||
use crate::autofix::Fix;
|
||||
use crate::cst::matchers::match_tree;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
fn match_expr<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Expr<'b>> {
|
||||
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
|
||||
if let Some(SmallStatement::Expr(expr)) = expr.body.first_mut() {
|
||||
Ok(expr)
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
"Expected node to be: SmallStatement::Expr."
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Expected node to be: Statement::Simple."))
|
||||
}
|
||||
}
|
||||
|
||||
fn match_call<'a, 'b>(expr: &'a mut Expr<'b>) -> Result<&'a mut Call<'b>> {
|
||||
if let Expression::Call(call) = &mut expr.value {
|
||||
Ok(call)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Expected node to be: Expression::Call."))
|
||||
}
|
||||
}
|
||||
|
||||
fn match_arg<'a, 'b>(call: &'a Call<'b>) -> Result<&'a Arg<'b>> {
|
||||
if let Some(arg) = call.args.first() {
|
||||
Ok(arg)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Expected node to be: Arg."))
|
||||
}
|
||||
}
|
||||
|
||||
/// (C400) Convert `list(x for x in y)` to `[x for x in y]`.
|
||||
pub fn fix_unnecessary_generator_list(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(Call(GeneratorExp)))) -> Expr(ListComp)))
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
|
||||
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
|
||||
generator_exp
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::GeneratorExp."
|
||||
));
|
||||
};
|
||||
|
||||
body.value = Expression::ListComp(Box::new(ListComp {
|
||||
elt: generator_exp.elt.clone(),
|
||||
for_in: generator_exp.for_in.clone(),
|
||||
lbracket: LeftSquareBracket {
|
||||
whitespace_after: call.whitespace_before_args.clone(),
|
||||
},
|
||||
rbracket: RightSquareBracket {
|
||||
whitespace_before: arg.whitespace_after_arg.clone(),
|
||||
},
|
||||
lpar: generator_exp.lpar.clone(),
|
||||
rpar: generator_exp.rpar.clone(),
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// (C401) Convert `set(x for x in y)` to `{x for x in y}`.
|
||||
pub fn fix_unnecessary_generator_set(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(Call(GeneratorExp)))) -> Expr(SetComp)))
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
|
||||
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
|
||||
generator_exp
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::GeneratorExp."
|
||||
));
|
||||
};
|
||||
|
||||
body.value = Expression::SetComp(Box::new(SetComp {
|
||||
elt: generator_exp.elt.clone(),
|
||||
for_in: generator_exp.for_in.clone(),
|
||||
lbrace: LeftCurlyBrace {
|
||||
whitespace_after: call.whitespace_before_args.clone(),
|
||||
},
|
||||
rbrace: RightCurlyBrace {
|
||||
whitespace_before: arg.whitespace_after_arg.clone(),
|
||||
},
|
||||
lpar: generator_exp.lpar.clone(),
|
||||
rpar: generator_exp.rpar.clone(),
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// (C402) Convert `dict((x, x) for x in range(3))` to `{x: x for x in
|
||||
/// range(3)}`.
|
||||
pub fn fix_unnecessary_generator_dict(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
|
||||
// Extract the (k, v) from `(k, v) for ...`.
|
||||
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
|
||||
generator_exp
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::GeneratorExp."
|
||||
));
|
||||
};
|
||||
let tuple = if let Expression::Tuple(tuple) = &generator_exp.elt.as_ref() {
|
||||
tuple
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Expression::Tuple."));
|
||||
};
|
||||
let key = if let Some(Element::Simple { value, .. }) = &tuple.elements.get(0) {
|
||||
value
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected tuple to contain a key as the first element."
|
||||
));
|
||||
};
|
||||
let value = if let Some(Element::Simple { value, .. }) = &tuple.elements.get(1) {
|
||||
value
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected tuple to contain a key as the second element."
|
||||
));
|
||||
};
|
||||
|
||||
body.value = Expression::DictComp(Box::new(DictComp {
|
||||
key: Box::new(key.clone()),
|
||||
value: Box::new(value.clone()),
|
||||
for_in: generator_exp.for_in.clone(),
|
||||
lbrace: LeftCurlyBrace {
|
||||
whitespace_after: call.whitespace_before_args.clone(),
|
||||
},
|
||||
rbrace: RightCurlyBrace {
|
||||
whitespace_before: arg.whitespace_after_arg.clone(),
|
||||
},
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
whitespace_before_colon: Default::default(),
|
||||
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" ")),
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// (C403) Convert `set([x for x in y])` to `{x for x in y}`.
|
||||
pub fn fix_unnecessary_list_comprehension_set(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(Call(ListComp)))) ->
|
||||
// Expr(SetComp)))
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
|
||||
let list_comp = if let Expression::ListComp(list_comp) = &arg.value {
|
||||
list_comp
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::ListComp."
|
||||
));
|
||||
};
|
||||
|
||||
body.value = Expression::SetComp(Box::new(SetComp {
|
||||
elt: list_comp.elt.clone(),
|
||||
for_in: list_comp.for_in.clone(),
|
||||
lbrace: LeftCurlyBrace {
|
||||
whitespace_after: call.whitespace_before_args.clone(),
|
||||
},
|
||||
rbrace: RightCurlyBrace {
|
||||
whitespace_before: arg.whitespace_after_arg.clone(),
|
||||
},
|
||||
lpar: list_comp.lpar.clone(),
|
||||
rpar: list_comp.rpar.clone(),
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// (C405) Convert `set((1, 2))` to `{1, 2}`.
|
||||
pub fn fix_unnecessary_literal_set(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(Call(List|Tuple)))) -> Expr(Set)))
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let mut call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
|
||||
let elements = match &arg.value {
|
||||
Expression::Tuple(inner) => &inner.elements,
|
||||
Expression::List(inner) => &inner.elements,
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::Tuple | Expression::List."
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
if elements.is_empty() {
|
||||
call.args = vec![];
|
||||
} else {
|
||||
body.value = Expression::Set(Box::new(Set {
|
||||
elements: elements.clone(),
|
||||
lbrace: LeftCurlyBrace {
|
||||
whitespace_after: call.whitespace_before_args.clone(),
|
||||
},
|
||||
rbrace: RightCurlyBrace {
|
||||
whitespace_before: arg.whitespace_after_arg.clone(),
|
||||
},
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
}));
|
||||
}
|
||||
|
||||
let mut state = Default::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// (C408)
|
||||
pub fn fix_unnecessary_collection_call(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(Call("list" | "tuple" | "dict")))) -> Expr(List|Tuple|Dict)))
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let name = if let Expression::Name(name) = &call.func.as_ref() {
|
||||
name
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Expression::Name."));
|
||||
};
|
||||
|
||||
match name.value {
|
||||
"tuple" => {
|
||||
body.value = Expression::Tuple(Box::new(Tuple {
|
||||
elements: Default::default(),
|
||||
lpar: vec![Default::default()],
|
||||
rpar: vec![Default::default()],
|
||||
}));
|
||||
}
|
||||
"list" => {
|
||||
body.value = Expression::List(Box::new(List {
|
||||
elements: Default::default(),
|
||||
lbracket: Default::default(),
|
||||
rbracket: Default::default(),
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
}));
|
||||
}
|
||||
"dict" => {
|
||||
body.value = Expression::Dict(Box::new(Dict {
|
||||
elements: Default::default(),
|
||||
lbrace: Default::default(),
|
||||
rbrace: Default::default(),
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
}));
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!("Expected function name to be one of: \
|
||||
'tuple', 'list', 'dict'."
|
||||
.to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
let mut state = Default::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// (C409) Convert `tuple([1, 2])` to `tuple(1, 2)`
|
||||
pub fn fix_unnecessary_literal_within_tuple_call(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
let (elements, whitespace_after, whitespace_before) = match &arg.value {
|
||||
Expression::Tuple(inner) => (
|
||||
&inner.elements,
|
||||
&inner
|
||||
.lpar
|
||||
.first()
|
||||
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses."))?
|
||||
.whitespace_after,
|
||||
&inner
|
||||
.rpar
|
||||
.first()
|
||||
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses."))?
|
||||
.whitespace_before,
|
||||
),
|
||||
Expression::List(inner) => (
|
||||
&inner.elements,
|
||||
&inner.lbracket.whitespace_after,
|
||||
&inner.rbracket.whitespace_before,
|
||||
),
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::Tuple | Expression::List."
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
body.value = Expression::Tuple(Box::new(Tuple {
|
||||
elements: elements.clone(),
|
||||
lpar: vec![LeftParen {
|
||||
whitespace_after: whitespace_after.clone(),
|
||||
}],
|
||||
rpar: vec![RightParen {
|
||||
whitespace_before: whitespace_before.clone(),
|
||||
}],
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// (C410) Convert `list([1, 2])` to `[1, 2]`
|
||||
pub fn fix_unnecessary_literal_within_list_call(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
let (elements, whitespace_after, whitespace_before) = match &arg.value {
|
||||
Expression::Tuple(inner) => (
|
||||
&inner.elements,
|
||||
&inner
|
||||
.lpar
|
||||
.first()
|
||||
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses."))?
|
||||
.whitespace_after,
|
||||
&inner
|
||||
.rpar
|
||||
.first()
|
||||
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses."))?
|
||||
.whitespace_before,
|
||||
),
|
||||
Expression::List(inner) => (
|
||||
&inner.elements,
|
||||
&inner.lbracket.whitespace_after,
|
||||
&inner.rbracket.whitespace_before,
|
||||
),
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::Tuple | Expression::List."
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
body.value = Expression::List(Box::new(List {
|
||||
elements: elements.clone(),
|
||||
lbracket: LeftSquareBracket {
|
||||
whitespace_after: whitespace_after.clone(),
|
||||
},
|
||||
rbracket: RightSquareBracket {
|
||||
whitespace_before: whitespace_before.clone(),
|
||||
},
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// (C411) Convert `list([i for i in x])` to `[i for i in x]`.
|
||||
pub fn fix_unnecessary_list_call(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(Call(List|Tuple)))) -> Expr(List|Tuple)))
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
|
||||
body.value = arg.value.clone();
|
||||
|
||||
let mut state = Default::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod checks;
|
||||
mod fixes;
|
||||
|
||||
@@ -9,25 +9,20 @@ pub fn print_call(
|
||||
func: &Expr,
|
||||
check_print: bool,
|
||||
check_pprint: bool,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if check_print && id == "print" {
|
||||
return Some(Check::new(CheckKind::PrintFound, Range::from_located(expr)));
|
||||
} else if check_pprint && id == "pprint" {
|
||||
return Some(Check::new(
|
||||
CheckKind::PPrintFound,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
return Some(Check::new(CheckKind::PPrintFound, location));
|
||||
}
|
||||
}
|
||||
|
||||
if let ExprKind::Attribute { value, attr, .. } = &func.node {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
if check_pprint && id == "pprint" && attr == "pprint" {
|
||||
return Some(Check::new(
|
||||
CheckKind::PPrintFound,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
return Some(Check::new(CheckKind::PPrintFound, location));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use log::error;
|
||||
use rustpython_ast::{Expr, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::autofix::helpers;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::CheckCode;
|
||||
@@ -12,6 +13,7 @@ pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
|
||||
func,
|
||||
checker.settings.enabled.contains(&CheckCode::T201),
|
||||
checker.settings.enabled.contains(&CheckCode::T203),
|
||||
checker.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
if checker.patch() {
|
||||
let context = checker.binding_context();
|
||||
|
||||
@@ -148,8 +148,7 @@ mod tests {
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::flake8_quotes::settings::Quote;
|
||||
use crate::linter::tokenize;
|
||||
use crate::{flake8_quotes, linter, Settings};
|
||||
use crate::{fs, noqa};
|
||||
use crate::{flake8_quotes, fs, linter, noqa, Settings};
|
||||
|
||||
fn check_path(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
|
||||
@@ -10,7 +10,8 @@ use rustpython_parser::lexer::Tok;
|
||||
enum State {
|
||||
// Start of the module: first string gets marked as a docstring.
|
||||
ExpectModuleDocstring,
|
||||
// After seeing a class definition, we're waiting for the block colon (and do bracket counting).
|
||||
// After seeing a class definition, we're waiting for the block colon (and do bracket
|
||||
// counting).
|
||||
ExpectClassColon,
|
||||
// After seeing the block colon in a class definition, we expect a docstring.
|
||||
ExpectClassDocstring,
|
||||
@@ -27,6 +28,12 @@ pub struct StateMachine {
|
||||
bracket_count: usize,
|
||||
}
|
||||
|
||||
impl Default for StateMachine {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl StateMachine {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub enum Quote {
|
||||
Single,
|
||||
Double,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
pub inline_quotes: Option<Quote>,
|
||||
@@ -18,7 +18,7 @@ pub struct Options {
|
||||
pub avoid_escape: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct Settings {
|
||||
pub inline_quotes: Quote,
|
||||
pub multiline_quotes: Quote,
|
||||
@@ -29,7 +29,7 @@ pub struct Settings {
|
||||
impl Settings {
|
||||
pub fn from_options(options: Options) -> Self {
|
||||
Self {
|
||||
inline_quotes: options.inline_quotes.unwrap_or(Quote::Single),
|
||||
inline_quotes: options.inline_quotes.unwrap_or(Quote::Double),
|
||||
multiline_quotes: options.multiline_quotes.unwrap_or(Quote::Double),
|
||||
docstring_quotes: options.docstring_quotes.unwrap_or(Quote::Double),
|
||||
avoid_escape: options.avoid_escape.unwrap_or(true),
|
||||
@@ -40,7 +40,7 @@ impl Settings {
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inline_quotes: Quote::Single,
|
||||
inline_quotes: Quote::Double,
|
||||
multiline_quotes: Quote::Double,
|
||||
docstring_quotes: Quote::Double,
|
||||
avoid_escape: true,
|
||||
|
||||
@@ -7,8 +7,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use log::debug;
|
||||
use path_absolutize::path_dedot;
|
||||
use path_absolutize::Absolutize;
|
||||
use path_absolutize::{path_dedot, Absolutize};
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
@@ -137,7 +136,8 @@ pub fn ignores_from_path<'a>(
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Convert any path to an absolute path (based on the current working directory).
|
||||
/// Convert any path to an absolute path (based on the current working
|
||||
/// directory).
|
||||
pub fn normalize_path(path: &Path) -> PathBuf {
|
||||
if let Ok(path) = path.absolutize() {
|
||||
return path.to_path_buf();
|
||||
|
||||
16
src/lib.rs
16
src/lib.rs
@@ -5,9 +5,7 @@ use std::path::Path;
|
||||
use anyhow::Result;
|
||||
use log::debug;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use settings::pyproject;
|
||||
use settings::Settings;
|
||||
use settings::{pyproject, Settings};
|
||||
|
||||
use crate::autofix::fixer::Mode;
|
||||
use crate::checks::Check;
|
||||
@@ -21,7 +19,7 @@ pub mod check_ast;
|
||||
mod check_lines;
|
||||
mod check_tokens;
|
||||
pub mod checks;
|
||||
mod checks_gen;
|
||||
pub mod checks_gen;
|
||||
pub mod cli;
|
||||
pub mod code_gen;
|
||||
mod cst;
|
||||
@@ -30,13 +28,13 @@ mod flake8_bugbear;
|
||||
mod flake8_builtins;
|
||||
mod flake8_comprehensions;
|
||||
mod flake8_print;
|
||||
mod flake8_quotes;
|
||||
pub mod flake8_quotes;
|
||||
pub mod fs;
|
||||
pub mod linter;
|
||||
pub mod logging;
|
||||
pub mod message;
|
||||
mod noqa;
|
||||
mod pep8_naming;
|
||||
pub mod pep8_naming;
|
||||
pub mod printer;
|
||||
mod pycodestyle;
|
||||
mod pydocstyle;
|
||||
@@ -47,8 +45,8 @@ pub mod settings;
|
||||
pub mod source_code_locator;
|
||||
pub mod visibility;
|
||||
|
||||
/// Run ruff over Python source code directly.
|
||||
pub fn check(path: &Path, contents: &str) -> Result<Vec<Check>> {
|
||||
/// Run Ruff over Python source code directly.
|
||||
pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
// Find the project root and pyproject.toml.
|
||||
let project_root = pyproject::find_project_root(&[path.to_path_buf()]);
|
||||
match &project_root {
|
||||
@@ -77,7 +75,7 @@ pub fn check(path: &Path, contents: &str) -> Result<Vec<Check>> {
|
||||
tokens,
|
||||
&noqa_line_for,
|
||||
&settings,
|
||||
&Mode::None,
|
||||
&if autofix { Mode::Generate } else { Mode::None },
|
||||
)?;
|
||||
|
||||
Ok(checks)
|
||||
|
||||
@@ -141,13 +141,7 @@ pub fn lint_stdin(
|
||||
// Convert to messages.
|
||||
Ok(checks
|
||||
.into_iter()
|
||||
.map(|check| Message {
|
||||
kind: check.kind,
|
||||
fixed: check.fix.map(|fix| fix.applied).unwrap_or_default(),
|
||||
location: check.location,
|
||||
end_location: check.end_location,
|
||||
filename: path.to_string_lossy().to_string(),
|
||||
})
|
||||
.map(|check| Message::from_check(path.to_string_lossy().to_string(), check))
|
||||
.collect())
|
||||
}
|
||||
|
||||
@@ -189,13 +183,7 @@ pub fn lint_path(
|
||||
// Convert to messages.
|
||||
let messages: Vec<Message> = checks
|
||||
.into_iter()
|
||||
.map(|check| Message {
|
||||
kind: check.kind,
|
||||
fixed: check.fix.map(|fix| fix.applied).unwrap_or_default(),
|
||||
location: check.location,
|
||||
end_location: check.end_location,
|
||||
filename: path.to_string_lossy().to_string(),
|
||||
})
|
||||
.map(|check| Message::from_check(path.to_string_lossy().to_string(), check))
|
||||
.collect();
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
cache::set(path, &metadata, settings, autofix, &messages, mode);
|
||||
@@ -255,9 +243,7 @@ mod tests {
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::linter::tokenize;
|
||||
use crate::settings;
|
||||
use crate::{fs, noqa};
|
||||
use crate::{linter, Settings};
|
||||
use crate::{fs, linter, noqa, settings, Settings};
|
||||
|
||||
fn check_path(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
@@ -377,7 +363,9 @@ mod tests {
|
||||
#[test_case(CheckCode::F706, Path::new("F706.py"); "F706")]
|
||||
#[test_case(CheckCode::F707, Path::new("F707.py"); "F707")]
|
||||
#[test_case(CheckCode::F722, Path::new("F722.py"); "F722")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821.py"); "F821")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821_0.py"); "F821_0")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821_1.py"); "F821_1")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821_2.py"); "F821_2")]
|
||||
#[test_case(CheckCode::F822, Path::new("F822.py"); "F822")]
|
||||
#[test_case(CheckCode::F823, Path::new("F823.py"); "F823")]
|
||||
#[test_case(CheckCode::F831, Path::new("F831.py"); "F831")]
|
||||
|
||||
33
src/main.rs
33
src/main.rs
@@ -1,3 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::{self, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
@@ -11,35 +12,32 @@ use log::{debug, error};
|
||||
use notify::{raw_watcher, RecursiveMode, Watcher};
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rayon::prelude::*;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use ruff::cache;
|
||||
use ruff::checks::CheckCode;
|
||||
use ruff::checks::CheckKind;
|
||||
use ruff::cli::{warn_on, Cli, Warnable};
|
||||
use ruff::checks::{CheckCode, CheckKind};
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
use ruff::cli::{collect_per_file_ignores, warn_on, Cli, Warnable};
|
||||
use ruff::fs::iter_python_files;
|
||||
use ruff::linter::add_noqa_to_path;
|
||||
use ruff::linter::autoformat_path;
|
||||
use ruff::linter::{lint_path, lint_stdin};
|
||||
use ruff::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin};
|
||||
use ruff::logging::set_up_logging;
|
||||
use ruff::message::Message;
|
||||
use ruff::printer::{Printer, SerializationFormat};
|
||||
use ruff::settings::configuration::Configuration;
|
||||
use ruff::settings::pyproject;
|
||||
use ruff::settings::types::{FilePattern, PerFileIgnore};
|
||||
use ruff::settings::types::FilePattern;
|
||||
use ruff::settings::user::UserConfiguration;
|
||||
use ruff::settings::Settings;
|
||||
use ruff::settings::{pyproject, Settings};
|
||||
use ruff::tell_user;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
#[cfg(feature = "update-informer")]
|
||||
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
||||
#[cfg(feature = "update-informer")]
|
||||
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
/// Shim that calls par_iter except for wasm because there's no wasm support in rayon yet
|
||||
/// (there is a shim to be used for the web, but it requires js cooperation)
|
||||
/// Unfortunately, ParallelIterator does not implement Iterator so the signatures diverge
|
||||
/// Shim that calls par_iter except for wasm because there's no wasm support in
|
||||
/// rayon yet (there is a shim to be used for the web, but it requires js
|
||||
/// cooperation) Unfortunately, ParallelIterator does not implement Iterator so
|
||||
/// the signatures diverge
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
fn par_iter<T: Sync>(iterable: &Vec<T>) -> impl ParallelIterator<Item = &T> {
|
||||
iterable.par_iter()
|
||||
@@ -255,11 +253,8 @@ fn inner_main() -> Result<ExitCode> {
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.collect();
|
||||
let per_file_ignores: Vec<PerFileIgnore> = cli
|
||||
.per_file_ignores
|
||||
.into_iter()
|
||||
.map(|pair| PerFileIgnore::new(pair, &project_root))
|
||||
.collect();
|
||||
let per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> =
|
||||
collect_per_file_ignores(cli.per_file_ignores);
|
||||
|
||||
let mut configuration = Configuration::from_pyproject(&pyproject, &project_root)?;
|
||||
if !exclude.is_empty() {
|
||||
|
||||
@@ -6,7 +6,7 @@ use colored::Colorize;
|
||||
use rustpython_parser::ast::Location;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::checks::CheckKind;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::fs::relativize_path;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -18,6 +18,18 @@ pub struct Message {
|
||||
pub filename: String,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn from_check(filename: String, check: Check) -> Self {
|
||||
Self {
|
||||
kind: check.kind,
|
||||
fixed: check.fix.map(|fix| fix.applied).unwrap_or_default(),
|
||||
location: Location::new(check.location.row(), check.location.column() + 1),
|
||||
end_location: Location::new(check.end_location.row(), check.end_location.column() + 1),
|
||||
filename,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Message {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
(&self.filename, self.location.row(), self.location.column()).cmp(&(
|
||||
|
||||
@@ -60,7 +60,8 @@ pub fn extract_noqa_line_for(lxr: &[LexResult]) -> Vec<usize> {
|
||||
min_line = min(min_line, start.row());
|
||||
max_line = max(max_line, start.row());
|
||||
|
||||
// For now, we only care about preserving noqa directives across multi-line strings.
|
||||
// For now, we only care about preserving noqa directives across multi-line
|
||||
// strings.
|
||||
if in_string {
|
||||
for i in (noqa_line_for.len())..(min_line - 1) {
|
||||
noqa_line_for.push(i + 1);
|
||||
@@ -123,7 +124,7 @@ fn add_noqa_inner(
|
||||
output.push_str(line);
|
||||
}
|
||||
Directive::All(start, _) => output.push_str(&line[..start]),
|
||||
Directive::Codes(start, _, _) => output.push_str(&line[..start]),
|
||||
Directive::Codes(start, ..) => output.push_str(&line[..start]),
|
||||
};
|
||||
let codes: Vec<&str> = codes.iter().map(|code| code.as_ref()).collect();
|
||||
output.push_str(" # noqa: ");
|
||||
|
||||
@@ -118,7 +118,7 @@ pub fn non_lowercase_variable_in_function(scope: &Scope, expr: &Expr, name: &str
|
||||
if !matches!(scope.kind, ScopeKind::Function(FunctionScope { .. })) {
|
||||
return None;
|
||||
}
|
||||
if !is_lower(name) {
|
||||
if name.to_lowercase() != name {
|
||||
return Some(Check::new(
|
||||
CheckKind::NonLowercaseVariableInFunction(name.to_string()),
|
||||
Range::from_located(expr),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Settings for the `pep8-naming` plugin.
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const IGNORE_NAMES: [&str; 12] = [
|
||||
"setUp",
|
||||
@@ -21,7 +21,7 @@ const CLASSMETHOD_DECORATORS: [&str; 1] = ["classmethod"];
|
||||
|
||||
const STATICMETHOD_DECORATORS: [&str; 1] = ["staticmethod"];
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
pub ignore_names: Option<Vec<String>>,
|
||||
@@ -29,7 +29,7 @@ pub struct Options {
|
||||
pub staticmethod_decorators: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct Settings {
|
||||
pub ignore_names: Vec<String>,
|
||||
pub classmethod_decorators: Vec<String>,
|
||||
|
||||
@@ -162,7 +162,7 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
|
||||
} = &docstring.node
|
||||
{
|
||||
if checker.settings.enabled.contains(&CheckCode::D201) {
|
||||
let (before, _, _) = checker.locator.partition_source_code_at(
|
||||
let (before, ..) = checker.locator.partition_source_code_at(
|
||||
&Range::from_located(parent),
|
||||
&Range::from_located(docstring),
|
||||
);
|
||||
@@ -248,7 +248,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
|
||||
if checker.settings.enabled.contains(&CheckCode::D203)
|
||||
|| checker.settings.enabled.contains(&CheckCode::D211)
|
||||
{
|
||||
let (before, _, _) = checker.locator.partition_source_code_at(
|
||||
let (before, ..) = checker.locator.partition_source_code_at(
|
||||
&Range::from_located(parent),
|
||||
&Range::from_located(docstring),
|
||||
);
|
||||
@@ -406,7 +406,8 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
|
||||
|
||||
let line_indent = helpers::leading_space(lines[i]);
|
||||
|
||||
// We only report tab indentation once, so only check if we haven't seen a tab yet.
|
||||
// We only report tab indentation once, so only check if we haven't seen a tab
|
||||
// yet.
|
||||
has_seen_tab = has_seen_tab || line_indent.contains('\t');
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::D207) {
|
||||
@@ -432,9 +433,10 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
|
||||
}
|
||||
|
||||
// Like pydocstyle, we only report over-indentation if either: (1) every line
|
||||
// (except, optionally, the last line) is over-indented, or (2) the last line (which
|
||||
// contains the closing quotation marks) is over-indented. We can't know if we've
|
||||
// achieved that condition until we've viewed all the lines, so for now, just track
|
||||
// (except, optionally, the last line) is over-indented, or (2) the last line
|
||||
// (which contains the closing quotation marks) is
|
||||
// over-indented. We can't know if we've achieved that condition
|
||||
// until we've viewed all the lines, so for now, just track
|
||||
// the over-indentation status of every line.
|
||||
if i < lines.len() - 1 {
|
||||
if line_indent.len() > docstring_indent.len() {
|
||||
@@ -855,7 +857,8 @@ pub fn not_empty(checker: &mut Checker, definition: &Definition) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// D212, D214, D215, D405, D406, D407, D408, D409, D410, D411, D412, D413, D414, D416, D417
|
||||
/// D212, D214, D215, D405, D406, D407, D408, D409, D410, D411, D412, D413,
|
||||
/// D414, D416, D417
|
||||
pub fn sections(checker: &mut Checker, definition: &Definition) {
|
||||
if let Some(docstring) = definition.docstring {
|
||||
if let ExprKind::Constant {
|
||||
@@ -1304,11 +1307,9 @@ fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args:
|
||||
.chain(arguments.kwonlyargs.iter())
|
||||
.skip(
|
||||
// If this is a non-static method, skip `cls` or `self`.
|
||||
if matches!(definition.kind, DefinitionKind::Method(_)) && !is_static(parent) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
},
|
||||
usize::from(
|
||||
matches!(definition.kind, DefinitionKind::Method(_)) && !is_static(parent),
|
||||
),
|
||||
)
|
||||
.collect();
|
||||
if let Some(arg) = &arguments.vararg {
|
||||
@@ -1403,7 +1404,8 @@ fn parameters_section(checker: &mut Checker, definition: &Definition, context: &
|
||||
// Otherwise, it's just a list of parameters on the current line.
|
||||
current_line.trim()
|
||||
};
|
||||
// Notably, NumPy lets you put multiple parameters of the same type on the same line.
|
||||
// Notably, NumPy lets you put multiple parameters of the same type on the same
|
||||
// line.
|
||||
for parameter in parameters.split(',') {
|
||||
docstring_args.insert(parameter.trim());
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ use anyhow::Result;
|
||||
use libcst_native::{Codegen, ImportNames, NameOrAttribute, SmallStatement, Statement};
|
||||
use rustpython_ast::Stmt;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::{helpers, Fix};
|
||||
use crate::cst::helpers::compose_module_path;
|
||||
use crate::cst::matchers::match_tree;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
/// Generate a Fix to remove any unused imports from an `import` statement.
|
||||
@@ -15,13 +15,7 @@ pub fn remove_unused_imports(
|
||||
parent: Option<&Stmt>,
|
||||
deleted: &[&Stmt],
|
||||
) -> Result<Fix> {
|
||||
let mut tree = match libcst_native::parse_module(
|
||||
locator.slice_source_code_range(&Range::from_located(stmt)),
|
||||
None,
|
||||
) {
|
||||
Ok(m) => m,
|
||||
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
|
||||
};
|
||||
let mut tree = match_tree(locator, stmt)?;
|
||||
|
||||
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
|
||||
body
|
||||
@@ -78,13 +72,7 @@ pub fn remove_unused_import_froms(
|
||||
parent: Option<&Stmt>,
|
||||
deleted: &[&Stmt],
|
||||
) -> Result<Fix> {
|
||||
let mut tree = match libcst_native::parse_module(
|
||||
locator.slice_source_code_range(&Range::from_located(stmt)),
|
||||
None,
|
||||
) {
|
||||
Ok(m) => m,
|
||||
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
|
||||
};
|
||||
let mut tree = match_tree(locator, stmt)?;
|
||||
|
||||
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
|
||||
body
|
||||
|
||||
@@ -153,8 +153,8 @@ pub const BUILTINS: &[&str] = &[
|
||||
"zip",
|
||||
];
|
||||
|
||||
// Globally defined names which are not attributes of the builtins module, or are only present on
|
||||
// some platforms.
|
||||
// Globally defined names which are not attributes of the builtins module, or
|
||||
// are only present on some platforms.
|
||||
pub const MAGIC_GLOBALS: &[&str] = &[
|
||||
"WindowsError",
|
||||
"__annotations__",
|
||||
|
||||
@@ -1,101 +1,282 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
static ANNOTATED_SUBSCRIPTS: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
// See: https://pypi.org/project/typing-extensions/
|
||||
static TYPING_EXTENSIONS: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
BTreeSet::from([
|
||||
"AbstractAsyncContextManager",
|
||||
"AbstractContextManager",
|
||||
"AbstractSet",
|
||||
// "Annotated",
|
||||
"Annotated",
|
||||
"Any",
|
||||
"AsyncContextManager",
|
||||
"AsyncGenerator",
|
||||
"AsyncIterable",
|
||||
"AsyncIterator",
|
||||
"Awaitable",
|
||||
"BinaryIO",
|
||||
"BsdDbShelf",
|
||||
"ByteString",
|
||||
"Callable",
|
||||
"ChainMap",
|
||||
"ClassVar",
|
||||
"Collection",
|
||||
"Concatenate",
|
||||
"Container",
|
||||
"ContextManager",
|
||||
"Coroutine",
|
||||
"Counter",
|
||||
"Counter",
|
||||
"DbfilenameShelf",
|
||||
"DefaultDict",
|
||||
"Deque",
|
||||
"Dict",
|
||||
"Field",
|
||||
"Final",
|
||||
"FrozenSet",
|
||||
"Generator",
|
||||
"Iterator",
|
||||
"Generic",
|
||||
"IO",
|
||||
"ItemsView",
|
||||
"Iterable",
|
||||
"Iterator",
|
||||
"KeysView",
|
||||
"LifoQueue",
|
||||
"List",
|
||||
"Mapping",
|
||||
"MappingProxyType",
|
||||
"MappingView",
|
||||
"Match",
|
||||
"MutableMapping",
|
||||
"MutableSequence",
|
||||
"MutableSet",
|
||||
"Optional",
|
||||
"Literal",
|
||||
"LiteralString",
|
||||
"NamedTuple",
|
||||
"Never",
|
||||
"NewType",
|
||||
"NotRequired",
|
||||
"OrderedDict",
|
||||
"PathLike",
|
||||
"Pattern",
|
||||
"PriorityQueue",
|
||||
"ParamSpec",
|
||||
"ParamSpecArgs",
|
||||
"ParamSpecKwargs",
|
||||
"Protocol",
|
||||
"Queue",
|
||||
"Reversible",
|
||||
"Sequence",
|
||||
"Set",
|
||||
"Shelf",
|
||||
"SimpleQueue",
|
||||
"TextIO",
|
||||
"Tuple",
|
||||
"Required",
|
||||
"Self",
|
||||
"TYPE_CHECKING",
|
||||
"Text",
|
||||
"Type",
|
||||
"TypeAlias",
|
||||
"TypeGuard",
|
||||
"Union",
|
||||
"ValuesView",
|
||||
"WeakKeyDictionary",
|
||||
"WeakMethod",
|
||||
"WeakSet",
|
||||
"WeakValueDictionary",
|
||||
"cached_property",
|
||||
"defaultdict",
|
||||
"deque",
|
||||
"dict",
|
||||
"frozenset",
|
||||
"list",
|
||||
"partialmethod",
|
||||
"set",
|
||||
"tuple",
|
||||
"type",
|
||||
"TypeVar",
|
||||
"TypeVarTuple",
|
||||
"TypedDict",
|
||||
"Unpack",
|
||||
"assert_never",
|
||||
"assert_type",
|
||||
"clear_overloads",
|
||||
"final",
|
||||
"get_Type_hints",
|
||||
"get_args",
|
||||
"get_origin",
|
||||
"get_overloads",
|
||||
"is_typeddict",
|
||||
"overload",
|
||||
"reveal_type",
|
||||
"runtime_checkable",
|
||||
])
|
||||
});
|
||||
|
||||
pub fn is_annotated_subscript(name: &str) -> bool {
|
||||
ANNOTATED_SUBSCRIPTS.contains(name)
|
||||
pub fn in_extensions(name: &str) -> bool {
|
||||
TYPING_EXTENSIONS.contains(name)
|
||||
}
|
||||
|
||||
pub fn is_pep593_annotated_subscript(name: &str) -> bool {
|
||||
// See: https://docs.python.org/3/library/typing.html
|
||||
static IMPORTED_SUBSCRIPTS: Lazy<BTreeMap<&'static str, BTreeSet<&'static str>>> =
|
||||
Lazy::new(|| {
|
||||
let mut import_map = BTreeMap::new();
|
||||
for (name, module) in [
|
||||
// `collections`
|
||||
("ChainMap", "collections"),
|
||||
("Counter", "collections"),
|
||||
("OrderedDict", "collections"),
|
||||
("defaultdict", "collections"),
|
||||
("deque", "collections"),
|
||||
// `collections.abc`
|
||||
("AsyncGenerator", "collections.abc"),
|
||||
("AsyncIterable", "collections.abc"),
|
||||
("AsyncIterator", "collections.abc"),
|
||||
("Awaitable", "collections.abc"),
|
||||
("ByteString", "collections.abc"),
|
||||
("Callable", "collections.abc"),
|
||||
("Collection", "collections.abc"),
|
||||
("Container", "collections.abc"),
|
||||
("Coroutine", "collections.abc"),
|
||||
("Generator", "collections.abc"),
|
||||
("ItemsView", "collections.abc"),
|
||||
("Iterable", "collections.abc"),
|
||||
("Iterator", "collections.abc"),
|
||||
("KeysView", "collections.abc"),
|
||||
("Mapping", "collections.abc"),
|
||||
("MappingView", "collections.abc"),
|
||||
("MutableMapping", "collections.abc"),
|
||||
("MutableSequence", "collections.abc"),
|
||||
("MutableSet", "collections.abc"),
|
||||
("Reversible", "collections.abc"),
|
||||
("Sequence", "collections.abc"),
|
||||
("Set", "collections.abc"),
|
||||
("ValuesView", "collections.abc"),
|
||||
// `contextlib`
|
||||
("AbstractAsyncContextManager", "contextlib"),
|
||||
("AbstractContextManager", "contextlib"),
|
||||
// `re`
|
||||
("Match", "re"),
|
||||
("Pattern", "re"),
|
||||
// `typing`
|
||||
("AbstractSet", "typing"),
|
||||
("Annotated", "typing"),
|
||||
("AsyncContextManager", "typing"),
|
||||
("AsyncGenerator", "typing"),
|
||||
("AsyncIterator", "typing"),
|
||||
("Awaitable", "typing"),
|
||||
("BinaryIO", "typing"),
|
||||
("ByteString", "typing"),
|
||||
("Callable", "typing"),
|
||||
("ChainMap", "typing"),
|
||||
("ClassVar", "typing"),
|
||||
("Collection", "typing"),
|
||||
("Concatenate", "typing"),
|
||||
("Container", "typing"),
|
||||
("ContextManager", "typing"),
|
||||
("Coroutine", "typing"),
|
||||
("Counter", "typing"),
|
||||
("DefaultDict", "typing"),
|
||||
("Deque", "typing"),
|
||||
("Dict", "typing"),
|
||||
("Final", "typing"),
|
||||
("FrozenSet", "typing"),
|
||||
("Generator", "typing"),
|
||||
("Generic", "typing"),
|
||||
("IO", "typing"),
|
||||
("ItemsView", "typing"),
|
||||
("Iterable", "typing"),
|
||||
("Iterator", "typing"),
|
||||
("KeysView", "typing"),
|
||||
("List", "typing"),
|
||||
("Mapping", "typing"),
|
||||
("Match", "typing"),
|
||||
("MutableMapping", "typing"),
|
||||
("MutableSequence", "typing"),
|
||||
("MutableSet", "typing"),
|
||||
("Optional", "typing"),
|
||||
("OrderedDict", "typing"),
|
||||
("Pattern", "typing"),
|
||||
("Reversible", "typing"),
|
||||
("Sequence", "typing"),
|
||||
("Set", "typing"),
|
||||
("TextIO", "typing"),
|
||||
("Tuple", "typing"),
|
||||
("Type", "typing"),
|
||||
("TypeGuard", "typing"),
|
||||
("Union", "typing"),
|
||||
("Unpack", "typing"),
|
||||
("ValuesView", "typing"),
|
||||
// `typing.io`
|
||||
("BinaryIO", "typing.io"),
|
||||
("IO", "typing.io"),
|
||||
("TextIO", "typing.io"),
|
||||
// `typing.re`
|
||||
("Match", "typing.re"),
|
||||
("Pattern", "typing.re"),
|
||||
// `typing_extensions`
|
||||
("Annotated", "typing_extensions"),
|
||||
("AsyncContextManager", "typing_extensions"),
|
||||
("AsyncGenerator", "typing_extensions"),
|
||||
("AsyncIterable", "typing_extensions"),
|
||||
("AsyncIterator", "typing_extensions"),
|
||||
("Awaitable", "typing_extensions"),
|
||||
("ChainMap", "typing_extensions"),
|
||||
("ClassVar", "typing_extensions"),
|
||||
("Concatenate", "typing_extensions"),
|
||||
("ContextManager", "typing_extensions"),
|
||||
("Coroutine", "typing_extensions"),
|
||||
("Counter", "typing_extensions"),
|
||||
("DefaultDict", "typing_extensions"),
|
||||
("Deque", "typing_extensions"),
|
||||
("Literal", "typing_extensions"),
|
||||
("Type", "typing_extensions"),
|
||||
// `weakref`
|
||||
("WeakKeyDictionary", "weakref"),
|
||||
("WeakSet", "weakref"),
|
||||
("WeakValueDictionary", "weakref"),
|
||||
] {
|
||||
import_map
|
||||
.entry(name)
|
||||
.or_insert_with(BTreeSet::new)
|
||||
.insert(module);
|
||||
}
|
||||
import_map
|
||||
});
|
||||
|
||||
// These are all assumed to come from the `typing` module.
|
||||
// See: https://peps.python.org/pep-0585/
|
||||
static PEP_585_BUILTINS_ELIGIBLE: Lazy<BTreeSet<&'static str>> =
|
||||
Lazy::new(|| BTreeSet::from(["Dict", "FrozenSet", "List", "Set", "Tuple", "Type"]));
|
||||
|
||||
// These are all assumed to come from the `typing` module.
|
||||
// See: https://peps.python.org/pep-0585/
|
||||
static PEP_585_BUILTINS: Lazy<BTreeSet<&'static str>> =
|
||||
Lazy::new(|| BTreeSet::from(["dict", "frozenset", "list", "set", "tuple", "type"]));
|
||||
|
||||
fn is_pep593_annotated_subscript(name: &str) -> bool {
|
||||
name == "Annotated"
|
||||
}
|
||||
|
||||
static PEP_585_BUILTINS: Lazy<BTreeSet<&'static str>> =
|
||||
Lazy::new(|| BTreeSet::from(["Dict", "FrozenSet", "List", "Set", "Tuple", "Type"]));
|
||||
|
||||
pub fn is_pep585_builtin(name: &str) -> bool {
|
||||
PEP_585_BUILTINS.contains(name)
|
||||
pub enum SubscriptKind {
|
||||
AnnotatedSubscript,
|
||||
PEP593AnnotatedSubscript,
|
||||
}
|
||||
|
||||
pub fn match_annotated_subscript(
|
||||
expr: &Expr,
|
||||
imports: &BTreeMap<&str, BTreeSet<&str>>,
|
||||
) -> Option<SubscriptKind> {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, value, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
// If `id` is `typing` and `attr` is `Union`, verify that `typing.Union` is an
|
||||
// annotated subscript.
|
||||
if IMPORTED_SUBSCRIPTS
|
||||
.get(&attr.as_str())
|
||||
.map(|imports| imports.contains(&id.as_str()))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return if is_pep593_annotated_subscript(attr) {
|
||||
Some(SubscriptKind::PEP593AnnotatedSubscript)
|
||||
} else {
|
||||
Some(SubscriptKind::AnnotatedSubscript)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Name { id, .. } => {
|
||||
// Built-ins (no import necessary).
|
||||
if PEP_585_BUILTINS.contains(&id.as_str()) {
|
||||
return Some(SubscriptKind::AnnotatedSubscript);
|
||||
}
|
||||
|
||||
// Verify that, e.g., `Union` is a reference to `typing.Union`.
|
||||
if let Some(modules) = IMPORTED_SUBSCRIPTS.get(&id.as_str()) {
|
||||
for module in modules {
|
||||
if imports
|
||||
.get(module)
|
||||
.map(|imports| imports.contains(&id.as_str()))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return if is_pep593_annotated_subscript(id) {
|
||||
Some(SubscriptKind::PEP593AnnotatedSubscript)
|
||||
} else {
|
||||
Some(SubscriptKind::AnnotatedSubscript)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns `true` if `Expr` represents a reference to a typing object with a
|
||||
/// PEP 585 built-in. Note that none of the PEP 585 built-ins are in
|
||||
/// `typing_extensions`.
|
||||
pub fn is_pep585_builtin(expr: &Expr, typing_imports: Option<&BTreeSet<&str>>) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, value, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
id == "typing" && PEP_585_BUILTINS_ELIGIBLE.contains(&attr.as_str())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
ExprKind::Name { id, .. } => {
|
||||
typing_imports
|
||||
.map(|imports| imports.contains(&id.as_str()))
|
||||
.unwrap_or_default()
|
||||
&& PEP_585_BUILTINS_ELIGIBLE.contains(&id.as_str())
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,9 @@ pub fn super_args(
|
||||
|
||||
let mut parents = parents.iter().rev();
|
||||
|
||||
// For a `super` invocation to be unnecessary, the first argument needs to match the enclosing
|
||||
// class, and the second argument needs to match the first argument to the enclosing function.
|
||||
// For a `super` invocation to be unnecessary, the first argument needs to match
|
||||
// the enclosing class, and the second argument needs to match the first
|
||||
// argument to the enclosing function.
|
||||
if let [first_arg, second_arg] = args {
|
||||
// Find the enclosing function definition (if any).
|
||||
if let Some(StmtKind::FunctionDef {
|
||||
|
||||
@@ -79,7 +79,8 @@ pub fn remove_class_def_base(
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
// Case 3: `object` is the last node, so we have to find the last token that isn't a comma.
|
||||
// Case 3: `object` is the last node, so we have to find the last token that
|
||||
// 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(content).flatten() {
|
||||
|
||||
@@ -4,22 +4,18 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::python::typing;
|
||||
|
||||
pub fn use_pep585_annotation(checker: &mut Checker, expr: &Expr, id: &str) {
|
||||
// TODO(charlie): Verify that the builtin is imported from the `typing` module.
|
||||
if typing::is_pep585_builtin(id) {
|
||||
let mut check = Check::new(
|
||||
CheckKind::UsePEP585Annotation(id.to_string()),
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if checker.patch() {
|
||||
check.amend(Fix::replacement(
|
||||
id.to_lowercase(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
let mut check = Check::new(
|
||||
CheckKind::UsePEP585Annotation(id.to_string()),
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if checker.patch() {
|
||||
check.amend(Fix::replacement(
|
||||
id.to_lowercase(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprKind, Operator};
|
||||
|
||||
use crate::ast::helpers::match_name_or_attr;
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::check_ast::Checker;
|
||||
@@ -43,7 +42,7 @@ fn union(elts: &[Expr]) -> Expr {
|
||||
}
|
||||
|
||||
pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, slice: &Expr) {
|
||||
if match_name_or_attr(value, "Optional") {
|
||||
if checker.match_typing_module(value, "Optional") {
|
||||
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
|
||||
if checker.patch() {
|
||||
let mut generator = SourceGenerator::new();
|
||||
@@ -58,7 +57,7 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
} else if match_name_or_attr(value, "Union") {
|
||||
} else if checker.match_typing_module(value, "Union") {
|
||||
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
|
||||
if checker.patch() {
|
||||
match &slice.node {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! User-provided program settings, taking into account pyproject.toml and command-line options.
|
||||
//! Structure mirrors the user-facing representation of the various parameters.
|
||||
//! User-provided program settings, taking into account pyproject.toml and
|
||||
//! command-line options. Structure mirrors the user-facing representation of
|
||||
//! the various parameters.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
@@ -9,7 +11,7 @@ use regex::Regex;
|
||||
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::settings::pyproject::load_options;
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
|
||||
use crate::settings::types::{FilePattern, PythonVersion};
|
||||
use crate::{flake8_quotes, pep8_naming};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -21,7 +23,7 @@ pub struct Configuration {
|
||||
pub extend_select: Vec<CheckCodePrefix>,
|
||||
pub ignore: Vec<CheckCodePrefix>,
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>>,
|
||||
pub select: Vec<CheckCodePrefix>,
|
||||
pub target_version: PythonVersion,
|
||||
// Plugins
|
||||
@@ -80,21 +82,18 @@ impl Configuration {
|
||||
.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
|
||||
extend_exclude: options
|
||||
.extend_exclude
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, project_root))
|
||||
.collect(),
|
||||
extend_ignore: options.extend_ignore,
|
||||
extend_ignore: options.extend_ignore.unwrap_or_default(),
|
||||
select: options
|
||||
.select
|
||||
.unwrap_or_else(|| vec![CheckCodePrefix::E, CheckCodePrefix::F]),
|
||||
extend_select: options.extend_select,
|
||||
ignore: options.ignore,
|
||||
extend_select: options.extend_select.unwrap_or_default(),
|
||||
ignore: options.ignore.unwrap_or_default(),
|
||||
line_length: options.line_length.unwrap_or(88),
|
||||
per_file_ignores: options
|
||||
.per_file_ignores
|
||||
.into_iter()
|
||||
.map(|pair| PerFileIgnore::new(pair, project_root))
|
||||
.collect(),
|
||||
per_file_ignores: options.per_file_ignores.unwrap_or_default(),
|
||||
// Plugins
|
||||
flake8_quotes: options
|
||||
.flake8_quotes
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
//! Effective program settings, taking into account pyproject.toml and command-line options.
|
||||
//! Structure is optimized for internal usage, as opposed to external visibility or parsing.
|
||||
//! Effective program settings, taking into account pyproject.toml and
|
||||
//! command-line options. Structure is optimized for internal usage, as opposed
|
||||
//! to external visibility or parsing.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use regex::Regex;
|
||||
@@ -47,7 +48,7 @@ impl Settings {
|
||||
flake8_quotes: config.flake8_quotes,
|
||||
line_length: config.line_length,
|
||||
pep8_naming: config.pep8_naming,
|
||||
per_file_ignores: config.per_file_ignores,
|
||||
per_file_ignores: resolve_per_file_ignores(&config.per_file_ignores),
|
||||
target_version: config.target_version,
|
||||
}
|
||||
}
|
||||
@@ -56,10 +57,10 @@ impl Settings {
|
||||
Self {
|
||||
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
|
||||
enabled: BTreeSet::from([check_code]),
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
exclude: Default::default(),
|
||||
extend_exclude: Default::default(),
|
||||
line_length: 88,
|
||||
per_file_ignores: vec![],
|
||||
per_file_ignores: Default::default(),
|
||||
target_version: PythonVersion::Py310,
|
||||
flake8_quotes: Default::default(),
|
||||
pep8_naming: Default::default(),
|
||||
@@ -70,10 +71,10 @@ impl Settings {
|
||||
Self {
|
||||
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
|
||||
enabled: BTreeSet::from_iter(check_codes),
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
exclude: Default::default(),
|
||||
extend_exclude: Default::default(),
|
||||
line_length: 88,
|
||||
per_file_ignores: vec![],
|
||||
per_file_ignores: Default::default(),
|
||||
target_version: PythonVersion::Py310,
|
||||
flake8_quotes: Default::default(),
|
||||
pep8_naming: Default::default(),
|
||||
@@ -83,18 +84,24 @@ impl Settings {
|
||||
|
||||
impl Hash for Settings {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.line_length.hash(state);
|
||||
// Add base properties in alphabetical order.
|
||||
self.dummy_variable_rgx.as_str().hash(state);
|
||||
for value in self.enabled.iter() {
|
||||
value.hash(state);
|
||||
}
|
||||
self.line_length.hash(state);
|
||||
for value in self.per_file_ignores.iter() {
|
||||
value.hash(state);
|
||||
}
|
||||
self.target_version.hash(state);
|
||||
// Add plugin properties in alphabetical order.
|
||||
self.flake8_quotes.hash(state);
|
||||
self.pep8_naming.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a set of selected and ignored prefixes, resolve the set of enabled error codes.
|
||||
/// Given a set of selected and ignored prefixes, resolve the set of enabled
|
||||
/// error codes.
|
||||
fn resolve_codes(
|
||||
select: &[CheckCodePrefix],
|
||||
extend_select: &[CheckCodePrefix],
|
||||
@@ -136,6 +143,15 @@ fn resolve_codes(
|
||||
codes
|
||||
}
|
||||
|
||||
fn resolve_per_file_ignores(
|
||||
per_file_ignores: &BTreeMap<String, Vec<CheckCodePrefix>>,
|
||||
) -> Vec<PerFileIgnore> {
|
||||
per_file_ignores
|
||||
.iter()
|
||||
.map(|(pattern, prefixes)| PerFileIgnore::new(pattern, prefixes, &None))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
//! Options that the user can provide via pyproject.toml.
|
||||
|
||||
use serde::Deserialize;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::settings::types::{PythonVersion, StrCheckCodePair};
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::{flake8_quotes, pep8_naming};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
pub line_length: Option<usize>,
|
||||
pub exclude: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub extend_exclude: Vec<String>,
|
||||
pub extend_exclude: Option<Vec<String>>,
|
||||
pub select: Option<Vec<CheckCodePrefix>>,
|
||||
#[serde(default)]
|
||||
pub extend_select: Vec<CheckCodePrefix>,
|
||||
#[serde(default)]
|
||||
pub ignore: Vec<CheckCodePrefix>,
|
||||
#[serde(default)]
|
||||
pub extend_ignore: Vec<CheckCodePrefix>,
|
||||
#[serde(default)]
|
||||
pub per_file_ignores: Vec<StrCheckCodePair>,
|
||||
pub extend_select: Option<Vec<CheckCodePrefix>>,
|
||||
pub ignore: Option<Vec<CheckCodePrefix>>,
|
||||
pub extend_ignore: Option<Vec<CheckCodePrefix>>,
|
||||
pub per_file_ignores: Option<BTreeMap<String, Vec<CheckCodePrefix>>>,
|
||||
pub dummy_variable_rgx: Option<String>,
|
||||
pub target_version: Option<PythonVersion>,
|
||||
// Plugins
|
||||
|
||||
@@ -6,21 +6,31 @@ use anyhow::Result;
|
||||
use common_path::common_path_all;
|
||||
use log::debug;
|
||||
use path_absolutize::Absolutize;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::fs;
|
||||
use crate::settings::options::Options;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct Tools {
|
||||
ruff: Option<Options>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
struct Pyproject {
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Pyproject {
|
||||
tool: Option<Tools>,
|
||||
}
|
||||
|
||||
impl Pyproject {
|
||||
pub fn new(options: Options) -> Self {
|
||||
Self {
|
||||
tool: Some(Tools {
|
||||
ruff: Some(options),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_pyproject_toml(path: &Path) -> Result<Pyproject> {
|
||||
let contents = fs::read_file(path)?;
|
||||
toml::from_str(&contents).map_err(|e| e.into())
|
||||
@@ -86,6 +96,7 @@ pub fn load_options(pyproject: &Option<PathBuf>) -> Result<Options> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
use std::env::current_dir;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
@@ -97,7 +108,7 @@ mod tests {
|
||||
use crate::settings::pyproject::{
|
||||
find_project_root, find_pyproject_toml, parse_pyproject_toml, Options, Pyproject, Tools,
|
||||
};
|
||||
use crate::settings::types::StrCheckCodePair;
|
||||
use crate::settings::types::PatternPrefixPair;
|
||||
use crate::{flake8_quotes, pep8_naming};
|
||||
|
||||
#[test]
|
||||
@@ -124,12 +135,12 @@ mod tests {
|
||||
ruff: Some(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: vec![],
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
extend_select: vec![],
|
||||
ignore: vec![],
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: None,
|
||||
@@ -151,12 +162,12 @@ line-length = 79
|
||||
ruff: Some(Options {
|
||||
line_length: Some(79),
|
||||
exclude: None,
|
||||
extend_exclude: vec![],
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
extend_select: vec![],
|
||||
ignore: vec![],
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: None,
|
||||
@@ -178,12 +189,12 @@ exclude = ["foo.py"]
|
||||
ruff: Some(Options {
|
||||
line_length: None,
|
||||
exclude: Some(vec!["foo.py".to_string()]),
|
||||
extend_exclude: vec![],
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
extend_select: vec![],
|
||||
ignore: vec![],
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: None,
|
||||
@@ -205,12 +216,12 @@ select = ["E501"]
|
||||
ruff: Some(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: vec![],
|
||||
extend_exclude: None,
|
||||
select: Some(vec![CheckCodePrefix::E501]),
|
||||
extend_select: vec![],
|
||||
ignore: vec![],
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: None,
|
||||
@@ -233,12 +244,12 @@ ignore = ["E501"]
|
||||
ruff: Some(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: vec![],
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
extend_select: vec![CheckCodePrefix::M001],
|
||||
ignore: vec![CheckCodePrefix::E501],
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
extend_select: Some(vec![CheckCodePrefix::M001]),
|
||||
ignore: Some(vec![CheckCodePrefix::E501]),
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: None,
|
||||
@@ -300,19 +311,19 @@ other-attribute = 1
|
||||
Options {
|
||||
line_length: Some(88),
|
||||
exclude: None,
|
||||
extend_exclude: vec![
|
||||
extend_exclude: Some(vec![
|
||||
"excluded.py".to_string(),
|
||||
"migrations".to_string(),
|
||||
"directory/also_excluded.py".to_string(),
|
||||
],
|
||||
]),
|
||||
select: None,
|
||||
extend_select: vec![],
|
||||
ignore: vec![],
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![StrCheckCodePair {
|
||||
pattern: "__init__.py".to_string(),
|
||||
code: CheckCodePrefix::F401
|
||||
}],
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: Some(BTreeMap::from([(
|
||||
"__init__.py".to_string(),
|
||||
vec![CheckCodePrefix::F401]
|
||||
),])),
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: Some(flake8_quotes::settings::Options {
|
||||
@@ -347,21 +358,21 @@ other-attribute = 1
|
||||
|
||||
#[test]
|
||||
fn str_check_code_pair_strings() {
|
||||
let result = StrCheckCodePair::from_str("foo:E501");
|
||||
let result = PatternPrefixPair::from_str("foo:E501");
|
||||
assert!(result.is_ok());
|
||||
let result = StrCheckCodePair::from_str("foo: E501");
|
||||
let result = PatternPrefixPair::from_str("foo: E501");
|
||||
assert!(result.is_ok());
|
||||
let result = StrCheckCodePair::from_str("E501:foo");
|
||||
let result = PatternPrefixPair::from_str("E501:foo");
|
||||
assert!(result.is_err());
|
||||
let result = StrCheckCodePair::from_str("E501");
|
||||
let result = PatternPrefixPair::from_str("E501");
|
||||
assert!(result.is_err());
|
||||
let result = StrCheckCodePair::from_str("foo");
|
||||
let result = PatternPrefixPair::from_str("foo");
|
||||
assert!(result.is_err());
|
||||
let result = StrCheckCodePair::from_str("foo:E501:E402");
|
||||
let result = PatternPrefixPair::from_str("foo:E501:E402");
|
||||
assert!(result.is_err());
|
||||
let result = StrCheckCodePair::from_str("**/bar:E501");
|
||||
let result = PatternPrefixPair::from_str("**/bar:E501");
|
||||
assert!(result.is_ok());
|
||||
let result = StrCheckCodePair::from_str("bar:E502");
|
||||
let result = PatternPrefixPair::from_str("bar:E502");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::checks::CheckCode;
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::fs;
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
pub enum PythonVersion {
|
||||
Py33,
|
||||
Py34,
|
||||
@@ -75,24 +75,28 @@ pub struct PerFileIgnore {
|
||||
}
|
||||
|
||||
impl PerFileIgnore {
|
||||
pub fn new(user_in: StrCheckCodePair, project_root: &Option<PathBuf>) -> Self {
|
||||
let pattern = FilePattern::from_user(user_in.pattern.as_str(), project_root);
|
||||
let codes = BTreeSet::from_iter(user_in.code.codes());
|
||||
pub fn new(
|
||||
pattern: &str,
|
||||
prefixes: &[CheckCodePrefix],
|
||||
project_root: &Option<PathBuf>,
|
||||
) -> Self {
|
||||
let pattern = FilePattern::from_user(pattern, project_root);
|
||||
let codes = BTreeSet::from_iter(prefixes.iter().flat_map(|prefix| prefix.codes()));
|
||||
Self { pattern, codes }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct StrCheckCodePair {
|
||||
pub struct PatternPrefixPair {
|
||||
pub pattern: String,
|
||||
pub code: CheckCodePrefix,
|
||||
pub prefix: CheckCodePrefix,
|
||||
}
|
||||
|
||||
impl StrCheckCodePair {
|
||||
impl PatternPrefixPair {
|
||||
const EXPECTED_PATTERN: &'static str = "<FilePattern>:<CheckCode> pattern";
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for StrCheckCodePair {
|
||||
impl<'de> Deserialize<'de> for PatternPrefixPair {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
@@ -107,7 +111,7 @@ impl<'de> Deserialize<'de> for StrCheckCodePair {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for StrCheckCodePair {
|
||||
impl FromStr for PatternPrefixPair {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
@@ -118,8 +122,8 @@ impl FromStr for StrCheckCodePair {
|
||||
}
|
||||
(tokens[0].trim(), tokens[1].trim())
|
||||
};
|
||||
let code = CheckCodePrefix::from_str(code_string)?;
|
||||
let pattern = pattern_str.into();
|
||||
Ok(Self { pattern, code })
|
||||
let prefix = CheckCodePrefix::from_str(code_string)?;
|
||||
Ok(Self { pattern, prefix })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
//! Structs to render user-facing settings.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
|
||||
use crate::settings::types::{FilePattern, PythonVersion};
|
||||
use crate::{flake8_quotes, pep8_naming, Configuration};
|
||||
|
||||
/// Struct to render user-facing exclusion patterns.
|
||||
@@ -41,7 +42,7 @@ pub struct UserConfiguration {
|
||||
pub extend_select: Vec<CheckCodePrefix>,
|
||||
pub ignore: Vec<CheckCodePrefix>,
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>>,
|
||||
pub select: Vec<CheckCodePrefix>,
|
||||
pub target_version: PythonVersion,
|
||||
// Plugins
|
||||
|
||||
@@ -9,5 +9,31 @@ expression: checks
|
||||
end_location:
|
||||
row: 1
|
||||
column: 29
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "[x for x in range(3)]"
|
||||
location:
|
||||
row: 1
|
||||
column: 4
|
||||
end_location:
|
||||
row: 1
|
||||
column: 29
|
||||
applied: false
|
||||
- kind: UnnecessaryGeneratorList
|
||||
location:
|
||||
row: 2
|
||||
column: 4
|
||||
end_location:
|
||||
row: 4
|
||||
column: 1
|
||||
fix:
|
||||
patch:
|
||||
content: "[\n x for x in range(3)\n]"
|
||||
location:
|
||||
row: 2
|
||||
column: 4
|
||||
end_location:
|
||||
row: 4
|
||||
column: 1
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -9,5 +9,31 @@ expression: checks
|
||||
end_location:
|
||||
row: 1
|
||||
column: 28
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "{x for x in range(3)}"
|
||||
location:
|
||||
row: 1
|
||||
column: 4
|
||||
end_location:
|
||||
row: 1
|
||||
column: 28
|
||||
applied: false
|
||||
- kind: UnnecessaryGeneratorSet
|
||||
location:
|
||||
row: 2
|
||||
column: 4
|
||||
end_location:
|
||||
row: 4
|
||||
column: 1
|
||||
fix:
|
||||
patch:
|
||||
content: "{\n x for x in range(3)\n}"
|
||||
location:
|
||||
row: 2
|
||||
column: 4
|
||||
end_location:
|
||||
row: 4
|
||||
column: 1
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -9,5 +9,31 @@ expression: checks
|
||||
end_location:
|
||||
row: 1
|
||||
column: 30
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "{x: x for x in range(3)}"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 1
|
||||
column: 30
|
||||
applied: false
|
||||
- kind: UnnecessaryGeneratorDict
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 1
|
||||
fix:
|
||||
patch:
|
||||
content: "{\n x: x for x in range(3)\n}"
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 1
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -9,5 +9,31 @@ expression: checks
|
||||
end_location:
|
||||
row: 1
|
||||
column: 30
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "{x for x in range(3)}"
|
||||
location:
|
||||
row: 1
|
||||
column: 4
|
||||
end_location:
|
||||
row: 1
|
||||
column: 30
|
||||
applied: false
|
||||
- kind: UnnecessaryListComprehensionSet
|
||||
location:
|
||||
row: 2
|
||||
column: 4
|
||||
end_location:
|
||||
row: 4
|
||||
column: 1
|
||||
fix:
|
||||
patch:
|
||||
content: "{\n x for x in range(3)\n}"
|
||||
location:
|
||||
row: 2
|
||||
column: 4
|
||||
end_location:
|
||||
row: 4
|
||||
column: 1
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -10,7 +10,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 1
|
||||
column: 16
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "{1, 2}"
|
||||
location:
|
||||
row: 1
|
||||
column: 5
|
||||
end_location:
|
||||
row: 1
|
||||
column: 16
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryLiteralSet: tuple
|
||||
location:
|
||||
@@ -19,7 +28,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 2
|
||||
column: 16
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "{1, 2}"
|
||||
location:
|
||||
row: 2
|
||||
column: 5
|
||||
end_location:
|
||||
row: 2
|
||||
column: 16
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryLiteralSet: list
|
||||
location:
|
||||
@@ -28,7 +46,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 3
|
||||
column: 12
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: set()
|
||||
location:
|
||||
row: 3
|
||||
column: 5
|
||||
end_location:
|
||||
row: 3
|
||||
column: 12
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryLiteralSet: tuple
|
||||
location:
|
||||
@@ -37,5 +64,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 4
|
||||
column: 12
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: set()
|
||||
location:
|
||||
row: 4
|
||||
column: 5
|
||||
end_location:
|
||||
row: 4
|
||||
column: 12
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -10,7 +10,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 1
|
||||
column: 11
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: ()
|
||||
location:
|
||||
row: 1
|
||||
column: 4
|
||||
end_location:
|
||||
row: 1
|
||||
column: 11
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryCollectionCall: list
|
||||
location:
|
||||
@@ -19,7 +28,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 2
|
||||
column: 10
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "[]"
|
||||
location:
|
||||
row: 2
|
||||
column: 4
|
||||
end_location:
|
||||
row: 2
|
||||
column: 10
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryCollectionCall: dict
|
||||
location:
|
||||
@@ -28,7 +46,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 3
|
||||
column: 11
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "{}"
|
||||
location:
|
||||
row: 3
|
||||
column: 5
|
||||
end_location:
|
||||
row: 3
|
||||
column: 11
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryCollectionCall: dict
|
||||
location:
|
||||
|
||||
@@ -9,24 +9,87 @@ expression: checks
|
||||
column: 5
|
||||
end_location:
|
||||
row: 1
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryLiteralWithinTupleCall: tuple
|
||||
location:
|
||||
row: 2
|
||||
column: 5
|
||||
end_location:
|
||||
row: 2
|
||||
column: 18
|
||||
fix: ~
|
||||
column: 14
|
||||
fix:
|
||||
patch:
|
||||
content: ()
|
||||
location:
|
||||
row: 1
|
||||
column: 5
|
||||
end_location:
|
||||
row: 1
|
||||
column: 14
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryLiteralWithinTupleCall: list
|
||||
location:
|
||||
row: 2
|
||||
column: 5
|
||||
end_location:
|
||||
row: 2
|
||||
column: 18
|
||||
fix:
|
||||
patch:
|
||||
content: "(1, 2)"
|
||||
location:
|
||||
row: 2
|
||||
column: 5
|
||||
end_location:
|
||||
row: 2
|
||||
column: 18
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryLiteralWithinTupleCall: tuple
|
||||
location:
|
||||
row: 3
|
||||
column: 5
|
||||
end_location:
|
||||
row: 3
|
||||
column: 14
|
||||
fix: ~
|
||||
column: 18
|
||||
fix:
|
||||
patch:
|
||||
content: "(1, 2)"
|
||||
location:
|
||||
row: 3
|
||||
column: 5
|
||||
end_location:
|
||||
row: 3
|
||||
column: 18
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryLiteralWithinTupleCall: list
|
||||
location:
|
||||
row: 4
|
||||
column: 5
|
||||
end_location:
|
||||
row: 7
|
||||
column: 2
|
||||
fix:
|
||||
patch:
|
||||
content: "(\n 1,\n 2\n)"
|
||||
location:
|
||||
row: 4
|
||||
column: 5
|
||||
end_location:
|
||||
row: 7
|
||||
column: 2
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryLiteralWithinTupleCall: tuple
|
||||
location:
|
||||
row: 8
|
||||
column: 5
|
||||
end_location:
|
||||
row: 10
|
||||
column: 1
|
||||
fix:
|
||||
patch:
|
||||
content: "(1, 2)"
|
||||
location:
|
||||
row: 8
|
||||
column: 5
|
||||
end_location:
|
||||
row: 10
|
||||
column: 1
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -10,7 +10,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 1
|
||||
column: 17
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "[1, 2]"
|
||||
location:
|
||||
row: 1
|
||||
column: 5
|
||||
end_location:
|
||||
row: 1
|
||||
column: 17
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryLiteralWithinListCall: tuple
|
||||
location:
|
||||
@@ -19,7 +28,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 2
|
||||
column: 17
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "[1, 2]"
|
||||
location:
|
||||
row: 2
|
||||
column: 5
|
||||
end_location:
|
||||
row: 2
|
||||
column: 17
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryLiteralWithinListCall: list
|
||||
location:
|
||||
@@ -28,7 +46,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 3
|
||||
column: 13
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "[]"
|
||||
location:
|
||||
row: 3
|
||||
column: 5
|
||||
end_location:
|
||||
row: 3
|
||||
column: 13
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryLiteralWithinListCall: tuple
|
||||
location:
|
||||
@@ -37,5 +64,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 4
|
||||
column: 13
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "[]"
|
||||
location:
|
||||
row: 4
|
||||
column: 5
|
||||
end_location:
|
||||
row: 4
|
||||
column: 13
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -9,5 +9,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 2
|
||||
column: 20
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "[i for i in x]"
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 2
|
||||
column: 20
|
||||
applied: false
|
||||
|
||||
|
||||
41
src/snapshots/ruff__linter__tests__F821_F821_1.py.snap
Normal file
41
src/snapshots/ruff__linter__tests__F821_F821_1.py.snap
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UndefinedName: Model
|
||||
location:
|
||||
row: 11
|
||||
column: 9
|
||||
end_location:
|
||||
row: 11
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: Model
|
||||
location:
|
||||
row: 18
|
||||
column: 16
|
||||
end_location:
|
||||
row: 18
|
||||
column: 23
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: Model
|
||||
location:
|
||||
row: 24
|
||||
column: 12
|
||||
end_location:
|
||||
row: 24
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: Model
|
||||
location:
|
||||
row: 30
|
||||
column: 10
|
||||
end_location:
|
||||
row: 30
|
||||
column: 17
|
||||
fix: ~
|
||||
|
||||
14
src/snapshots/ruff__linter__tests__F821_F821_2.py.snap
Normal file
14
src/snapshots/ruff__linter__tests__F821_F821_2.py.snap
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UndefinedName: Model
|
||||
location:
|
||||
row: 5
|
||||
column: 11
|
||||
end_location:
|
||||
row: 5
|
||||
column: 18
|
||||
fix: ~
|
||||
|
||||
@@ -25,7 +25,8 @@ pub fn compute_offsets(contents: &str) -> Vec<Vec<usize>> {
|
||||
char_index = i + char.len_utf8();
|
||||
}
|
||||
}
|
||||
// If we end in a newline, add an extra character to indicate the start of that line.
|
||||
// If we end in a newline, add an extra character to indicate the start of that
|
||||
// line.
|
||||
if newline {
|
||||
offsets[line_index].push(char_index);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Abstractions for tracking public and private visibility across modules, classes, and functions.
|
||||
//! Abstractions for tracking public and private visibility across modules,
|
||||
//! classes, and functions.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
@@ -133,8 +134,8 @@ fn class_visibility(stmt: &Stmt) -> Visibility {
|
||||
|
||||
/// Transition a `VisibleScope` based on a new `Documentable` definition.
|
||||
///
|
||||
/// `scope` is the current `VisibleScope`, while `Documentable` and `Stmt` describe the current
|
||||
/// node used to modify visibility.
|
||||
/// `scope` is the current `VisibleScope`, while `Documentable` and `Stmt`
|
||||
/// describe the current node used to modify visibility.
|
||||
pub fn transition_scope(scope: &VisibleScope, stmt: &Stmt, kind: &Documentable) -> VisibleScope {
|
||||
match kind {
|
||||
Documentable::Function => VisibleScope {
|
||||
|
||||
@@ -18,7 +18,7 @@ fn test_stdin_error() -> Result<()> {
|
||||
.write_stdin("import os\n")
|
||||
.assert()
|
||||
.failure();
|
||||
assert!(str::from_utf8(&output.get_output().stdout)?.contains("-:1:0: F401"));
|
||||
assert!(str::from_utf8(&output.get_output().stdout)?.contains("-:1:1: F401"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ fn test_stdin_filename() -> Result<()> {
|
||||
.write_stdin("import os\n")
|
||||
.assert()
|
||||
.failure();
|
||||
assert!(str::from_utf8(&output.get_output().stdout)?.contains("F401.py:1:0: F401"));
|
||||
assert!(str::from_utf8(&output.get_output().stdout)?.contains("F401.py:1:1: F401"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user