Compare commits
145 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b335a6a5ec | ||
|
|
f5cbee452a | ||
|
|
5d1ea4410a | ||
|
|
f27163d2c7 | ||
|
|
62c126f70e | ||
|
|
8344d5151c | ||
|
|
f7780eb720 | ||
|
|
c544d84b46 | ||
|
|
fbdc075e5c | ||
|
|
d3472104d0 | ||
|
|
0c8b981216 | ||
|
|
0e3f08aa68 | ||
|
|
a75b1c85ee | ||
|
|
726e6c68cf | ||
|
|
34c91224d7 | ||
|
|
50c4bbc52b | ||
|
|
f0239de559 | ||
|
|
2be632f3cc | ||
|
|
ec3fed5a61 | ||
|
|
1da44485d5 | ||
|
|
e5f30ff5a8 | ||
|
|
c92f5c14a3 | ||
|
|
2f92399f4e | ||
|
|
83dfb5fe8b | ||
|
|
cc915371ca | ||
|
|
5576db3d5a | ||
|
|
f26f38d023 | ||
|
|
578ec4d843 | ||
|
|
2f3bebe5a2 | ||
|
|
22991e3e0e | ||
|
|
242bdf86b1 | ||
|
|
a3f7de2257 | ||
|
|
9f9cbb5520 | ||
|
|
937c83d57f | ||
|
|
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 | ||
|
|
062c41b6f5 | ||
|
|
78889efa37 | ||
|
|
97fc281779 | ||
|
|
138b06c98a | ||
|
|
2415d73260 | ||
|
|
b060ae2f22 | ||
|
|
9aa91d3d3c | ||
|
|
dcedb801e5 | ||
|
|
d3e7fdabb5 | ||
|
|
dfa5a4f0f7 | ||
|
|
58a2d600da | ||
|
|
7ecbfe4f6a | ||
|
|
d8248104a7 | ||
|
|
6eb09122c0 | ||
|
|
f84c1f1fa1 | ||
|
|
3aa9528229 | ||
|
|
5a3f06bab1 | ||
|
|
db59d5b558 | ||
|
|
2fcbf3ab62 | ||
|
|
fa9b10be72 | ||
|
|
c495cef529 | ||
|
|
c0c8dff6ce | ||
|
|
80b00cc89f | ||
|
|
934db3d179 | ||
|
|
6a040a0405 | ||
|
|
2821ef0f69 | ||
|
|
343d931ddb | ||
|
|
3fc257f71b | ||
|
|
6dbb0a17e9 | ||
|
|
ae5ad6a4ac | ||
|
|
549af6c584 | ||
|
|
9a799eb4e6 | ||
|
|
f260b873b6 | ||
|
|
782a90b584 | ||
|
|
7df903dc4d | ||
|
|
8fc5e91ec7 | ||
|
|
9ca1a2c273 | ||
|
|
86265c1d7c | ||
|
|
a057c9a323 | ||
|
|
2e63bb6dcb | ||
|
|
1b5db80b32 | ||
|
|
3f20cea402 | ||
|
|
389fe1ff64 | ||
|
|
bad2d7ba85 | ||
|
|
416aa298ac | ||
|
|
a535b1adbf | ||
|
|
05fbd1a283 | ||
|
|
63552cbc8e | ||
|
|
c00bd489f1 | ||
|
|
16c2e3a995 | ||
|
|
650b025181 | ||
|
|
8fe46f7400 | ||
|
|
a9bcc15797 | ||
|
|
b6c856bd07 | ||
|
|
4beea0484a | ||
|
|
2679db1d10 | ||
|
|
3e73462e04 | ||
|
|
f63a87737a | ||
|
|
e7472eac1c | ||
|
|
db3c847771 | ||
|
|
4adbfc24a5 | ||
|
|
8f734a6562 | ||
|
|
bcf7519eb3 | ||
|
|
66089052ee | ||
|
|
d50cc8ff65 | ||
|
|
b75ea94f58 | ||
|
|
2c24e2fd28 | ||
|
|
f8dc208665 | ||
|
|
c72b8e8d1e | ||
|
|
b108c693fa | ||
|
|
aac1912ea7 | ||
|
|
3dcd26aac3 |
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 ./${{ 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 ./${{ 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 ./${{ 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 ./${{ 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 ./${{ 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 ./${{ 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 ./${{ 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 ./${{ 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,8 +1,8 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.40
|
||||
rev: v0.0.102
|
||||
hooks:
|
||||
- id: lint
|
||||
- id: ruff
|
||||
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.10.1
|
||||
|
||||
217
Cargo.lock
generated
217
Cargo.lock
generated
@@ -37,6 +37,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "annotate-snippets"
|
||||
version = "0.6.1"
|
||||
@@ -368,6 +374,12 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
@@ -416,6 +428,45 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"ciborium-ll",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-io"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-ll"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"clap_lex 0.2.4",
|
||||
"indexmap",
|
||||
"textwrap 0.16.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.0.15"
|
||||
@@ -425,7 +476,7 @@ dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"clap_lex",
|
||||
"clap_lex 0.3.0",
|
||||
"once_cell",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
@@ -444,6 +495,15 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.3.0"
|
||||
@@ -466,6 +526,15 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codegen"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff61280aed771c3070e7dcc9e050c66f1eb1e3b96431ba66f9f74641d02fc41d"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
@@ -502,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"
|
||||
@@ -539,6 +614,42 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"atty",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap 3.2.23",
|
||||
"criterion-plot",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.6"
|
||||
@@ -807,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.102-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.15",
|
||||
"configparser",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"ruff",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.24"
|
||||
@@ -1032,6 +1158,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@@ -1562,6 +1694,12 @@ version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.2.3"
|
||||
@@ -1770,6 +1908,34 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "2.3.0"
|
||||
@@ -2043,19 +2209,31 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ropey"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd22239fafefc42138ca5da064f3c17726a80d2379d817a3521240e78dd0064"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"str_indices",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.81"
|
||||
version = "0.0.102"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
"bincode",
|
||||
"cacache",
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap 4.0.15",
|
||||
"clearscreen",
|
||||
"codegen",
|
||||
"colored",
|
||||
"common-path",
|
||||
"criterion",
|
||||
"dirs 4.0.0",
|
||||
"fern",
|
||||
"filetime",
|
||||
@@ -2071,6 +2249,7 @@ dependencies = [
|
||||
"path-absolutize",
|
||||
"rayon",
|
||||
"regex",
|
||||
"ropey",
|
||||
"rustpython-ast",
|
||||
"rustpython-common",
|
||||
"rustpython-parser",
|
||||
@@ -2079,7 +2258,7 @@ dependencies = [
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"test-case",
|
||||
"textwrap",
|
||||
"textwrap 0.15.1",
|
||||
"titlecase",
|
||||
"toml",
|
||||
"update-informer",
|
||||
@@ -2101,7 +2280,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=1b253a12705f84972cd76e8dc1cdaaccb233e5a5#1b253a12705f84972cd76e8dc1cdaaccb233e5a5"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-common",
|
||||
@@ -2111,7 +2290,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-common"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=1b253a12705f84972cd76e8dc1cdaaccb233e5a5#1b253a12705f84972cd76e8dc1cdaaccb233e5a5"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -2134,7 +2313,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=1b253a12705f84972cd76e8dc1cdaaccb233e5a5#1b253a12705f84972cd76e8dc1cdaaccb233e5a5"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -2151,7 +2330,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=1b253a12705f84972cd76e8dc1cdaaccb233e5a5#1b253a12705f84972cd76e8dc1cdaaccb233e5a5"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
@@ -2391,6 +2570,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "str_indices"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d9199fa80c817e074620be84374a520062ebac833f358d74b37060ce4a0f2c0"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.4"
|
||||
@@ -2539,6 +2724,12 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.37"
|
||||
@@ -2579,6 +2770,16 @@ dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
|
||||
20
Cargo.toml
20
Cargo.toml
@@ -1,6 +1,11 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"flake8_to_ruff",
|
||||
]
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.81"
|
||||
version = "0.0.102"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -26,9 +31,10 @@ once_cell = { version = "1.13.1" }
|
||||
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/charliermarsh/RustPython.git", rev = "1b253a12705f84972cd76e8dc1cdaaccb233e5a5" }
|
||||
rustpython-common = { git = "https://github.com/charliermarsh/RustPython.git", rev = "1b253a12705f84972cd76e8dc1cdaaccb233e5a5" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "1b253a12705f84972cd76e8dc1cdaaccb233e5a5" }
|
||||
ropey = { version = "1.5.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
|
||||
serde = { version = "1.0.143", features = ["derive"] }
|
||||
serde_json = { version = "1.0.83" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
@@ -50,6 +56,8 @@ getrandom = { version = "0.2.7", features = ["js"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = { version = "2.0.4" }
|
||||
codegen = { version = "0.2.0" }
|
||||
criterion = { version = "0.4.0" }
|
||||
insta = { version = "1.19.1", features = ["yaml"] }
|
||||
test-case = { version = "2.2.2" }
|
||||
|
||||
@@ -68,3 +76,7 @@ opt-level = 3
|
||||
|
||||
[profile.dev.package.similar]
|
||||
opt-level = 3
|
||||
|
||||
[[bench]]
|
||||
name = "source_code_locator"
|
||||
harness = false
|
||||
|
||||
319
README.md
319
README.md
@@ -38,13 +38,26 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu
|
||||
1. [Installation and Usage](#installation-and-usage)
|
||||
2. [Configuration](#configuration)
|
||||
3. [Supported Rules](#supported-rules)
|
||||
4. [Editor Integrations](#editor-integrations)
|
||||
5. [FAQ](#faq)
|
||||
6. [Development](#development)
|
||||
7. [Releases](#releases)
|
||||
8. [Benchmarks](#benchmarks)
|
||||
9. [License](#license)
|
||||
10. [Contributing](#contributing)
|
||||
1. [Pyflakes](#pyflakes)
|
||||
2. [pycodestyle (error)](#pycodestyle-error)
|
||||
3. [pycodestyle (warning)](#pycodestyle-warning)
|
||||
4. [pydocstyle](#pydocstyle)
|
||||
5. [pyupgrade](#pyupgrade)
|
||||
6. [pep8-naming](#pep8-naming)
|
||||
7. [flake8-comprehensions](#flake8-comprehensions)
|
||||
8. [flake8-bugbear](#flake8-bugbear)
|
||||
9. [flake8-builtins](#flake8-builtins)
|
||||
10. [flake8-print](#flake8-print)
|
||||
11. [flake8-quotes](#flake8-quotes)
|
||||
12. [Ruff-specific rules](#ruff-specific-rules)
|
||||
13. [Meta rules](#meta-rules)
|
||||
5. [Editor Integrations](#editor-integrations)
|
||||
6. [FAQ](#faq)
|
||||
7. [Development](#development)
|
||||
8. [Releases](#releases)
|
||||
9. [Benchmarks](#benchmarks)
|
||||
10. [License](#license)
|
||||
11. [Contributing](#contributing)
|
||||
|
||||
## Installation and Usage
|
||||
|
||||
@@ -77,27 +90,84 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.81
|
||||
rev: v0.0.102
|
||||
hooks:
|
||||
- id: lint
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
<!-- TODO(charlie): Remove this message a few versions after v0.0.86. -->
|
||||
_Note: prior to `v0.0.86`, `ruff-pre-commit` used `lint` (rather than `ruff`) as the hook ID._
|
||||
|
||||
## Configuration
|
||||
|
||||
Ruff is configurable both via `pyproject.toml` and the command line.
|
||||
|
||||
For example, you could configure Ruff to only enforce a subset of rules with:
|
||||
Ruff is configurable both via `pyproject.toml` and the command line. If left unspecified, the
|
||||
default configuration is equivalent to:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
select = [
|
||||
"F401",
|
||||
"F403",
|
||||
|
||||
# Enable Flake's "E" and "F" codes by default.
|
||||
select = ["E", "F"]
|
||||
ignore = []
|
||||
|
||||
# Exclude a variety of commonly ignored directories.
|
||||
exclude = [
|
||||
".bzr",
|
||||
".direnv",
|
||||
".eggs",
|
||||
".git",
|
||||
".hg",
|
||||
".mypy_cache",
|
||||
".nox",
|
||||
".pants.d",
|
||||
".ruff_cache",
|
||||
".svn",
|
||||
".tox",
|
||||
".venv",
|
||||
"__pypackages__",
|
||||
"_build",
|
||||
"buck-out",
|
||||
"build",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"venv",
|
||||
]
|
||||
per-file-ignores = {}
|
||||
|
||||
# Allow unused variables when underscore-prefixed.
|
||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
# Assume Python 3.10.
|
||||
target-version = "py310"
|
||||
```
|
||||
|
||||
Alternatively, on the command-line:
|
||||
As an example, the following would configure Ruff to (1) avoid checking for line-length
|
||||
violations (`E501`) and (2) ignore unused import rules in `__init__.py` files:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
select = ["E", "F"]
|
||||
|
||||
# Never enforce `E501`.
|
||||
ignore = ["E501"]
|
||||
|
||||
# Ignore `F401` violations in any `__init__.py` file, and in `path/to/file.py`.
|
||||
per-file-ignores = {"__init__.py" = ["F401"], "path/to/file.py" = ["F401"]}
|
||||
```
|
||||
|
||||
Plugin configurations should be expressed as subsections, e.g.:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Add "Q" to the list of enabled codes.
|
||||
select = ["E", "F", "Q"]
|
||||
|
||||
[tool.ruff.flake8-quotes]
|
||||
docstring-quotes = "double"
|
||||
```
|
||||
|
||||
Alternatively, common configuration settings can be provided via the command-line:
|
||||
|
||||
```shell
|
||||
ruff path/to/code/ --select F401 --select F403
|
||||
@@ -119,6 +189,8 @@ Options:
|
||||
-v, --verbose
|
||||
Enable verbose logging
|
||||
-q, --quiet
|
||||
Only log errors
|
||||
-s, --silent
|
||||
Disable all logging (but still exit with status code "1" upon detecting errors)
|
||||
-e, --exit-zero
|
||||
Exit with status code "0", even upon detecting errors
|
||||
@@ -219,8 +291,7 @@ add `noqa` directives to all failing lines, with the appropriate error codes.**
|
||||
|
||||
## Supported Rules
|
||||
|
||||
By default, Ruff enables all `E`, `W`, and `F` error codes, which correspond to those built-in to
|
||||
Flake8.
|
||||
By default, Ruff enables all `E` and `F` error codes, which correspond to those built-in to Flake8.
|
||||
|
||||
The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` command-line option.
|
||||
|
||||
@@ -246,7 +317,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| F634 | IfTuple | If test is a tuple, which is always `True` | |
|
||||
| F701 | BreakOutsideLoop | `break` outside loop | |
|
||||
| F702 | ContinueOutsideLoop | `continue` not properly in loop | |
|
||||
| F704 | YieldOutsideFunction | `yield` or `yield from` statement outside of a function/method | |
|
||||
| F704 | YieldOutsideFunction | `yield` or `yield from` statement outside of a function | |
|
||||
| F706 | ReturnOutsideFunction | `return` statement outside of a function/method | |
|
||||
| F707 | DefaultExceptNotLast | An `except:` block as not the last exception handler | |
|
||||
| F722 | ForwardAnnotationSyntaxError | Syntax error in forward annotation: `...` | |
|
||||
@@ -255,9 +326,9 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| F823 | UndefinedLocal | Local variable `...` referenced before assignment | |
|
||||
| F831 | DuplicateArgumentName | Duplicate argument name in function definition | |
|
||||
| F841 | UnusedVariable | Local variable `...` is assigned to but never used | |
|
||||
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | |
|
||||
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | 🛠 |
|
||||
|
||||
### pycodestyle
|
||||
### pycodestyle (error)
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
@@ -275,7 +346,13 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| E743 | AmbiguousFunctionName | Ambiguous function name: `...` | |
|
||||
| E902 | IOError | IOError: `...` | |
|
||||
| E999 | SyntaxError | SyntaxError: `...` | |
|
||||
|
||||
### pycodestyle (warning)
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| W292 | NoNewLineAtEndOfFile | No newline at end of file | |
|
||||
| W605 | InvalidEscapeSequence | Invalid escape sequence: '\c' | |
|
||||
|
||||
### pydocstyle
|
||||
|
||||
@@ -307,13 +384,13 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| D215 | SectionUnderlineNotOverIndented | Section underline is over-indented ("Returns") | 🛠 |
|
||||
| D300 | UsesTripleQuotes | Use """triple double quotes""" | |
|
||||
| D400 | EndsInPeriod | First line should end with a period | |
|
||||
| D402 | NoSignature | First line should not be the function's 'signature' | |
|
||||
| D402 | NoSignature | First line should not be the function's signature | |
|
||||
| D403 | FirstLineCapitalized | First word of the first line should be properly capitalized | |
|
||||
| D404 | NoThisPrefix | First word of the docstring should not be `This` | |
|
||||
| D404 | NoThisPrefix | First word of the docstring should not be 'This' | |
|
||||
| D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") | 🛠 |
|
||||
| D406 | NewLineAfterSectionName | Section name should end with a newline ("Returns") | 🛠 |
|
||||
| D407 | DashedUnderlineAfterSection | Missing dashed underline after section ("Returns") | 🛠 |
|
||||
| D408 | SectionUnderlineAfterName | Section underline should be in the line following the section's name ("Returns") | |
|
||||
| D408 | SectionUnderlineAfterName | Section underline should be in the line following the section's name ("Returns") | 🛠 |
|
||||
| D409 | SectionUnderlineMatchesSectionLength | Section underline should match the length of its name ("Returns") | 🛠 |
|
||||
| D410 | BlankLineAfterSection | Missing blank line after section ("Returns") | 🛠 |
|
||||
| D411 | BlankLineBeforeSection | Missing blank line before section ("Returns") | 🛠 |
|
||||
@@ -323,7 +400,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | |
|
||||
| D416 | SectionNameEndsInColon | Section name should end with a colon ("Returns") | 🛠 |
|
||||
| D417 | DocumentAllArguments | Missing argument descriptions in the docstring: `x`, `y` | |
|
||||
| D418 | SkipDocstring | Function decorated with @overload shouldn't contain a docstring | |
|
||||
| D418 | SkipDocstring | Function decorated with `@overload` shouldn't contain a docstring | |
|
||||
| D419 | NonEmpty | Docstring is empty | |
|
||||
|
||||
### pyupgrade
|
||||
@@ -348,34 +425,54 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| N803 | InvalidArgumentName | Argument name `...` should be lowercase | |
|
||||
| N804 | InvalidFirstArgumentNameForClassMethod | First argument of a class method should be named `cls` | |
|
||||
| N805 | InvalidFirstArgumentNameForMethod | First argument of a method should be named `self` | |
|
||||
| N806 | NonLowercaseVariableInFunction | Variable `...` in function should be lowercase | |
|
||||
| N807 | DunderFunctionName | Function name should not start and end with `__` | |
|
||||
| N811 | ConstantImportedAsNonConstant | Constant `...` imported as non-constant `...` | |
|
||||
| N812 | LowercaseImportedAsNonLowercase | Lowercase `...` imported as non-lowercase `...` | |
|
||||
| N813 | CamelcaseImportedAsLowercase | Camelcase `...` imported as lowercase `...` | |
|
||||
| N814 | CamelcaseImportedAsConstant | Camelcase `...` imported as constant `...` | |
|
||||
| N815 | MixedCaseVariableInClassScope | Variable `mixedCase` in class scope should not be mixedCase | |
|
||||
| N816 | MixedCaseVariableInGlobalScope | Variable `mixedCase` in global scope should not be mixedCase | |
|
||||
| N817 | CamelcaseImportedAsAcronym | Camelcase `...` imported as acronym `...` | |
|
||||
| N818 | ErrorSuffixOnExceptionName | Exception name `...` should be named with an Error suffix | |
|
||||
|
||||
### flake8-comprehensions
|
||||
|
||||
| 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) | |
|
||||
| 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()`) | |
|
||||
| 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()`) | 🛠 |
|
||||
| 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)()` | |
|
||||
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | |
|
||||
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | 🛠 |
|
||||
| C417 | UnnecessaryMap | Unnecessary `map` usage (rewrite using a `(list\|set\|dict)` comprehension) | |
|
||||
|
||||
### flake8-bugbear
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| B002 | UnaryPrefixIncrement | Python does not support the unary prefix increment. | |
|
||||
| B003 | AssignmentToOsEnviron | Assigning to `os.environ` doesn't clear the environment. | |
|
||||
| B006 | MutableArgumentDefault | Do not use mutable data structures for argument defaults. | |
|
||||
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body. | 🛠 |
|
||||
| B008 | FunctionCallArgumentDefault | Do not perform function calls in argument defaults. | |
|
||||
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
|
||||
| B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError:` instead of `except (ValueError,):`. | |
|
||||
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
|
||||
| B015 | UselessComparison | Pointless comparison. This comparison does nothing but waste CPU instructions. Either prepend `assert` or remove it. | |
|
||||
| B016 | CannotRaiseLiteral | Cannot raise a literal. Did you intend to return it or raise an Exception? | |
|
||||
| B017 | NoAssertRaisesException | `assertRaises(Exception):` should be considered evil. | |
|
||||
| B018 | UselessExpression | Found useless expression. Either assign it to a variable or remove it. | |
|
||||
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | |
|
||||
|
||||
### flake8-builtins
|
||||
@@ -393,6 +490,22 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| T201 | PrintFound | `print` found | 🛠 |
|
||||
| T203 | PPrintFound | `pprint` found | 🛠 |
|
||||
|
||||
### flake8-quotes
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| Q000 | BadQuotesInlineString | Single quotes found but double quotes preferred | |
|
||||
| Q001 | BadQuotesMultilineString | Single quote multiline found but double quotes preferred | |
|
||||
| Q002 | BadQuotesDocstring | Single quote docstring found but double quotes preferred | |
|
||||
| Q003 | AvoidQuoteEscape | Change outer quotes to avoid escaping inner quotes | |
|
||||
|
||||
### Ruff-specific rules
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| RUF001 | AmbiguousUnicodeCharacterString | String contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
|
||||
| RUF002 | AmbiguousUnicodeCharacterDocstring | Docstring contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
|
||||
|
||||
### Meta rules
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
@@ -459,14 +572,16 @@ Ruff re-implements some of the most popular Flake8 plugins and related code qual
|
||||
including:
|
||||
|
||||
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
|
||||
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
|
||||
- [`yesqa`](https://github.com/asottile/yesqa)
|
||||
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
|
||||
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
|
||||
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (15/32)
|
||||
- [`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:
|
||||
@@ -480,15 +595,17 @@ Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis F
|
||||
|
||||
Today, Ruff can be used to replace Flake8 when used with any of the following plugins:
|
||||
|
||||
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
|
||||
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
|
||||
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
|
||||
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (15/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.
|
||||
|
||||
@@ -506,7 +623,7 @@ on Rust at all.
|
||||
### Can I write my own plugins for Ruff?
|
||||
|
||||
Ruff does not yet support third-party plugins, though a plugin system is within-scope for the
|
||||
project. See [#283](https://github.com/charliermarsh/ruff/issues/2830) for more.
|
||||
project. See [#283](https://github.com/charliermarsh/ruff/issues/283) for more.
|
||||
|
||||
### Does Ruff support NumPy- or Google-style docstrings?
|
||||
|
||||
@@ -514,35 +631,55 @@ Yes! To enable a specific docstring convention, start by enabling all `pydocstyl
|
||||
then selectively disabling based on your [preferred convention](https://www.pydocstyle.org/en/latest/error_codes.html#default-conventions).
|
||||
|
||||
For example, if you're coming from `flake8-docstrings`, the following configuration is equivalent to
|
||||
`--docstring-convention numpy`:
|
||||
`--docstring-convention=numpy`:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
extend-select = [
|
||||
"D100",
|
||||
"D101",
|
||||
"D102",
|
||||
"D103",
|
||||
"D104",
|
||||
"D105",
|
||||
"D106",
|
||||
"D200",
|
||||
"D201",
|
||||
"D202",
|
||||
extend-select = ["D"]
|
||||
extend-ignore = [
|
||||
"D107",
|
||||
"D203",
|
||||
"D212",
|
||||
"D213",
|
||||
"D402",
|
||||
"D413",
|
||||
"D415",
|
||||
"D416",
|
||||
"D417",
|
||||
]
|
||||
```
|
||||
|
||||
Similarly, the following is equivalent to `--docstring-convention=google`:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
extend-select = ["D"]
|
||||
extend-ignore = [
|
||||
"D203",
|
||||
"D204",
|
||||
"D205",
|
||||
"D206",
|
||||
"D207",
|
||||
"D208",
|
||||
"D209",
|
||||
"D210",
|
||||
"D211",
|
||||
"D213",
|
||||
"D215",
|
||||
"D400",
|
||||
"D404",
|
||||
"D406",
|
||||
"D407",
|
||||
"D408",
|
||||
"D409",
|
||||
"D413",
|
||||
]
|
||||
```
|
||||
|
||||
Similarly, the following is equivalent to `--docstring-convention=pep8`:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
extend-select = ["D"]
|
||||
extend-ignore = [
|
||||
"D203",
|
||||
"D212",
|
||||
"D213",
|
||||
"D214",
|
||||
"D215",
|
||||
"D300",
|
||||
"D400",
|
||||
"D402",
|
||||
"D403",
|
||||
"D404",
|
||||
"D405",
|
||||
"D406",
|
||||
@@ -551,66 +688,30 @@ extend-select = [
|
||||
"D409",
|
||||
"D410",
|
||||
"D411",
|
||||
"D412",
|
||||
"D413",
|
||||
"D418",
|
||||
"D419",
|
||||
]
|
||||
```
|
||||
|
||||
Similarly, the following is equivalent to `--docstring-convention google`:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
extend-select = [
|
||||
"D100",
|
||||
"D101",
|
||||
"D102",
|
||||
"D103",
|
||||
"D104",
|
||||
"D105",
|
||||
"D106",
|
||||
"D107",
|
||||
"D200",
|
||||
"D201",
|
||||
"D202",
|
||||
"D205",
|
||||
"D206",
|
||||
"D207",
|
||||
"D208",
|
||||
"D209",
|
||||
"D210",
|
||||
"D211",
|
||||
"D212",
|
||||
"D214",
|
||||
"D300",
|
||||
"D402",
|
||||
"D403",
|
||||
"D405",
|
||||
"D410",
|
||||
"D411",
|
||||
"D412",
|
||||
"D414",
|
||||
"D415",
|
||||
"D416",
|
||||
"D417",
|
||||
"D418",
|
||||
"D419",
|
||||
]
|
||||
```
|
||||
|
||||
## 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.65.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
|
||||
|
||||
18
benches/source_code_locator.rs
Normal file
18
benches/source_code_locator.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use ropey::Rope;
|
||||
use ruff::fs;
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let contents = fs::read_file(Path::new("resources/test/fixtures/D.py")).unwrap();
|
||||
c.bench_function("rope", |b| {
|
||||
b.iter(|| {
|
||||
let rope = Rope::from_str(black_box(&contents));
|
||||
rope.line_to_char(black_box(4));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
111
examples/generate_check_code_prefix.rs
Normal file
111
examples/generate_check_code_prefix.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
//! Generate the CheckCodePrefix enum.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use codegen::{Scope, Type, Variant};
|
||||
use itertools::Itertools;
|
||||
use ruff::checks::CheckCode;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
fn main() {
|
||||
// Build up a map from prefix to matching CheckCodes.
|
||||
let mut prefix_to_codes: BTreeMap<String, BTreeSet<CheckCode>> = Default::default();
|
||||
for check_code in CheckCode::iter() {
|
||||
let as_ref: String = check_code.as_ref().to_string();
|
||||
let prefix_len = as_ref
|
||||
.chars()
|
||||
.take_while(|char| char.is_alphabetic())
|
||||
.count();
|
||||
for i in prefix_len..=as_ref.len() {
|
||||
let prefix = as_ref[..i].to_string();
|
||||
let entry = prefix_to_codes
|
||||
.entry(prefix)
|
||||
.or_insert_with(|| Default::default());
|
||||
entry.insert(check_code.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Create the `CheckCodePrefix` definition.
|
||||
let mut gen = scope
|
||||
.new_enum("CheckCodePrefix")
|
||||
.vis("pub")
|
||||
.derive("EnumString")
|
||||
.derive("Debug")
|
||||
.derive("PartialEq")
|
||||
.derive("Eq")
|
||||
.derive("PartialOrd")
|
||||
.derive("Ord")
|
||||
.derive("Clone")
|
||||
.derive("Serialize")
|
||||
.derive("Deserialize");
|
||||
for (prefix, _) in &prefix_to_codes {
|
||||
gen = gen.push_variant(Variant::new(prefix.to_string()));
|
||||
}
|
||||
|
||||
// Create the `PrefixSpecificity` definition.
|
||||
scope
|
||||
.new_enum("PrefixSpecificity")
|
||||
.vis("pub")
|
||||
.derive("PartialEq")
|
||||
.derive("Eq")
|
||||
.derive("PartialOrd")
|
||||
.derive("Ord")
|
||||
.push_variant(Variant::new("Category"))
|
||||
.push_variant(Variant::new("Hundreds"))
|
||||
.push_variant(Variant::new("Tens"))
|
||||
.push_variant(Variant::new("Explicit"));
|
||||
|
||||
// Create the `match` statement, to map from definition to relevant codes.
|
||||
let mut gen = scope
|
||||
.new_impl("CheckCodePrefix")
|
||||
.new_fn("codes")
|
||||
.arg_ref_self()
|
||||
.ret(Type::new("Vec<CheckCode>"))
|
||||
.vis("pub")
|
||||
.line("match self {");
|
||||
for (prefix, codes) in &prefix_to_codes {
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => vec![{}],",
|
||||
codes
|
||||
.iter()
|
||||
.map(|code| format!("CheckCode::{}", code.as_ref()))
|
||||
.join(", ")
|
||||
));
|
||||
}
|
||||
gen.line("}");
|
||||
|
||||
// Create the `match` statement, to map from definition to specificity.
|
||||
let mut gen = scope
|
||||
.new_impl("CheckCodePrefix")
|
||||
.new_fn("specificity")
|
||||
.arg_ref_self()
|
||||
.ret(Type::new("PrefixSpecificity"))
|
||||
.vis("pub")
|
||||
.line("match self {");
|
||||
for (prefix, _) in &prefix_to_codes {
|
||||
let num_numeric = prefix.chars().filter(|char| char.is_numeric()).count();
|
||||
let specificity = match num_numeric {
|
||||
3 => "Explicit",
|
||||
2 => "Tens",
|
||||
1 => "Hundreds",
|
||||
0 => "Category",
|
||||
_ => panic!("Invalid prefix: {}", prefix),
|
||||
};
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => PrefixSpecificity::{},",
|
||||
specificity
|
||||
));
|
||||
}
|
||||
gen.line("}");
|
||||
|
||||
println!("//! File automatically generated by examples/generate_check_code_prefix.rs.");
|
||||
println!();
|
||||
println!("use serde::{{Serialize, Deserialize}};");
|
||||
println!("use strum_macros::EnumString;");
|
||||
println!();
|
||||
println!("use crate::checks::CheckCode;");
|
||||
println!();
|
||||
println!("{}", scope.to_string());
|
||||
}
|
||||
@@ -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() {
|
||||
@@ -19,7 +18,7 @@ fn main() {
|
||||
"| {} | {} | {} | {} |",
|
||||
check_kind.code().as_ref(),
|
||||
check_kind.as_ref(),
|
||||
check_kind.body().replace("|", r"\|"),
|
||||
check_kind.summary().replace("|", r"\|"),
|
||||
fix_token
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
2964
flake8_to_ruff/Cargo.lock
generated
Normal file
2964
flake8_to_ruff/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
flake8_to_ruff/Cargo.toml
Normal file
22
flake8_to_ruff/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.102-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]
|
||||
98
flake8_to_ruff/README.md
Normal file
98
flake8_to_ruff/README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# 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 `flake8-to-ruff`:
|
||||
|
||||
```shell
|
||||
flake8-to-ruff path/to/setup.cfg
|
||||
flake8-to-ruff path/to/tox.ini
|
||||
flake8-to-ruff path/to/.flake8
|
||||
```
|
||||
|
||||
`flake8-to-ruff` will print the relevant `pyproject.toml` sections to standard output, like so:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
exclude = [
|
||||
'.svn',
|
||||
'CVS',
|
||||
'.bzr',
|
||||
'.hg',
|
||||
'.git',
|
||||
'__pycache__',
|
||||
'.tox',
|
||||
'.idea',
|
||||
'.mypy_cache',
|
||||
'.venv',
|
||||
'node_modules',
|
||||
'_state_machine.py',
|
||||
'test_fstring.py',
|
||||
'bad_coding2.py',
|
||||
'badsyntax_*.py',
|
||||
]
|
||||
select = [
|
||||
'A',
|
||||
'E',
|
||||
'F',
|
||||
'Q',
|
||||
]
|
||||
ignore = []
|
||||
|
||||
[tool.ruff.flake8-quotes]
|
||||
inline-quotes = 'single'
|
||||
|
||||
[tool.ruff.pep8-naming]
|
||||
ignore-names = [
|
||||
'foo',
|
||||
'bar',
|
||||
]
|
||||
```
|
||||
|
||||
### Plugins
|
||||
|
||||
`flake8-to-ruff` will attempt to infer any activated plugins based on the settings provided in your
|
||||
configuration file.
|
||||
|
||||
For example, if your `.flake8` file includes a `docstring-convention` property, `flake8-to-ruff`
|
||||
will enable the appropriate [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
|
||||
checks.
|
||||
|
||||
Alternatively, you can manually specify plugins on the command-line:
|
||||
|
||||
```shell
|
||||
flake8-to-ruff path/to/.flake8 --plugin flake8-builtins --plugin flake8-quotes
|
||||
```
|
||||
|
||||
## 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.)
|
||||
|
||||
## 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).
|
||||
36
flake8_to_ruff/examples/poetry.ini
Normal file
36
flake8_to_ruff/examples/poetry.ini
Normal file
@@ -0,0 +1,36 @@
|
||||
[flake8]
|
||||
min_python_version = 3.7.0
|
||||
max-line-length = 88
|
||||
ban-relative-imports = true
|
||||
# flake8-use-fstring: https://github.com/MichaelKim0407/flake8-use-fstring#--percent-greedy-and---format-greedy
|
||||
format-greedy = 1
|
||||
inline-quotes = double
|
||||
enable-extensions = TC, TC1
|
||||
type-checking-strict = true
|
||||
eradicate-whitelist-extend = ^-.*;
|
||||
extend-ignore =
|
||||
# E203: Whitespace before ':' (pycqa/pycodestyle#373)
|
||||
E203,
|
||||
# SIM106: Handle error-cases first
|
||||
SIM106,
|
||||
# ANN101: Missing type annotation for self in method
|
||||
ANN101,
|
||||
# ANN102: Missing type annotation for cls in classmethod
|
||||
ANN102,
|
||||
# PIE781: assign-and-return
|
||||
PIE781,
|
||||
# PIE798 no-unnecessary-class: Consider using a module for namespacing instead
|
||||
PIE798,
|
||||
per-file-ignores =
|
||||
# TC002: Move third-party import '...' into a type-checking block
|
||||
__init__.py:TC002,
|
||||
# ANN201: Missing return type annotation for public function
|
||||
tests/test_*:ANN201
|
||||
tests/**/test_*:ANN201
|
||||
extend-exclude =
|
||||
# Frozen and not subject to change in this repo:
|
||||
get-poetry.py,
|
||||
install-poetry.py,
|
||||
# External to the project's coding standards:
|
||||
tests/fixtures/*,
|
||||
tests/**/fixtures/*,
|
||||
19
flake8_to_ruff/examples/python-discord.ini
Normal file
19
flake8_to_ruff/examples/python-discord.ini
Normal file
@@ -0,0 +1,19 @@
|
||||
[flake8]
|
||||
max-line-length=120
|
||||
docstring-convention=all
|
||||
import-order-style=pycharm
|
||||
application_import_names=bot,tests
|
||||
exclude=.cache,.venv,.git,constants.py
|
||||
extend-ignore=
|
||||
B311,W503,E226,S311,T000,E731
|
||||
# Missing Docstrings
|
||||
D100,D104,D105,D107,
|
||||
# Docstring Whitespace
|
||||
D203,D212,D214,D215,
|
||||
# Docstring Quotes
|
||||
D301,D302,
|
||||
# Docstring Content
|
||||
D400,D401,D402,D404,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414,D416,D417
|
||||
# Type Annotations
|
||||
ANN002,ANN003,ANN101,ANN102,ANN204,ANN206,ANN401
|
||||
per-file-ignores=tests/*:D,ANN
|
||||
6
flake8_to_ruff/examples/requests.ini
Normal file
6
flake8_to_ruff/examples/requests.ini
Normal file
@@ -0,0 +1,6 @@
|
||||
[flake8]
|
||||
ignore = E203, E501, W503
|
||||
per-file-ignores =
|
||||
requests/__init__.py:E402, F401
|
||||
requests/compat.py:E402, F401
|
||||
tests/compat.py:F401
|
||||
33
flake8_to_ruff/pyproject.toml
Normal file
33
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
|
||||
399
flake8_to_ruff/src/converter.rs
Normal file
399
flake8_to_ruff/src/converter.rs
Normal file
@@ -0,0 +1,399 @@
|
||||
use std::collections::{BTreeSet, 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::plugin::Plugin;
|
||||
use crate::{parser, plugin};
|
||||
|
||||
pub fn convert(
|
||||
flake8: &HashMap<String, Option<String>>,
|
||||
plugins: Option<Vec<Plugin>>,
|
||||
) -> Result<Pyproject> {
|
||||
// Check if the user has specified a `select`. If not, we'll add our own
|
||||
// default `select`, and populate it based on user plugins.
|
||||
let mut select = flake8
|
||||
.get("select")
|
||||
.and_then(|value| {
|
||||
value
|
||||
.as_ref()
|
||||
.map(|value| BTreeSet::from_iter(parser::parse_prefix_codes(value)))
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
plugin::resolve_select(
|
||||
flake8,
|
||||
&plugins.unwrap_or_else(|| plugin::infer_plugins(flake8)),
|
||||
)
|
||||
});
|
||||
let mut ignore = flake8
|
||||
.get("ignore")
|
||||
.and_then(|value| {
|
||||
value
|
||||
.as_ref()
|
||||
.map(|value| BTreeSet::from_iter(parser::parse_prefix_codes(value)))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
// 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" => {
|
||||
// No-op (handled above).
|
||||
}
|
||||
"ignore" => {
|
||||
// No-op (handled above).
|
||||
}
|
||||
"extend-select" | "extend_select" => {
|
||||
// Unlike Flake8, use a single explicit `select`.
|
||||
select.extend(parser::parse_prefix_codes(value.as_ref()));
|
||||
}
|
||||
"extend-ignore" | "extend_ignore" => {
|
||||
// Unlike Flake8, use a single explicit `ignore`.
|
||||
ignore.extend(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()));
|
||||
}
|
||||
// flake8-docstrings
|
||||
"docstring-convention" => {
|
||||
// No-op (handled above).
|
||||
}
|
||||
// Unknown
|
||||
_ => eprintln!("Skipping unsupported property: {key}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deduplicate and sort.
|
||||
options.select = Some(Vec::from_iter(select));
|
||||
options.ignore = Some(Vec::from_iter(ignore));
|
||||
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::checks_gen::CheckCodePrefix;
|
||||
use ruff::flake8_quotes;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::pyproject::Pyproject;
|
||||
|
||||
use crate::converter::convert;
|
||||
use crate::plugin::Plugin;
|
||||
|
||||
#[test]
|
||||
fn it_converts_empty() -> Result<()> {
|
||||
let actual = convert(&HashMap::from([]), None)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::W,
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: Some(vec![]),
|
||||
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([("max-line-length".to_string(), Some("100".to_string()))]),
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: Some(100),
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::W,
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: Some(vec![]),
|
||||
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([("max_line_length".to_string(), Some("100".to_string()))]),
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: Some(100),
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::W,
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: Some(vec![]),
|
||||
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([("max_line_length".to_string(), Some("abc".to_string()))]),
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::W,
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: Some(vec![]),
|
||||
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_plugin_options() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::W,
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: Some(vec![]),
|
||||
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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_converts_docstring_conventions() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([(
|
||||
"docstring-convention".to_string(),
|
||||
Some("numpy".to_string()),
|
||||
)]),
|
||||
Some(vec![Plugin::Flake8Docstrings]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::D100,
|
||||
CheckCodePrefix::D101,
|
||||
CheckCodePrefix::D102,
|
||||
CheckCodePrefix::D103,
|
||||
CheckCodePrefix::D104,
|
||||
CheckCodePrefix::D105,
|
||||
CheckCodePrefix::D106,
|
||||
CheckCodePrefix::D200,
|
||||
CheckCodePrefix::D201,
|
||||
CheckCodePrefix::D202,
|
||||
CheckCodePrefix::D204,
|
||||
CheckCodePrefix::D205,
|
||||
CheckCodePrefix::D206,
|
||||
CheckCodePrefix::D207,
|
||||
CheckCodePrefix::D208,
|
||||
CheckCodePrefix::D209,
|
||||
CheckCodePrefix::D210,
|
||||
CheckCodePrefix::D211,
|
||||
CheckCodePrefix::D214,
|
||||
CheckCodePrefix::D215,
|
||||
CheckCodePrefix::D300,
|
||||
CheckCodePrefix::D400,
|
||||
CheckCodePrefix::D403,
|
||||
CheckCodePrefix::D404,
|
||||
CheckCodePrefix::D405,
|
||||
CheckCodePrefix::D406,
|
||||
CheckCodePrefix::D407,
|
||||
CheckCodePrefix::D408,
|
||||
CheckCodePrefix::D409,
|
||||
CheckCodePrefix::D410,
|
||||
CheckCodePrefix::D411,
|
||||
CheckCodePrefix::D412,
|
||||
CheckCodePrefix::D414,
|
||||
CheckCodePrefix::D418,
|
||||
CheckCodePrefix::D419,
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::W,
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: Some(vec![]),
|
||||
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_infers_plugins_if_omitted() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||
None,
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::Q,
|
||||
CheckCodePrefix::W,
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: Some(vec![]),
|
||||
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(())
|
||||
}
|
||||
}
|
||||
5
flake8_to_ruff/src/lib.rs
Normal file
5
flake8_to_ruff/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#![allow(clippy::collapsible_if, clippy::collapsible_else_if)]
|
||||
|
||||
pub mod converter;
|
||||
mod parser;
|
||||
pub mod plugin;
|
||||
44
flake8_to_ruff/src/main.rs
Normal file
44
flake8_to_ruff/src/main.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
//! 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;
|
||||
use flake8_to_ruff::plugin::Plugin;
|
||||
|
||||
#[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,
|
||||
/// List of plugins to enable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
plugin: Option<Vec<Plugin>>,
|
||||
}
|
||||
|
||||
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))?;
|
||||
|
||||
// Extract the Flake8 section.
|
||||
let flake8 = config
|
||||
.get("flake8")
|
||||
.expect("Unable to find flake8 section in INI file.");
|
||||
|
||||
// Create the pyproject.toml.
|
||||
let pyproject = converter::convert(flake8, cli.plugin)?;
|
||||
println!("{}", toml::to_string_pretty(&pyproject)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
373
flake8_to_ruff/src/parser.rs
Normal file
373
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(())
|
||||
}
|
||||
}
|
||||
321
flake8_to_ruff/src/plugin.rs
Normal file
321
flake8_to_ruff/src/plugin.rs
Normal file
@@ -0,0 +1,321 @@
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub enum Plugin {
|
||||
Flake8Bugbear,
|
||||
Flake8Builtins,
|
||||
Flake8Comprehensions,
|
||||
Flake8Docstrings,
|
||||
Flake8Print,
|
||||
Flake8Quotes,
|
||||
PEP8Naming,
|
||||
Pyupgrade,
|
||||
}
|
||||
|
||||
impl FromStr for Plugin {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
match string {
|
||||
"flake8-bugbear" => Ok(Plugin::Flake8Bugbear),
|
||||
"flake8-builtins" => Ok(Plugin::Flake8Builtins),
|
||||
"flake8-comprehensions" => Ok(Plugin::Flake8Comprehensions),
|
||||
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
|
||||
"flake8-print" => Ok(Plugin::Flake8Print),
|
||||
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
|
||||
"pep8-naming" => Ok(Plugin::PEP8Naming),
|
||||
"pyupgrade" => Ok(Plugin::Pyupgrade),
|
||||
_ => Err(anyhow!("Unknown plugin: {}", string)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin {
|
||||
pub fn select(&self, flake8: &HashMap<String, Option<String>>) -> Vec<CheckCodePrefix> {
|
||||
match self {
|
||||
Plugin::Flake8Bugbear => vec![CheckCodePrefix::B],
|
||||
Plugin::Flake8Builtins => vec![CheckCodePrefix::A],
|
||||
Plugin::Flake8Comprehensions => vec![CheckCodePrefix::C],
|
||||
Plugin::Flake8Docstrings => {
|
||||
// Use the user-provided docstring.
|
||||
for key in ["docstring-convention", "docstring_convention"] {
|
||||
if let Some(Some(value)) = flake8.get(key) {
|
||||
match DocstringConvention::from_str(value) {
|
||||
Ok(convention) => return convention.select(),
|
||||
Err(e) => {
|
||||
eprintln!("Unexpected '{key}' value: {value} ({e}");
|
||||
return vec![];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Default to PEP8.
|
||||
DocstringConvention::PEP8.select()
|
||||
}
|
||||
Plugin::Flake8Print => vec![CheckCodePrefix::T],
|
||||
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
|
||||
Plugin::PEP8Naming => vec![CheckCodePrefix::N],
|
||||
Plugin::Pyupgrade => vec![CheckCodePrefix::U],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DocstringConvention {
|
||||
All,
|
||||
PEP8,
|
||||
NumPy,
|
||||
Google,
|
||||
}
|
||||
|
||||
impl FromStr for DocstringConvention {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
match string {
|
||||
"all" => Ok(DocstringConvention::All),
|
||||
"pep8" => Ok(DocstringConvention::PEP8),
|
||||
"numpy" => Ok(DocstringConvention::NumPy),
|
||||
"google" => Ok(DocstringConvention::Google),
|
||||
_ => Err(anyhow!("Unknown docstring convention: {}", string)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DocstringConvention {
|
||||
fn select(&self) -> Vec<CheckCodePrefix> {
|
||||
match self {
|
||||
DocstringConvention::All => vec![CheckCodePrefix::D],
|
||||
DocstringConvention::PEP8 => vec![
|
||||
// All errors except D203, D212, D213, D214, D215, D404, D405, D406, D407, D408,
|
||||
// D409, D410, D411, D413, D415, D416 and D417.
|
||||
CheckCodePrefix::D100,
|
||||
CheckCodePrefix::D101,
|
||||
CheckCodePrefix::D102,
|
||||
CheckCodePrefix::D103,
|
||||
CheckCodePrefix::D104,
|
||||
CheckCodePrefix::D105,
|
||||
CheckCodePrefix::D106,
|
||||
CheckCodePrefix::D107,
|
||||
CheckCodePrefix::D200,
|
||||
CheckCodePrefix::D201,
|
||||
CheckCodePrefix::D202,
|
||||
// CheckCodePrefix::D203,
|
||||
CheckCodePrefix::D204,
|
||||
CheckCodePrefix::D205,
|
||||
CheckCodePrefix::D206,
|
||||
CheckCodePrefix::D207,
|
||||
CheckCodePrefix::D208,
|
||||
CheckCodePrefix::D209,
|
||||
CheckCodePrefix::D210,
|
||||
CheckCodePrefix::D211,
|
||||
// CheckCodePrefix::D212,
|
||||
// CheckCodePrefix::D213,
|
||||
// CheckCodePrefix::D214,
|
||||
// CheckCodePrefix::D215,
|
||||
CheckCodePrefix::D300,
|
||||
CheckCodePrefix::D400,
|
||||
CheckCodePrefix::D402,
|
||||
CheckCodePrefix::D403,
|
||||
// CheckCodePrefix::D404,
|
||||
// CheckCodePrefix::D405,
|
||||
// CheckCodePrefix::D406,
|
||||
// CheckCodePrefix::D407,
|
||||
// CheckCodePrefix::D408,
|
||||
// CheckCodePrefix::D409,
|
||||
// CheckCodePrefix::D410,
|
||||
// CheckCodePrefix::D411,
|
||||
CheckCodePrefix::D412,
|
||||
// CheckCodePrefix::D413,
|
||||
CheckCodePrefix::D414,
|
||||
// CheckCodePrefix::D415,
|
||||
// CheckCodePrefix::D416,
|
||||
// CheckCodePrefix::D417,
|
||||
CheckCodePrefix::D418,
|
||||
CheckCodePrefix::D419,
|
||||
],
|
||||
DocstringConvention::NumPy => vec![
|
||||
// All errors except D107, D203, D212, D213, D402, D413, D415, D416, and D417.
|
||||
CheckCodePrefix::D100,
|
||||
CheckCodePrefix::D101,
|
||||
CheckCodePrefix::D102,
|
||||
CheckCodePrefix::D103,
|
||||
CheckCodePrefix::D104,
|
||||
CheckCodePrefix::D105,
|
||||
CheckCodePrefix::D106,
|
||||
// CheckCodePrefix::D107,
|
||||
CheckCodePrefix::D200,
|
||||
CheckCodePrefix::D201,
|
||||
CheckCodePrefix::D202,
|
||||
// CheckCodePrefix::D203,
|
||||
CheckCodePrefix::D204,
|
||||
CheckCodePrefix::D205,
|
||||
CheckCodePrefix::D206,
|
||||
CheckCodePrefix::D207,
|
||||
CheckCodePrefix::D208,
|
||||
CheckCodePrefix::D209,
|
||||
CheckCodePrefix::D210,
|
||||
CheckCodePrefix::D211,
|
||||
// CheckCodePrefix::D212,
|
||||
// CheckCodePrefix::D213,
|
||||
CheckCodePrefix::D214,
|
||||
CheckCodePrefix::D215,
|
||||
CheckCodePrefix::D300,
|
||||
CheckCodePrefix::D400,
|
||||
// CheckCodePrefix::D402,
|
||||
CheckCodePrefix::D403,
|
||||
CheckCodePrefix::D404,
|
||||
CheckCodePrefix::D405,
|
||||
CheckCodePrefix::D406,
|
||||
CheckCodePrefix::D407,
|
||||
CheckCodePrefix::D408,
|
||||
CheckCodePrefix::D409,
|
||||
CheckCodePrefix::D410,
|
||||
CheckCodePrefix::D411,
|
||||
CheckCodePrefix::D412,
|
||||
// CheckCodePrefix::D413,
|
||||
CheckCodePrefix::D414,
|
||||
// CheckCodePrefix::D415,
|
||||
// CheckCodePrefix::D416,
|
||||
// CheckCodePrefix::D417,
|
||||
CheckCodePrefix::D418,
|
||||
CheckCodePrefix::D419,
|
||||
],
|
||||
DocstringConvention::Google => vec![
|
||||
// All errors except D203, D204, D213, D215, D400, D401, D404, D406, D407, D408,
|
||||
// D409 and D413.
|
||||
CheckCodePrefix::D100,
|
||||
CheckCodePrefix::D101,
|
||||
CheckCodePrefix::D102,
|
||||
CheckCodePrefix::D103,
|
||||
CheckCodePrefix::D104,
|
||||
CheckCodePrefix::D105,
|
||||
CheckCodePrefix::D106,
|
||||
CheckCodePrefix::D107,
|
||||
CheckCodePrefix::D200,
|
||||
CheckCodePrefix::D201,
|
||||
CheckCodePrefix::D202,
|
||||
// CheckCodePrefix::D203,
|
||||
// CheckCodePrefix::D204,
|
||||
CheckCodePrefix::D205,
|
||||
CheckCodePrefix::D206,
|
||||
CheckCodePrefix::D207,
|
||||
CheckCodePrefix::D208,
|
||||
CheckCodePrefix::D209,
|
||||
CheckCodePrefix::D210,
|
||||
CheckCodePrefix::D211,
|
||||
CheckCodePrefix::D212,
|
||||
// CheckCodePrefix::D213,
|
||||
CheckCodePrefix::D214,
|
||||
// CheckCodePrefix::D215,
|
||||
CheckCodePrefix::D300,
|
||||
// CheckCodePrefix::D400,
|
||||
CheckCodePrefix::D402,
|
||||
CheckCodePrefix::D403,
|
||||
// CheckCodePrefix::D404,
|
||||
CheckCodePrefix::D405,
|
||||
// CheckCodePrefix::D406,
|
||||
// CheckCodePrefix::D407,
|
||||
// CheckCodePrefix::D408,
|
||||
// CheckCodePrefix::D409,
|
||||
CheckCodePrefix::D410,
|
||||
CheckCodePrefix::D411,
|
||||
CheckCodePrefix::D412,
|
||||
// CheckCodePrefix::D413,
|
||||
CheckCodePrefix::D414,
|
||||
CheckCodePrefix::D415,
|
||||
CheckCodePrefix::D416,
|
||||
CheckCodePrefix::D417,
|
||||
CheckCodePrefix::D418,
|
||||
CheckCodePrefix::D419,
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Infer the enabled plugins based on user-provided settings.
|
||||
pub fn infer_plugins(flake8: &HashMap<String, Option<String>>) -> Vec<Plugin> {
|
||||
let mut plugins = BTreeSet::new();
|
||||
for key in flake8.keys() {
|
||||
match key.as_str() {
|
||||
// flake8-docstrings
|
||||
"docstring-convention" | "docstring_convention" => {
|
||||
plugins.insert(Plugin::Flake8Docstrings);
|
||||
}
|
||||
// flake8-builtins
|
||||
"builtins-ignorelist" | "builtins_ignorelist" => {
|
||||
plugins.insert(Plugin::Flake8Builtins);
|
||||
}
|
||||
// flake8-quotes
|
||||
"quotes" | "inline-quotes" | "inline_quotes" => {
|
||||
plugins.insert(Plugin::Flake8Quotes);
|
||||
}
|
||||
"multiline-quotes" | "multiline_quotes" => {
|
||||
plugins.insert(Plugin::Flake8Quotes);
|
||||
}
|
||||
"docstring-quotes" | "docstring_quotes" => {
|
||||
plugins.insert(Plugin::Flake8Quotes);
|
||||
}
|
||||
"avoid-escape" | "avoid_escape" => {
|
||||
plugins.insert(Plugin::Flake8Quotes);
|
||||
}
|
||||
// pep8-naming
|
||||
"ignore-names" | "ignore_names" => {
|
||||
plugins.insert(Plugin::PEP8Naming);
|
||||
}
|
||||
"classmethod-decorators" | "classmethod_decorators" => {
|
||||
plugins.insert(Plugin::PEP8Naming);
|
||||
}
|
||||
"staticmethod-decorators" | "staticmethod_decorators" => {
|
||||
plugins.insert(Plugin::PEP8Naming);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Vec::from_iter(plugins)
|
||||
}
|
||||
|
||||
/// Resolve the set of enabled `CheckCodePrefix` values for the given plugins.
|
||||
pub fn resolve_select(
|
||||
flake8: &HashMap<String, Option<String>>,
|
||||
plugins: &[Plugin],
|
||||
) -> BTreeSet<CheckCodePrefix> {
|
||||
// Include default Pyflakes and pycodestyle checks.
|
||||
let mut select = BTreeSet::from([CheckCodePrefix::E, CheckCodePrefix::F, CheckCodePrefix::W]);
|
||||
|
||||
// Add prefix codes for every plugin.
|
||||
for plugin in plugins {
|
||||
for prefix in plugin.select(flake8) {
|
||||
select.insert(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
select
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::plugin::{infer_plugins, Plugin};
|
||||
|
||||
#[test]
|
||||
fn it_infers_plugins() {
|
||||
let actual = infer_plugins(&HashMap::from([(
|
||||
"inline-quotes".to_string(),
|
||||
Some("single".to_string()),
|
||||
)]));
|
||||
let expected = vec![Plugin::Flake8Quotes];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = infer_plugins(&HashMap::from([(
|
||||
"staticmethod-decorators".to_string(),
|
||||
Some("[]".to_string()),
|
||||
)]));
|
||||
let expected = vec![Plugin::PEP8Naming];
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
20
resources/test/fixtures/B002.py
vendored
Normal file
20
resources/test/fixtures/B002.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
Should emit:
|
||||
B002 - on lines 15 and 20
|
||||
"""
|
||||
|
||||
|
||||
def this_is_all_fine(n):
|
||||
x = n + 1
|
||||
y = 1 + n
|
||||
z = +x + y
|
||||
return +z
|
||||
|
||||
|
||||
def this_is_buggy(n):
|
||||
x = ++n
|
||||
return x
|
||||
|
||||
|
||||
def this_is_buggy_too(n):
|
||||
return ++n
|
||||
18
resources/test/fixtures/B003.py
vendored
Normal file
18
resources/test/fixtures/B003.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Should emit:
|
||||
B003 - on line 9
|
||||
"""
|
||||
|
||||
import os
|
||||
from os import environ
|
||||
|
||||
os.environ = {}
|
||||
environ = {} # that's fine, assigning a new meaning to the module-level name
|
||||
|
||||
|
||||
class Object:
|
||||
os = None
|
||||
|
||||
|
||||
o = Object()
|
||||
o.os.environ = {}
|
||||
187
resources/test/fixtures/B006_B008.py
vendored
Normal file
187
resources/test/fixtures/B006_B008.py
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
import collections
|
||||
import datetime as dt
|
||||
import logging
|
||||
import operator
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
import types
|
||||
from operator import attrgetter, itemgetter, methodcaller
|
||||
from types import MappingProxyType
|
||||
|
||||
|
||||
# B006
|
||||
# Allow immutable literals/calls/comprehensions
|
||||
def this_is_okay(value=(1, 2, 3)):
|
||||
...
|
||||
|
||||
|
||||
async def and_this_also(value=tuple()):
|
||||
pass
|
||||
|
||||
|
||||
def frozenset_also_okay(value=frozenset()):
|
||||
pass
|
||||
|
||||
|
||||
def mappingproxytype_okay(
|
||||
value=MappingProxyType({}), value2=types.MappingProxyType({})
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def re_compile_ok(value=re.compile("foo")):
|
||||
pass
|
||||
|
||||
|
||||
def operators_ok(
|
||||
v=operator.attrgetter("foo"),
|
||||
v2=operator.itemgetter("foo"),
|
||||
v3=operator.methodcaller("foo"),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def operators_ok_unqualified(
|
||||
v=attrgetter("foo"),
|
||||
v2=itemgetter("foo"),
|
||||
v3=methodcaller("foo"),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def kwonlyargs_immutable(*, value=()):
|
||||
...
|
||||
|
||||
|
||||
# Flag mutable literals/comprehensions
|
||||
|
||||
|
||||
def this_is_wrong(value=[1, 2, 3]):
|
||||
...
|
||||
|
||||
|
||||
def this_is_also_wrong(value={}):
|
||||
...
|
||||
|
||||
|
||||
def and_this(value=set()):
|
||||
...
|
||||
|
||||
|
||||
def this_too(value=collections.OrderedDict()):
|
||||
...
|
||||
|
||||
|
||||
async def async_this_too(value=collections.defaultdict()):
|
||||
...
|
||||
|
||||
|
||||
def dont_forget_me(value=collections.deque()):
|
||||
...
|
||||
|
||||
|
||||
# N.B. we're also flagging the function call in the comprehension
|
||||
def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
|
||||
pass
|
||||
|
||||
|
||||
def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
|
||||
pass
|
||||
|
||||
|
||||
def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
|
||||
pass
|
||||
|
||||
|
||||
def kwonlyargs_mutable(*, value=[]):
|
||||
...
|
||||
|
||||
|
||||
# Recommended approach for mutable defaults
|
||||
def do_this_instead(value=None):
|
||||
if value is None:
|
||||
value = set()
|
||||
|
||||
|
||||
# B008
|
||||
# Flag function calls as default args (including if they are part of a sub-expression)
|
||||
def in_fact_all_calls_are_wrong(value=time.time()):
|
||||
...
|
||||
|
||||
|
||||
def f(when=dt.datetime.now() + dt.timedelta(days=7)):
|
||||
pass
|
||||
|
||||
|
||||
def can_even_catch_lambdas(a=(lambda x: x)()):
|
||||
...
|
||||
|
||||
|
||||
# Recommended approach for function calls as default args
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def do_this_instead_of_calls_in_defaults(logger=LOGGER):
|
||||
# That makes it more obvious that this one value is reused.
|
||||
...
|
||||
|
||||
|
||||
# Handle inf/infinity/nan special case
|
||||
def float_inf_okay(value=float("inf")):
|
||||
pass
|
||||
|
||||
|
||||
def float_infinity_okay(value=float("infinity")):
|
||||
pass
|
||||
|
||||
|
||||
def float_plus_infinity_okay(value=float("+infinity")):
|
||||
pass
|
||||
|
||||
|
||||
def float_minus_inf_okay(value=float("-inf")):
|
||||
pass
|
||||
|
||||
|
||||
def float_nan_okay(value=float("nan")):
|
||||
pass
|
||||
|
||||
|
||||
def float_minus_NaN_okay(value=float("-NaN")):
|
||||
pass
|
||||
|
||||
|
||||
def float_infinity_literal(value=float("1e999")):
|
||||
pass
|
||||
|
||||
|
||||
# But don't allow standard floats
|
||||
def float_int_is_wrong(value=float(3)):
|
||||
pass
|
||||
|
||||
|
||||
def float_str_not_inf_or_nan_is_wrong(value=float("3.14")):
|
||||
pass
|
||||
|
||||
|
||||
# B006 and B008
|
||||
# We should handle arbitrary nesting of these B008.
|
||||
def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
pass
|
||||
|
||||
|
||||
# Don't flag nested B006 since we can't guarantee that
|
||||
# it isn't made mutable by the outer operation.
|
||||
def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
|
||||
pass
|
||||
|
||||
|
||||
# B008-ception.
|
||||
def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
pass
|
||||
|
||||
|
||||
# Ignore lambda contents since they are evaluated at call time.
|
||||
def foo(f=lambda x: print(x)):
|
||||
f(1)
|
||||
31
resources/test/fixtures/B007.py
vendored
Normal file
31
resources/test/fixtures/B007.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
for i in range(10):
|
||||
print(i)
|
||||
|
||||
print(i) # name no longer defined on Python 3; no warning yet
|
||||
|
||||
for i in range(10): # name not used within the loop; B007
|
||||
print(10)
|
||||
|
||||
print(i) # name no longer defined on Python 3; no warning yet
|
||||
|
||||
|
||||
for _ in range(10): # _ is okay for a throw-away variable
|
||||
print(10)
|
||||
|
||||
|
||||
for i in range(10):
|
||||
for j in range(10):
|
||||
for k in range(10): # k not used, i and j used transitively
|
||||
print(i + j)
|
||||
|
||||
|
||||
def strange_generator():
|
||||
for i in range(10):
|
||||
for j in range(10):
|
||||
for k in range(10):
|
||||
for l in range(10):
|
||||
yield i, (j, (k, l))
|
||||
|
||||
|
||||
for i, (j, (k, l)) in strange_generator(): # i, k not used
|
||||
print(j, l)
|
||||
8
resources/test/fixtures/B013.py
vendored
Normal file
8
resources/test/fixtures/B013.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
try:
|
||||
pass
|
||||
except (ValueError,):
|
||||
pass
|
||||
except AttributeError:
|
||||
pass
|
||||
except (ImportError, TypeError):
|
||||
pass
|
||||
24
resources/test/fixtures/B015.py
vendored
Normal file
24
resources/test/fixtures/B015.py
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
assert 1 == 1
|
||||
|
||||
1 == 1
|
||||
|
||||
assert 1 in (1, 2)
|
||||
|
||||
1 in (1, 2)
|
||||
|
||||
|
||||
if 1 == 2:
|
||||
pass
|
||||
|
||||
|
||||
def test():
|
||||
assert 1 in (1, 2)
|
||||
|
||||
1 in (1, 2)
|
||||
|
||||
|
||||
data = [x for x in [1, 2, 3] if x in (1, 2)]
|
||||
|
||||
|
||||
class TestClass:
|
||||
1 == 1
|
||||
11
resources/test/fixtures/B016.py
vendored
Normal file
11
resources/test/fixtures/B016.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Should emit:
|
||||
B016 - on lines 6, 7, and 8
|
||||
"""
|
||||
|
||||
raise False
|
||||
raise 1
|
||||
raise "string"
|
||||
raise Exception(False)
|
||||
raise Exception(1)
|
||||
raise Exception("string")
|
||||
36
resources/test/fixtures/B017.py
vendored
Normal file
36
resources/test/fixtures/B017.py
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
Should emit:
|
||||
B017 - on lines 20
|
||||
"""
|
||||
import asyncio
|
||||
import unittest
|
||||
|
||||
CONSTANT = True
|
||||
|
||||
|
||||
def something_else() -> None:
|
||||
for i in (1, 2, 3):
|
||||
print(i)
|
||||
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
|
||||
class Foobar(unittest.TestCase):
|
||||
def evil_raises(self) -> None:
|
||||
with self.assertRaises(Exception):
|
||||
raise Exception("Evil I say!")
|
||||
|
||||
def context_manager_raises(self) -> None:
|
||||
with self.assertRaises(Exception) as ex:
|
||||
raise Exception("Context manager is good")
|
||||
self.assertEqual("Context manager is good", str(ex.exception))
|
||||
|
||||
def regex_raises(self) -> None:
|
||||
with self.assertRaisesRegex(Exception, "Regex is good"):
|
||||
raise Exception("Regex is good")
|
||||
|
||||
def raises_with_absolute_reference(self):
|
||||
with self.assertRaises(asyncio.CancelledError):
|
||||
Foo()
|
||||
59
resources/test/fixtures/B018.py
vendored
Normal file
59
resources/test/fixtures/B018.py
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
class Foo1:
|
||||
"""abc"""
|
||||
|
||||
|
||||
class Foo2:
|
||||
"""abc"""
|
||||
|
||||
a = 2
|
||||
"str" # Str (no raise)
|
||||
f"{int}" # JoinedStr (no raise)
|
||||
1j # Number (complex)
|
||||
1 # Number (int)
|
||||
1.0 # Number (float)
|
||||
b"foo" # Binary
|
||||
True # NameConstant (True)
|
||||
False # NameConstant (False)
|
||||
None # NameConstant (None)
|
||||
[1, 2] # list
|
||||
{1, 2} # set
|
||||
{"foo": "bar"} # dict
|
||||
|
||||
|
||||
class Foo3:
|
||||
123
|
||||
a = 2
|
||||
"str"
|
||||
1
|
||||
|
||||
|
||||
def foo1():
|
||||
"""my docstring"""
|
||||
|
||||
|
||||
def foo2():
|
||||
"""my docstring"""
|
||||
a = 2
|
||||
"str" # Str (no raise)
|
||||
f"{int}" # JoinedStr (no raise)
|
||||
1j # Number (complex)
|
||||
1 # Number (int)
|
||||
1.0 # Number (float)
|
||||
b"foo" # Binary
|
||||
True # NameConstant (True)
|
||||
False # NameConstant (False)
|
||||
None # NameConstant (None)
|
||||
[1, 2] # list
|
||||
{1, 2} # set
|
||||
{"foo": "bar"} # dict
|
||||
|
||||
|
||||
def foo3():
|
||||
123
|
||||
a = 2
|
||||
"str"
|
||||
3
|
||||
|
||||
|
||||
def foo4():
|
||||
...
|
||||
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)
|
||||
)
|
||||
|
||||
6
resources/test/fixtures/C402.py
vendored
6
resources/test/fixtures/C402.py
vendored
@@ -1 +1,5 @@
|
||||
d = dict((x, x) for x in range(3))
|
||||
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)]
|
||||
)
|
||||
|
||||
3
resources/test/fixtures/C404.py
vendored
3
resources/test/fixtures/C404.py
vendored
@@ -1 +1,2 @@
|
||||
d = dict([(i, i) for i in range(3)])
|
||||
dict([(i, i) for i in range(3)])
|
||||
dict([(i, i) for i in range(3)], z=4)
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
18
resources/test/fixtures/E501.py
vendored
18
resources/test/fixtures/E501.py
vendored
@@ -14,3 +14,21 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor i
|
||||
|
||||
_ = "---------------------------------------------------------------------------AAAAAAA"
|
||||
_ = "---------------------------------------------------------------------------亜亜亜亜亜亜亜"
|
||||
|
||||
|
||||
def caller(string: str) -> None:
|
||||
assert string is not None
|
||||
|
||||
|
||||
caller(
|
||||
"""
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
caller(
|
||||
"""
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""", # noqa: E501
|
||||
)
|
||||
|
||||
5
resources/test/fixtures/F401_5.py
vendored
Normal file
5
resources/test/fixtures/F401_5.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Test: removal of multi-segment and aliases imports."""
|
||||
from a.b import c
|
||||
from d.e import f as g
|
||||
import h.i
|
||||
import j.k as l
|
||||
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"]
|
||||
14
resources/test/fixtures/F821_3.py
vendored
Normal file
14
resources/test/fixtures/F821_3.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Test (edge case): accesses of object named `dict`."""
|
||||
|
||||
# OK
|
||||
dict = {"parameters": {}}
|
||||
dict["parameters"]["userId"] = "userId"
|
||||
dict["parameters"]["itemId"] = "itemId"
|
||||
del dict["parameters"]["itemId"]
|
||||
|
||||
# F821 Undefined name `key`
|
||||
# F821 Undefined name `value`
|
||||
x: dict["key", "value"]
|
||||
|
||||
# OK
|
||||
x: dict[str, str]
|
||||
15
resources/test/fixtures/N802.py
vendored
15
resources/test/fixtures/N802.py
vendored
@@ -1,3 +1,6 @@
|
||||
import unittest
|
||||
|
||||
|
||||
def Bad():
|
||||
pass
|
||||
|
||||
@@ -24,3 +27,15 @@ def _good():
|
||||
|
||||
def good_func():
|
||||
pass
|
||||
|
||||
|
||||
def tearDownModule():
|
||||
pass
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
return super().tearDown()
|
||||
|
||||
def testTest(self):
|
||||
assert True
|
||||
|
||||
5
resources/test/fixtures/N806.py
vendored
Normal file
5
resources/test/fixtures/N806.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
def f():
|
||||
lower = 0
|
||||
Camel = 0
|
||||
CONSTANT = 0
|
||||
_ = 0
|
||||
15
resources/test/fixtures/N807.py
vendored
Normal file
15
resources/test/fixtures/N807.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
def __bad__():
|
||||
pass
|
||||
|
||||
|
||||
def __good():
|
||||
pass
|
||||
|
||||
|
||||
def good__():
|
||||
pass
|
||||
|
||||
|
||||
class Class:
|
||||
def __good__(self):
|
||||
pass
|
||||
3
resources/test/fixtures/N811.py
vendored
Normal file
3
resources/test/fixtures/N811.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import mod.CONST as const
|
||||
from mod import CONSTANT as constant
|
||||
from mod import ANOTHER_CONSTANT as another_constant
|
||||
3
resources/test/fixtures/N812.py
vendored
Normal file
3
resources/test/fixtures/N812.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import modl.lowercase as Lower
|
||||
from mod import lowercase as Lowercase
|
||||
from mod import another_lowercase as AnotherLowercase
|
||||
3
resources/test/fixtures/N813.py
vendored
Normal file
3
resources/test/fixtures/N813.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import mod.Camel as camel
|
||||
from mod import CamelCase as camelcase
|
||||
from mod import AnotherCamelCase as another_camelcase
|
||||
3
resources/test/fixtures/N814.py
vendored
Normal file
3
resources/test/fixtures/N814.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import mod.Camel as CAMEL
|
||||
from mod import CamelCase as CAMELCASE
|
||||
from mod import AnotherCamelCase as ANOTHER_CAMELCASE
|
||||
6
resources/test/fixtures/N815.py
vendored
Normal file
6
resources/test/fixtures/N815.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
class C:
|
||||
lower = 0
|
||||
CONSTANT = 0
|
||||
mixedCase = 0
|
||||
_mixedCase = 0
|
||||
mixed_Case = 0
|
||||
5
resources/test/fixtures/N816.py
vendored
Normal file
5
resources/test/fixtures/N816.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
lower = 0
|
||||
CONSTANT = 0
|
||||
mixedCase = 0
|
||||
_mixedCase = 0
|
||||
mixed_Case = 0
|
||||
2
resources/test/fixtures/N817.py
vendored
Normal file
2
resources/test/fixtures/N817.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import mod.CaMel as CM
|
||||
from mod import CamelCase as CC
|
||||
10
resources/test/fixtures/N818.py
vendored
Normal file
10
resources/test/fixtures/N818.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AnotherError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class C(Exception):
|
||||
pass
|
||||
6
resources/test/fixtures/RUF001.py
vendored
Normal file
6
resources/test/fixtures/RUF001.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
x = "𝐁ad string"
|
||||
|
||||
|
||||
def f():
|
||||
"""Here's a comment with an unusual parenthesis: )"""
|
||||
...
|
||||
6
resources/test/fixtures/RUF002.py
vendored
Normal file
6
resources/test/fixtures/RUF002.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
x = "𝐁ad string"
|
||||
|
||||
|
||||
def f():
|
||||
"""Here's a comment with an unusual parenthesis: )"""
|
||||
...
|
||||
35
resources/test/fixtures/W605_0.py
vendored
Normal file
35
resources/test/fixtures/W605_0.py
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
#: W605:1:10
|
||||
regex = '\.png$'
|
||||
|
||||
#: W605:2:1
|
||||
regex = '''
|
||||
\.png$
|
||||
'''
|
||||
|
||||
#: W605:2:6
|
||||
f(
|
||||
'\_'
|
||||
)
|
||||
|
||||
#: W605:4:6
|
||||
"""
|
||||
multi-line
|
||||
literal
|
||||
with \_ somewhere
|
||||
in the middle
|
||||
"""
|
||||
|
||||
#: Okay
|
||||
regex = r'\.png$'
|
||||
regex = '\\.png$'
|
||||
regex = r'''
|
||||
\.png$
|
||||
'''
|
||||
regex = r'''
|
||||
\\.png$
|
||||
'''
|
||||
s = '\\'
|
||||
regex = '\w' # noqa
|
||||
regex = '''
|
||||
\w
|
||||
''' # noqa
|
||||
35
resources/test/fixtures/W605_1.py
vendored
Normal file
35
resources/test/fixtures/W605_1.py
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
#: W605:1:10
|
||||
regex = '\.png$'
|
||||
|
||||
#: W605:2:1
|
||||
regex = '''
|
||||
\.png$
|
||||
'''
|
||||
|
||||
#: W605:2:6
|
||||
f(
|
||||
'\_'
|
||||
)
|
||||
|
||||
#: W605:4:6
|
||||
"""
|
||||
multi-line
|
||||
literal
|
||||
with \_ somewhere
|
||||
in the middle
|
||||
"""
|
||||
|
||||
#: Okay
|
||||
regex = r'\.png$'
|
||||
regex = '\\.png$'
|
||||
regex = r'''
|
||||
\.png$
|
||||
'''
|
||||
regex = r'''
|
||||
\\.png$
|
||||
'''
|
||||
s = '\\'
|
||||
regex = '\w' # noqa
|
||||
regex = '''
|
||||
\w
|
||||
''' # noqa
|
||||
2
resources/test/fixtures/__init__.py
vendored
2
resources/test/fixtures/__init__.py
vendored
@@ -1,3 +1,5 @@
|
||||
import os
|
||||
|
||||
print(__path__)
|
||||
|
||||
__all__ = ["a", "b", "c"]
|
||||
|
||||
38
resources/test/fixtures/flake8_quotes/docstring_doubles.py
vendored
Normal file
38
resources/test/fixtures/flake8_quotes/docstring_doubles.py
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
Double quotes multiline module docstring
|
||||
"""
|
||||
|
||||
"""
|
||||
this is not a docstring
|
||||
"""
|
||||
|
||||
l = []
|
||||
|
||||
class Cls:
|
||||
"""
|
||||
Double quotes multiline class docstring
|
||||
"""
|
||||
|
||||
"""
|
||||
this is not a docstring
|
||||
"""
|
||||
|
||||
# The colon in the list indexing below is an edge case for the docstring scanner
|
||||
def f(self, bar="""
|
||||
definitely not a docstring""",
|
||||
val=l[Cls():3]):
|
||||
"""
|
||||
Double quotes multiline function docstring
|
||||
"""
|
||||
|
||||
some_expression = 'hello world'
|
||||
|
||||
"""
|
||||
this is not a docstring
|
||||
"""
|
||||
|
||||
if l:
|
||||
"""
|
||||
Looks like a docstring, but in reality it isn't - only modules, classes and functions
|
||||
"""
|
||||
pass
|
||||
9
resources/test/fixtures/flake8_quotes/docstring_doubles_class.py
vendored
Normal file
9
resources/test/fixtures/flake8_quotes/docstring_doubles_class.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
class SingleLineDocstrings():
|
||||
""" Double quotes single line class docstring """
|
||||
""" Not a docstring """
|
||||
|
||||
def foo(self, bar="""not a docstring"""):
|
||||
""" Double quotes single line method docstring"""
|
||||
pass
|
||||
|
||||
class Nested(foo()[:]): """ inline docstring """; pass
|
||||
22
resources/test/fixtures/flake8_quotes/docstring_doubles_function.py
vendored
Normal file
22
resources/test/fixtures/flake8_quotes/docstring_doubles_function.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
def foo():
|
||||
"""function without params, single line docstring"""
|
||||
""" not a docstring"""
|
||||
return
|
||||
|
||||
|
||||
def foo2():
|
||||
"""
|
||||
function without params, multiline docstring
|
||||
"""
|
||||
""" not a docstring"""
|
||||
return
|
||||
|
||||
|
||||
def fun_with_params_no_docstring(a, b="""
|
||||
not a
|
||||
""" """docstring"""):
|
||||
pass
|
||||
|
||||
def fun_with_params_no_docstring2(a, b=c[foo():], c=\
|
||||
""" not a docstring """):
|
||||
pass
|
||||
11
resources/test/fixtures/flake8_quotes/docstring_doubles_module_multiline.py
vendored
Normal file
11
resources/test/fixtures/flake8_quotes/docstring_doubles_module_multiline.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Double quotes multiline module docstring
|
||||
"""
|
||||
"""
|
||||
this is not a docstring
|
||||
"""
|
||||
def foo():
|
||||
pass
|
||||
"""
|
||||
this is not a docstring
|
||||
"""
|
||||
6
resources/test/fixtures/flake8_quotes/docstring_doubles_module_singleline.py
vendored
Normal file
6
resources/test/fixtures/flake8_quotes/docstring_doubles_module_singleline.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
""" Double quotes singleline module docstring """
|
||||
""" this is not a docstring """
|
||||
|
||||
def foo():
|
||||
pass
|
||||
""" this is not a docstring """
|
||||
40
resources/test/fixtures/flake8_quotes/docstring_singles.py
vendored
Normal file
40
resources/test/fixtures/flake8_quotes/docstring_singles.py
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
'''
|
||||
Single quotes multiline module docstring
|
||||
'''
|
||||
|
||||
'''
|
||||
this is not a docstring
|
||||
'''
|
||||
|
||||
l = []
|
||||
|
||||
class Cls(MakeKlass('''
|
||||
class params \t not a docstring
|
||||
''')):
|
||||
'''
|
||||
Single quotes multiline class docstring
|
||||
'''
|
||||
|
||||
'''
|
||||
this is not a docstring
|
||||
'''
|
||||
|
||||
# The colon in the list indexing below is an edge case for the docstring scanner
|
||||
def f(self, bar='''
|
||||
definitely not a docstring''',
|
||||
val=l[Cls():3]):
|
||||
'''
|
||||
Single quotes multiline function docstring
|
||||
'''
|
||||
|
||||
some_expression = 'hello world'
|
||||
|
||||
'''
|
||||
this is not a docstring
|
||||
'''
|
||||
|
||||
if l:
|
||||
'''
|
||||
Looks like a docstring, but in reality it isn't - only modules, classes and functions
|
||||
'''
|
||||
pass
|
||||
9
resources/test/fixtures/flake8_quotes/docstring_singles_class.py
vendored
Normal file
9
resources/test/fixtures/flake8_quotes/docstring_singles_class.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
class SingleLineDocstrings():
|
||||
''' Double quotes single line class docstring '''
|
||||
''' Not a docstring '''
|
||||
|
||||
def foo(self, bar='''not a docstring'''):
|
||||
''' Double quotes single line method docstring'''
|
||||
pass
|
||||
|
||||
class Nested(foo()[:]): ''' inline docstring '''; pass
|
||||
23
resources/test/fixtures/flake8_quotes/docstring_singles_function.py
vendored
Normal file
23
resources/test/fixtures/flake8_quotes/docstring_singles_function.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
def foo():
|
||||
'''function without params, single line docstring'''
|
||||
''' not a docstring'''
|
||||
return
|
||||
|
||||
|
||||
def foo2():
|
||||
'''
|
||||
function without params, multiline docstring
|
||||
'''
|
||||
''' not a docstring'''
|
||||
return
|
||||
|
||||
|
||||
def fun_with_params_no_docstring(a, b='''
|
||||
not a
|
||||
''' '''docstring'''):
|
||||
pass
|
||||
|
||||
def fun_with_params_no_docstring2(a, b=c[foo():], c=\
|
||||
''' not a docstring '''):
|
||||
pass
|
||||
|
||||
11
resources/test/fixtures/flake8_quotes/docstring_singles_module_multiline.py
vendored
Normal file
11
resources/test/fixtures/flake8_quotes/docstring_singles_module_multiline.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
'''
|
||||
Double quotes multiline module docstring
|
||||
'''
|
||||
'''
|
||||
this is not a docstring
|
||||
'''
|
||||
def foo():
|
||||
pass
|
||||
'''
|
||||
this is not a docstring
|
||||
'''
|
||||
6
resources/test/fixtures/flake8_quotes/docstring_singles_module_singleline.py
vendored
Normal file
6
resources/test/fixtures/flake8_quotes/docstring_singles_module_singleline.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
''' Double quotes singleline module docstring '''
|
||||
''' this is not a docstring '''
|
||||
|
||||
def foo():
|
||||
pass
|
||||
''' this is not a docstring '''
|
||||
2
resources/test/fixtures/flake8_quotes/doubles.py
vendored
Normal file
2
resources/test/fixtures/flake8_quotes/doubles.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
this_should_be_linted = "double quote string"
|
||||
this_should_be_linted = u"double quote string"
|
||||
5
resources/test/fixtures/flake8_quotes/doubles_escaped.py
vendored
Normal file
5
resources/test/fixtures/flake8_quotes/doubles_escaped.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
this_should_raise_Q003 = 'This is a \'string\''
|
||||
this_is_fine = '"This" is a \'string\''
|
||||
this_is_fine = "This is a 'string'"
|
||||
this_is_fine = "\"This\" is a 'string'"
|
||||
this_is_fine = r'This is a \'string\''
|
||||
9
resources/test/fixtures/flake8_quotes/doubles_multiline_string.py
vendored
Normal file
9
resources/test/fixtures/flake8_quotes/doubles_multiline_string.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
s = """ This "should"
|
||||
be
|
||||
"linted" """
|
||||
|
||||
s = ''' This "should"
|
||||
"not" be
|
||||
"linted" '''
|
||||
|
||||
s = """'This should not be linted due to having would-be quadruple end quote'"""
|
||||
1
resources/test/fixtures/flake8_quotes/doubles_noqa.py
vendored
Normal file
1
resources/test/fixtures/flake8_quotes/doubles_noqa.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
this_should_not_be_linted = "double quote string" # noqa
|
||||
2
resources/test/fixtures/flake8_quotes/doubles_wrapped.py
vendored
Normal file
2
resources/test/fixtures/flake8_quotes/doubles_wrapped.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
s = 'double "quotes" wrapped in singles are ignored'
|
||||
s = "single 'quotes' wrapped in doubles are ignored"
|
||||
2
resources/test/fixtures/flake8_quotes/singles.py
vendored
Normal file
2
resources/test/fixtures/flake8_quotes/singles.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
this_should_be_linted = 'single quote string'
|
||||
this_should_be_linted = u'double quote string'
|
||||
5
resources/test/fixtures/flake8_quotes/singles_escaped.py
vendored
Normal file
5
resources/test/fixtures/flake8_quotes/singles_escaped.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
this_should_raise_Q003 = "This is a \"string\""
|
||||
this_is_fine = "'This' is a \"string\""
|
||||
this_is_fine = 'This is a "string"'
|
||||
this_is_fine = '\'This\' is a "string"'
|
||||
this_is_fine = r"This is a \"string\""
|
||||
9
resources/test/fixtures/flake8_quotes/singles_multiline_string.py
vendored
Normal file
9
resources/test/fixtures/flake8_quotes/singles_multiline_string.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
s = ''' This 'should'
|
||||
be
|
||||
'linted' '''
|
||||
|
||||
s = """ This 'should'
|
||||
'not' be
|
||||
'linted' """
|
||||
|
||||
s = '''"This should not be linted due to having would-be quadruple end quote"'''
|
||||
1
resources/test/fixtures/flake8_quotes/singles_noqa.py
vendored
Normal file
1
resources/test/fixtures/flake8_quotes/singles_noqa.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
this_should_not_be_linted = 'single quote string' # noqa
|
||||
2
resources/test/fixtures/flake8_quotes/singles_wrapped.py
vendored
Normal file
2
resources/test/fixtures/flake8_quotes/singles_wrapped.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
s = "single 'quotes' wrapped in doubles are ignored"
|
||||
s = 'double "quotes" wrapped in singles are ignored'
|
||||
35
resources/test/fixtures/pyproject.toml
vendored
35
resources/test/fixtures/pyproject.toml
vendored
@@ -1,7 +1,36 @@
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
extend-exclude = [
|
||||
"excluded.py",
|
||||
"migrations",
|
||||
"directory/also_excluded.py",
|
||||
"excluded.py",
|
||||
"migrations",
|
||||
"directory/also_excluded.py",
|
||||
]
|
||||
per-file-ignores = { "__init__.py" = ["F401"] }
|
||||
|
||||
[tool.ruff.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
multiline-quotes = "double"
|
||||
docstring-quotes = "double"
|
||||
avoid-escape = true
|
||||
|
||||
[tool.ruff.pep8-naming]
|
||||
ignore-names = [
|
||||
"setUp",
|
||||
"tearDown",
|
||||
"setUpClass",
|
||||
"tearDownClass",
|
||||
"setUpModule",
|
||||
"tearDownModule",
|
||||
"asyncSetUp",
|
||||
"asyncTearDown",
|
||||
"setUpTestData",
|
||||
"failureException",
|
||||
"longMessage",
|
||||
"maxDiff",
|
||||
]
|
||||
classmethod-decorators = [
|
||||
"classmethod",
|
||||
]
|
||||
staticmethod-decorators = [
|
||||
"staticmethod",
|
||||
]
|
||||
|
||||
1
rust-toolchain
Normal file
1
rust-toolchain
Normal file
@@ -0,0 +1 @@
|
||||
1.65.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,12 +129,13 @@ 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(
|
||||
relative.row() + base.row() - 1,
|
||||
relative.column() + base.column() - 1,
|
||||
relative.column() + base.column(),
|
||||
)
|
||||
} else {
|
||||
Location::new(relative.row() + base.row() - 1, relative.column())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Location, Stmt, StmtKind};
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::{BindingKind, Range, Scope};
|
||||
use crate::ast::types::{BindingKind, Scope};
|
||||
|
||||
/// Extract the names bound to a given __all__ assignment.
|
||||
pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
|
||||
@@ -117,69 +117,3 @@ pub fn is_unpacking_assignment(stmt: &Stmt) -> bool {
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Struct used to efficiently slice source code at (row, column) Locations.
|
||||
pub struct SourceCodeLocator<'a> {
|
||||
content: &'a str,
|
||||
offsets: Vec<Vec<usize>>,
|
||||
initialized: bool,
|
||||
}
|
||||
|
||||
impl<'a> SourceCodeLocator<'a> {
|
||||
pub fn new(content: &'a str) -> Self {
|
||||
SourceCodeLocator {
|
||||
content,
|
||||
offsets: vec![],
|
||||
initialized: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
if !self.initialized {
|
||||
let mut offset = 0;
|
||||
for line in self.content.lines() {
|
||||
let mut newline = 0;
|
||||
let mut line_offsets: Vec<usize> = vec![];
|
||||
for (i, _char) in line.char_indices() {
|
||||
line_offsets.push(offset + i);
|
||||
newline = i + 1;
|
||||
}
|
||||
line_offsets.push(offset + newline);
|
||||
self.offsets.push(line_offsets);
|
||||
offset += newline + 1;
|
||||
}
|
||||
self.offsets.push(vec![offset]);
|
||||
self.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slice_source_code_at(&mut self, location: &Location) -> &'a str {
|
||||
self.init();
|
||||
let offset = self.offsets[location.row() - 1][location.column() - 1];
|
||||
&self.content[offset..]
|
||||
}
|
||||
|
||||
pub fn slice_source_code_range(&mut self, range: &Range) -> &'a str {
|
||||
self.init();
|
||||
let start = self.offsets[range.location.row() - 1][range.location.column() - 1];
|
||||
let end = self.offsets[range.end_location.row() - 1][range.end_location.column() - 1];
|
||||
&self.content[start..end]
|
||||
}
|
||||
|
||||
pub fn partition_source_code_at(
|
||||
&mut self,
|
||||
outer: &Range,
|
||||
inner: &Range,
|
||||
) -> (&'a str, &'a str, &'a str) {
|
||||
self.init();
|
||||
let outer_start = self.offsets[outer.location.row() - 1][outer.location.column() - 1];
|
||||
let outer_end = self.offsets[outer.end_location.row() - 1][outer.end_location.column() - 1];
|
||||
let inner_start = self.offsets[inner.location.row() - 1][inner.location.column() - 1];
|
||||
let inner_end = self.offsets[inner.end_location.row() - 1][inner.end_location.column() - 1];
|
||||
(
|
||||
&self.content[outer_start..inner_start],
|
||||
&self.content[inner_start..inner_end],
|
||||
&self.content[inner_end..outer_end],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -36,6 +36,7 @@ pub enum ScopeKind {
|
||||
Function(FunctionScope),
|
||||
Generator,
|
||||
Module,
|
||||
Arg,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -85,8 +86,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)>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
use crate::autofix::Fix;
|
||||
use crate::autofix::Patch;
|
||||
use itertools::Itertools;
|
||||
use rustpython_parser::ast::Location;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::checks::Check;
|
||||
use itertools::Itertools;
|
||||
use ropey::RopeBuilder;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::{Fix, Patch};
|
||||
use crate::checks::Check;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
// 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,
|
||||
@@ -13,6 +19,17 @@ pub enum Mode {
|
||||
None,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
/// Return `true` if a patch should be generated under the given `Mode`.
|
||||
pub fn patch(&self) -> bool {
|
||||
match &self {
|
||||
Mode::Generate => true,
|
||||
Mode::Apply => true,
|
||||
Mode::None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Mode {
|
||||
fn from(value: bool) -> Self {
|
||||
match value {
|
||||
@@ -23,77 +40,64 @@ impl From<bool> for Mode {
|
||||
}
|
||||
|
||||
/// Auto-fix errors in a file, and write the fixed source code to disk.
|
||||
pub fn fix_file(checks: &mut [Check], contents: &str) -> Option<String> {
|
||||
pub fn fix_file<'a>(
|
||||
checks: &'a mut [Check],
|
||||
locator: &'a SourceCodeLocator<'a>,
|
||||
) -> Option<Cow<'a, str>> {
|
||||
if checks.iter().all(|check| check.fix.is_none()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(apply_fixes(
|
||||
checks.iter_mut().filter_map(|check| check.fix.as_mut()),
|
||||
contents,
|
||||
locator,
|
||||
))
|
||||
}
|
||||
|
||||
/// Apply a series of fixes.
|
||||
fn apply_fixes<'a>(fixes: impl Iterator<Item = &'a mut Fix>, contents: &str) -> String {
|
||||
let lines: Vec<&str> = contents.lines().collect();
|
||||
|
||||
let mut output: String = Default::default();
|
||||
let mut last_pos: Location = Default::default();
|
||||
let mut applied: BTreeSet<&Patch> = Default::default();
|
||||
fn apply_fixes<'a>(
|
||||
fixes: impl Iterator<Item = &'a mut Fix>,
|
||||
locator: &'a SourceCodeLocator<'a>,
|
||||
) -> Cow<'a, str> {
|
||||
let mut output = RopeBuilder::new();
|
||||
let mut last_pos: Location = Location::new(1, 0);
|
||||
let mut applied: BTreeSet<&Patch> = BTreeSet::new();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if fix.patch.location.row() > last_pos.row() {
|
||||
if last_pos.row() > 0 || last_pos.column() > 0 {
|
||||
output.push_str(&lines[last_pos.row() - 1][last_pos.column() - 1..]);
|
||||
output.push('\n');
|
||||
}
|
||||
for line in &lines[last_pos.row()..fix.patch.location.row() - 1] {
|
||||
output.push_str(line);
|
||||
output.push('\n');
|
||||
}
|
||||
output
|
||||
.push_str(&lines[fix.patch.location.row() - 1][..fix.patch.location.column() - 1]);
|
||||
output.push_str(&fix.patch.content);
|
||||
} else {
|
||||
output.push_str(
|
||||
&lines[last_pos.row() - 1][last_pos.column() - 1..fix.patch.location.column() - 1],
|
||||
);
|
||||
output.push_str(&fix.patch.content);
|
||||
}
|
||||
last_pos = fix.patch.end_location;
|
||||
// Add all contents from `last_pos` to `fix.patch.location`.
|
||||
let slice = locator.slice_source_code_range(&Range {
|
||||
location: last_pos,
|
||||
end_location: fix.patch.location,
|
||||
});
|
||||
output.append(&slice);
|
||||
|
||||
// Add the patch itself.
|
||||
output.append(&fix.patch.content);
|
||||
|
||||
// Track that the fix was applied.
|
||||
last_pos = fix.patch.end_location;
|
||||
applied.insert(&fix.patch);
|
||||
fix.applied = true;
|
||||
}
|
||||
|
||||
if last_pos.row() > 0
|
||||
&& (last_pos.row() - 1) < lines.len()
|
||||
&& (last_pos.row() > 0 || last_pos.column() > 0)
|
||||
{
|
||||
output.push_str(&lines[last_pos.row() - 1][last_pos.column() - 1..]);
|
||||
output.push('\n');
|
||||
}
|
||||
if last_pos.row() < lines.len() {
|
||||
for line in &lines[last_pos.row()..] {
|
||||
output.push_str(line);
|
||||
output.push('\n');
|
||||
}
|
||||
}
|
||||
// Add the remaining content.
|
||||
let slice = locator.slice_source_code_at(&last_pos);
|
||||
output.append(&slice);
|
||||
|
||||
output
|
||||
Cow::from(output.finish())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -102,13 +106,14 @@ 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};
|
||||
use crate::SourceCodeLocator;
|
||||
|
||||
#[test]
|
||||
fn empty_file() -> Result<()> {
|
||||
let mut fixes = vec![];
|
||||
let actual = apply_fixes(fixes.iter_mut(), "");
|
||||
let locator = SourceCodeLocator::new("");
|
||||
let actual = apply_fixes(fixes.iter_mut(), &locator);
|
||||
let expected = "";
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
@@ -121,17 +126,17 @@ mod tests {
|
||||
let mut fixes = vec![Fix {
|
||||
patch: Patch {
|
||||
content: "Bar".to_string(),
|
||||
location: Location::new(1, 9),
|
||||
end_location: Location::new(1, 15),
|
||||
location: Location::new(1, 8),
|
||||
end_location: Location::new(1, 14),
|
||||
},
|
||||
applied: false,
|
||||
}];
|
||||
let actual = apply_fixes(
|
||||
fixes.iter_mut(),
|
||||
let locator = SourceCodeLocator::new(
|
||||
"class A(object):
|
||||
...
|
||||
",
|
||||
);
|
||||
let actual = apply_fixes(fixes.iter_mut(), &locator);
|
||||
|
||||
let expected = "class A(Bar):
|
||||
...
|
||||
@@ -147,17 +152,17 @@ mod tests {
|
||||
let mut fixes = vec![Fix {
|
||||
patch: Patch {
|
||||
content: "".to_string(),
|
||||
location: Location::new(1, 8),
|
||||
end_location: Location::new(1, 16),
|
||||
location: Location::new(1, 7),
|
||||
end_location: Location::new(1, 15),
|
||||
},
|
||||
applied: false,
|
||||
}];
|
||||
let actual = apply_fixes(
|
||||
fixes.iter_mut(),
|
||||
let locator = SourceCodeLocator::new(
|
||||
"class A(object):
|
||||
...
|
||||
",
|
||||
);
|
||||
let actual = apply_fixes(fixes.iter_mut(), &locator);
|
||||
|
||||
let expected = "class A:
|
||||
...
|
||||
@@ -174,26 +179,26 @@ mod tests {
|
||||
Fix {
|
||||
patch: Patch {
|
||||
content: "".to_string(),
|
||||
location: Location::new(1, 8),
|
||||
end_location: Location::new(1, 17),
|
||||
location: Location::new(1, 7),
|
||||
end_location: Location::new(1, 16),
|
||||
},
|
||||
applied: false,
|
||||
},
|
||||
Fix {
|
||||
patch: Patch {
|
||||
content: "".to_string(),
|
||||
location: Location::new(1, 17),
|
||||
end_location: Location::new(1, 24),
|
||||
location: Location::new(1, 16),
|
||||
end_location: Location::new(1, 23),
|
||||
},
|
||||
applied: false,
|
||||
},
|
||||
];
|
||||
let actual = apply_fixes(
|
||||
fixes.iter_mut(),
|
||||
let locator = SourceCodeLocator::new(
|
||||
"class A(object, object):
|
||||
...
|
||||
",
|
||||
);
|
||||
let actual = apply_fixes(fixes.iter_mut(), &locator);
|
||||
|
||||
let expected = "class A:
|
||||
...
|
||||
@@ -210,26 +215,26 @@ mod tests {
|
||||
Fix {
|
||||
patch: Patch {
|
||||
content: "".to_string(),
|
||||
location: Location::new(1, 8),
|
||||
end_location: Location::new(1, 16),
|
||||
location: Location::new(1, 7),
|
||||
end_location: Location::new(1, 15),
|
||||
},
|
||||
applied: false,
|
||||
},
|
||||
Fix {
|
||||
patch: Patch {
|
||||
content: "ignored".to_string(),
|
||||
location: Location::new(1, 10),
|
||||
end_location: Location::new(1, 12),
|
||||
location: Location::new(1, 9),
|
||||
end_location: Location::new(1, 11),
|
||||
},
|
||||
applied: false,
|
||||
},
|
||||
];
|
||||
let actual = apply_fixes(
|
||||
fixes.iter_mut(),
|
||||
let locator = SourceCodeLocator::new(
|
||||
"class A(object):
|
||||
...
|
||||
",
|
||||
);
|
||||
let actual = apply_fixes(fixes.iter_mut(), &locator);
|
||||
|
||||
let expected = "class A:
|
||||
...
|
||||
|
||||
@@ -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,10 +81,11 @@ 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(), 1),
|
||||
Location::new(stmt.end_location.unwrap().row() + 1, 1),
|
||||
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
|
||||
|
||||
572
src/check_ast.rs
572
src/check_ast.rs
@@ -1,3 +1,5 @@
|
||||
//! Lint rules based on AST traversal.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
@@ -9,8 +11,8 @@ use rustpython_parser::ast::{
|
||||
};
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::ast::helpers::{extract_handler_names, match_name_or_attr, SubscriptKind};
|
||||
use crate::ast::operations::{extract_all_names, SourceCodeLocator};
|
||||
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::{
|
||||
Binding, BindingContext, BindingKind, CheckLocator, FunctionScope, ImportKind, Range, Scope,
|
||||
@@ -23,21 +25,36 @@ 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::settings::{PythonVersion, Settings};
|
||||
use crate::python::typing;
|
||||
use crate::python::typing::SubscriptKind;
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::settings::Settings;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
|
||||
use crate::{
|
||||
docstrings, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_print, pep8_naming,
|
||||
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.
|
||||
pub(crate) path: &'a Path,
|
||||
pub(crate) locator: SourceCodeLocator<'a>,
|
||||
path: &'a Path,
|
||||
autofix: &'a fixer::Mode,
|
||||
pub(crate) settings: &'a Settings,
|
||||
pub(crate) autofix: &'a fixer::Mode,
|
||||
pub(crate) locator: &'a SourceCodeLocator<'a>,
|
||||
// Computed checks.
|
||||
checks: Vec<Check>,
|
||||
// Docstring tracking.
|
||||
@@ -62,10 +79,12 @@ pub struct Checker<'a> {
|
||||
in_f_string: Option<Range>,
|
||||
in_annotation: bool,
|
||||
in_literal: bool,
|
||||
in_subscript: bool,
|
||||
seen_import_boundary: bool,
|
||||
futures_allowed: bool,
|
||||
annotations_future_enabled: bool,
|
||||
except_handlers: Vec<Vec<String>>,
|
||||
from_imports: BTreeMap<&'a str, BTreeSet<&'a str>>,
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
@@ -73,13 +92,13 @@ impl<'a> Checker<'a> {
|
||||
settings: &'a Settings,
|
||||
autofix: &'a fixer::Mode,
|
||||
path: &'a Path,
|
||||
content: &'a str,
|
||||
locator: &'a SourceCodeLocator,
|
||||
) -> Checker<'a> {
|
||||
Checker {
|
||||
settings,
|
||||
autofix,
|
||||
path,
|
||||
locator: SourceCodeLocator::new(content),
|
||||
locator,
|
||||
checks: Default::default(),
|
||||
docstrings: Default::default(),
|
||||
deletions: Default::default(),
|
||||
@@ -97,15 +116,37 @@ 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(),
|
||||
in_subscript: 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`.
|
||||
pub fn patch(&self) -> bool {
|
||||
// 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"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Visitor<'b> for Checker<'a>
|
||||
@@ -199,6 +240,7 @@ where
|
||||
decorator_list,
|
||||
returns,
|
||||
args,
|
||||
body,
|
||||
..
|
||||
}
|
||||
| StmtKind::AsyncFunctionDef {
|
||||
@@ -206,6 +248,7 @@ where
|
||||
decorator_list,
|
||||
returns,
|
||||
args,
|
||||
body,
|
||||
..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::E743) {
|
||||
@@ -218,7 +261,11 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N802) {
|
||||
if let Some(check) = pep8_naming::checks::invalid_function_name(stmt, name) {
|
||||
if let Some(check) = pep8_naming::checks::invalid_function_name(
|
||||
stmt,
|
||||
name,
|
||||
&self.settings.pep8_naming,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
@@ -229,6 +276,7 @@ where
|
||||
self.current_scope(),
|
||||
decorator_list,
|
||||
args,
|
||||
&self.settings.pep8_naming,
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
@@ -240,14 +288,28 @@ where
|
||||
self.current_scope(),
|
||||
decorator_list,
|
||||
args,
|
||||
&self.settings.pep8_naming,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N807) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::dunder_function_name(stmt, self.current_scope(), name)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::B018) {
|
||||
flake8_bugbear::plugins::useless_expression(self, body);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -314,6 +376,7 @@ where
|
||||
bases,
|
||||
keywords,
|
||||
decorator_list,
|
||||
body,
|
||||
..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::U004) {
|
||||
@@ -337,6 +400,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N818) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::error_suffix_on_exception_name(stmt, bases, name)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::B018) {
|
||||
flake8_bugbear::plugins::useless_expression(self, body);
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(
|
||||
name,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
@@ -356,7 +431,7 @@ where
|
||||
}
|
||||
StmtKind::Import { names } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E402) {
|
||||
if self.seen_import_boundary && stmt.location.column() == 1 {
|
||||
if self.seen_import_boundary && stmt.location.column() == 0 {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ModuleImportNotAtTopOfFile,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
@@ -405,6 +480,55 @@ where
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
let name = alias.node.name.split('.').last().unwrap();
|
||||
if self.settings.enabled.contains(&CheckCode::N811) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::constant_imported_as_non_constant(
|
||||
stmt, name, asname,
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N812) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::lowercase_imported_as_non_lowercase(
|
||||
stmt, name, asname,
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N813) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::camelcase_imported_as_lowercase(
|
||||
stmt, name, asname,
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N814) {
|
||||
if let Some(check) = pep8_naming::checks::camelcase_imported_as_constant(
|
||||
stmt, name, asname,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N817) {
|
||||
if let Some(check) = pep8_naming::checks::camelcase_imported_as_acronym(
|
||||
stmt, name, asname,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::ImportFrom {
|
||||
@@ -412,8 +536,26 @@ 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() == 1 {
|
||||
if self.seen_import_boundary && stmt.location.column() == 0 {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ModuleImportNotAtTopOfFile,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
@@ -525,14 +667,75 @@ where
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
if self.settings.enabled.contains(&CheckCode::N811) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::constant_imported_as_non_constant(
|
||||
stmt,
|
||||
&alias.node.name,
|
||||
asname,
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N812) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::lowercase_imported_as_non_lowercase(
|
||||
stmt,
|
||||
&alias.node.name,
|
||||
asname,
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N813) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::camelcase_imported_as_lowercase(
|
||||
stmt,
|
||||
&alias.node.name,
|
||||
asname,
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N814) {
|
||||
if let Some(check) = pep8_naming::checks::camelcase_imported_as_constant(
|
||||
stmt,
|
||||
&alias.node.name,
|
||||
asname,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N817) {
|
||||
if let Some(check) = pep8_naming::checks::camelcase_imported_as_acronym(
|
||||
stmt,
|
||||
&alias.node.name,
|
||||
asname,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::Raise { exc, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::F901) {
|
||||
if let Some(expr) = exc {
|
||||
if let Some(check) = pyflakes::checks::raise_not_implemented(expr) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
pyflakes::plugins::raise_not_implemented(self, expr);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B016) {
|
||||
if let Some(exc) = exc {
|
||||
flake8_bugbear::plugins::cannot_raise_literal(self, exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -552,6 +755,16 @@ where
|
||||
flake8_bugbear::plugins::assert_false(self, stmt, test, msg);
|
||||
}
|
||||
}
|
||||
StmtKind::With { items, .. } | StmtKind::AsyncWith { items, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::B017) {
|
||||
flake8_bugbear::plugins::assert_raises_exception(self, stmt, items);
|
||||
}
|
||||
}
|
||||
StmtKind::For { target, body, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::B007) {
|
||||
flake8_bugbear::plugins::unused_loop_control_variable(self, target, body);
|
||||
}
|
||||
}
|
||||
StmtKind::Try { handlers, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::F707) {
|
||||
if let Some(check) = pyflakes::checks::default_except_not_last(handlers) {
|
||||
@@ -563,6 +776,9 @@ where
|
||||
{
|
||||
flake8_bugbear::plugins::duplicate_exceptions(self, stmt, handlers);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B013) {
|
||||
flake8_bugbear::plugins::redundant_tuple_in_exception_handler(self, handlers);
|
||||
}
|
||||
}
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E731) {
|
||||
@@ -576,6 +792,9 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::U001) {
|
||||
pyupgrade::plugins::useless_metaclass_type(self, stmt, value, targets);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B003) {
|
||||
flake8_bugbear::plugins::assignment_to_os_environ(self, targets);
|
||||
}
|
||||
}
|
||||
StmtKind::AnnAssign { value, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E731) {
|
||||
@@ -710,7 +929,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;
|
||||
}
|
||||
}
|
||||
@@ -735,6 +954,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);
|
||||
}
|
||||
@@ -757,23 +977,19 @@ 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 {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::U005) {
|
||||
pyupgrade::plugins::deprecated_unittest_alias(self, func);
|
||||
@@ -793,25 +1009,43 @@ where
|
||||
|
||||
// flake8-comprehensions
|
||||
if self.settings.enabled.contains(&CheckCode::C400) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_generator_list(expr, func, args)
|
||||
{
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_list(
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C401) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_generator_set(expr, func, args)
|
||||
{
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_set(
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C402) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_generator_dict(expr, func, args)
|
||||
{
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_dict(
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
@@ -819,7 +1053,13 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::C403) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_list_comprehension_set(
|
||||
expr, func, args,
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
@@ -829,7 +1069,10 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::C404) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_list_comprehension_dict(
|
||||
expr, func, args,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
)
|
||||
{
|
||||
self.checks.push(check);
|
||||
@@ -837,24 +1080,42 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C405) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_literal_set(expr, func, args)
|
||||
{
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_set(
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C406) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_literal_dict(expr, func, args)
|
||||
{
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_dict(
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
@@ -863,7 +1124,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);
|
||||
@@ -873,7 +1139,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);
|
||||
@@ -881,9 +1152,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);
|
||||
};
|
||||
}
|
||||
@@ -891,7 +1167,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);
|
||||
@@ -901,7 +1179,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);
|
||||
@@ -911,7 +1191,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);
|
||||
@@ -919,9 +1201,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);
|
||||
};
|
||||
}
|
||||
@@ -1011,6 +1295,10 @@ where
|
||||
self,
|
||||
));
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::B002) {
|
||||
flake8_bugbear::plugins::unary_prefix_increment(self, expr, op, operand);
|
||||
}
|
||||
}
|
||||
ExprKind::Compare {
|
||||
left,
|
||||
@@ -1046,6 +1334,12 @@ where
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
));
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::B015) {
|
||||
if let Some(parent) = self.parents.last() {
|
||||
flake8_bugbear::plugins::useless_comparison(self, left, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
@@ -1094,7 +1388,12 @@ 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.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
@@ -1122,12 +1421,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]);
|
||||
@@ -1135,12 +1434,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);
|
||||
@@ -1157,7 +1456,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)])
|
||||
@@ -1189,7 +1488,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})
|
||||
@@ -1216,36 +1515,56 @@ where
|
||||
}
|
||||
}
|
||||
ExprKind::Subscript { value, slice, ctx } => {
|
||||
match helpers::match_annotated_subscript(value) {
|
||||
Some(subscript) => match subscript {
|
||||
// Ex) Optional[int]
|
||||
SubscriptKind::AnnotatedSubscript => {
|
||||
self.visit_expr(value);
|
||||
self.visit_annotation(slice);
|
||||
self.visit_expr_context(ctx);
|
||||
}
|
||||
// Ex) Annotated[int, "Hello, world!"]
|
||||
SubscriptKind::PEP593AnnotatedSubscript => {
|
||||
// First argument is a type (including forward references); the rest are
|
||||
// arbitrary Python objects.
|
||||
self.visit_expr(value);
|
||||
if let ExprKind::Tuple { elts, ctx } = &slice.node {
|
||||
if let Some(expr) = elts.first() {
|
||||
self.visit_expr(expr);
|
||||
self.in_annotation = false;
|
||||
for expr in elts.iter().skip(1) {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.in_annotation = true;
|
||||
// Only allow annotations in `ExprContext::Load`. If we have, e.g.,
|
||||
// `obj["foo"]["bar"]`, we need to avoid treating the `obj["foo"]`
|
||||
// portion as an annotation, despite having `ExprContext::Load`. Thus, we track
|
||||
// the `ExprContext` at the top-level.
|
||||
let prev_in_subscript = self.in_subscript;
|
||||
if self.in_subscript {
|
||||
visitor::walk_expr(self, expr);
|
||||
} else if matches!(ctx, ExprContext::Store | ExprContext::Del) {
|
||||
self.in_subscript = true;
|
||||
visitor::walk_expr(self, expr);
|
||||
} else {
|
||||
self.in_subscript = true;
|
||||
match typing::match_annotated_subscript(value, &self.from_imports) {
|
||||
Some(subscript) => {
|
||||
match subscript {
|
||||
// Ex) Optional[int]
|
||||
SubscriptKind::AnnotatedSubscript => {
|
||||
self.visit_expr(value);
|
||||
self.visit_annotation(slice);
|
||||
self.visit_expr_context(ctx);
|
||||
}
|
||||
} else {
|
||||
error!("Found non-ExprKind::Tuple argument to PEP 593 Annotation.")
|
||||
// Ex) Annotated[int, "Hello, world!"]
|
||||
SubscriptKind::PEP593AnnotatedSubscript => {
|
||||
// First argument is a type (including forward references); the
|
||||
// rest are arbitrary Python
|
||||
// objects.
|
||||
self.visit_expr(value);
|
||||
if let ExprKind::Tuple { elts, ctx } = &slice.node {
|
||||
if let Some(expr) = elts.first() {
|
||||
self.visit_expr(expr);
|
||||
self.in_annotation = false;
|
||||
for expr in elts.iter().skip(1) {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.in_annotation = true;
|
||||
self.visit_expr_context(ctx);
|
||||
}
|
||||
} else {
|
||||
error!(
|
||||
"Found non-ExprKind::Tuple argument to PEP 593 \
|
||||
Annotation."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => visitor::walk_expr(self, expr),
|
||||
None => visitor::walk_expr(self, expr),
|
||||
}
|
||||
}
|
||||
self.in_subscript = prev_in_subscript;
|
||||
}
|
||||
_ => visitor::walk_expr(self, expr),
|
||||
}
|
||||
@@ -1349,8 +1668,15 @@ where
|
||||
self.checks
|
||||
.extend(pyflakes::checks::duplicate_arguments(arguments));
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B006) {
|
||||
flake8_bugbear::plugins::mutable_argument_default(self, arguments)
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B008) {
|
||||
flake8_bugbear::plugins::function_call_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);
|
||||
}
|
||||
@@ -1369,7 +1695,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 {
|
||||
@@ -1411,8 +1738,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
|
||||
@@ -1522,7 +1850,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) => {
|
||||
@@ -1652,6 +1981,30 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N806) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::non_lowercase_variable_in_function(current, expr, id)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N815) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::mixed_case_variable_in_class_scope(current, expr, id)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N816) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::mixed_case_variable_in_global_scope(current, expr, id)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(parent.node, StmtKind::AnnAssign { value: None, .. }) {
|
||||
self.add_binding(
|
||||
id.to_string(),
|
||||
@@ -1946,8 +2299,7 @@ impl<'a> Checker<'a> {
|
||||
let child = self.parents[defined_by];
|
||||
let parent = defined_in.map(|defined_in| self.parents[defined_in]);
|
||||
|
||||
let fix = if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply)
|
||||
{
|
||||
let fix = if self.patch() {
|
||||
let deleted: Vec<&Stmt> = self
|
||||
.deletions
|
||||
.iter()
|
||||
@@ -1959,7 +2311,7 @@ impl<'a> Checker<'a> {
|
||||
ImportKind::ImportFrom => pyflakes::fixes::remove_unused_import_froms,
|
||||
};
|
||||
|
||||
match removal_fn(&mut self.locator, &full_names, child, parent, &deleted) {
|
||||
match removal_fn(self.locator, &full_names, child, parent, &deleted) {
|
||||
Ok(fix) => Some(fix),
|
||||
Err(e) => {
|
||||
error!("Failed to fix unused imports: {}", e);
|
||||
@@ -1970,15 +2322,27 @@ impl<'a> Checker<'a> {
|
||||
None
|
||||
};
|
||||
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedImport(full_names.into_iter().map(String::from).collect()),
|
||||
self.locate_check(Range::from_located(child)),
|
||||
);
|
||||
if let Some(fix) = fix {
|
||||
check.amend(fix);
|
||||
if self.path.ends_with("__init__.py") {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::UnusedImport(
|
||||
full_names.into_iter().map(String::from).collect(),
|
||||
true,
|
||||
),
|
||||
self.locate_check(Range::from_located(child)),
|
||||
));
|
||||
} else {
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedImport(
|
||||
full_names.into_iter().map(String::from).collect(),
|
||||
false,
|
||||
),
|
||||
self.locate_check(Range::from_located(child)),
|
||||
);
|
||||
if let Some(fix) = fix {
|
||||
check.amend(fix);
|
||||
}
|
||||
self.checks.push(check);
|
||||
}
|
||||
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2107,12 +2471,12 @@ impl<'a> Checker<'a> {
|
||||
|
||||
pub fn check_ast(
|
||||
python_ast: &Suite,
|
||||
contents: &str,
|
||||
locator: &SourceCodeLocator,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
path: &Path,
|
||||
) -> Vec<Check> {
|
||||
let mut checker = Checker::new(settings, autofix, path, contents);
|
||||
let mut checker = Checker::new(settings, autofix, path, locator);
|
||||
checker.push_scope(Scope::new(ScopeKind::Module));
|
||||
checker.bind_builtins();
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
//! Lint rules based on checking raw physical lines.
|
||||
|
||||
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;
|
||||
@@ -66,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)
|
||||
}
|
||||
@@ -92,13 +93,13 @@ pub fn check_lines(
|
||||
let check = Check::new(
|
||||
CheckKind::LineTooLong(line_length, settings.line_length),
|
||||
Range {
|
||||
location: Location::new(lineno + 1, 1),
|
||||
end_location: Location::new(lineno + 1, line_length + 1),
|
||||
location: Location::new(lineno + 1, 0),
|
||||
end_location: Location::new(lineno + 1, line_length),
|
||||
},
|
||||
);
|
||||
|
||||
match noqa {
|
||||
(Directive::All(_, _), matches) => {
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
}
|
||||
(Directive::Codes(_, _, codes), matches) => {
|
||||
@@ -116,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
|
||||
@@ -138,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) => {
|
||||
@@ -162,14 +163,14 @@ pub fn check_lines(
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedNOQA(None),
|
||||
Range {
|
||||
location: Location::new(row + 1, start + 1),
|
||||
end_location: Location::new(row + 1, end + 1),
|
||||
location: Location::new(row + 1, start),
|
||||
end_location: Location::new(row + 1, end),
|
||||
},
|
||||
);
|
||||
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if autofix.patch() {
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(row + 1, start + 1),
|
||||
Location::new(row + 1, lines[row].chars().count() + 1),
|
||||
Location::new(row + 1, start),
|
||||
Location::new(row + 1, lines[row].chars().count()),
|
||||
));
|
||||
}
|
||||
line_checks.push(check);
|
||||
@@ -190,21 +191,21 @@ pub fn check_lines(
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedNOQA(Some(invalid_codes)),
|
||||
Range {
|
||||
location: Location::new(row + 1, start + 1),
|
||||
end_location: Location::new(row + 1, end + 1),
|
||||
location: Location::new(row + 1, start),
|
||||
end_location: Location::new(row + 1, end),
|
||||
},
|
||||
);
|
||||
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if autofix.patch() {
|
||||
if valid_codes.is_empty() {
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(row + 1, start + 1),
|
||||
Location::new(row + 1, lines[row].chars().count() + 1),
|
||||
Location::new(row + 1, start),
|
||||
Location::new(row + 1, lines[row].chars().count()),
|
||||
));
|
||||
} else {
|
||||
check.amend(Fix::replacement(
|
||||
format!(" # noqa: {}", valid_codes.join(", ")),
|
||||
Location::new(row + 1, start + 1),
|
||||
Location::new(row + 1, lines[row].chars().count() + 1),
|
||||
Location::new(row + 1, start),
|
||||
Location::new(row + 1, lines[row].chars().count()),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -225,11 +226,10 @@ pub fn check_lines(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::check_lines;
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::settings;
|
||||
|
||||
use super::check_lines;
|
||||
use crate::settings::Settings;
|
||||
|
||||
#[test]
|
||||
fn e501_non_ascii_char() {
|
||||
@@ -241,9 +241,9 @@ mod tests {
|
||||
&mut checks,
|
||||
line,
|
||||
&noqa_line_for,
|
||||
&settings::Settings {
|
||||
&Settings {
|
||||
line_length,
|
||||
..settings::Settings::for_rule(CheckCode::E501)
|
||||
..Settings::for_rule(CheckCode::E501)
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
);
|
||||
|
||||
77
src/check_tokens.rs
Normal file
77
src/check_tokens.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
//! Lint rules based on token traversal.
|
||||
|
||||
use rustpython_parser::lexer::{LexResult, Tok};
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::lex::docstring_detection::StateMachine;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::{flake8_quotes, pycodestyle, rules, Settings};
|
||||
|
||||
pub fn check_tokens(
|
||||
checks: &mut Vec<Check>,
|
||||
locator: &SourceCodeLocator,
|
||||
tokens: &[LexResult],
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) {
|
||||
let enforce_ambiguous_unicode_character = settings.enabled.contains(&CheckCode::RUF001)
|
||||
|| settings.enabled.contains(&CheckCode::RUF002);
|
||||
let enforce_quotes = settings.enabled.contains(&CheckCode::Q000)
|
||||
|| settings.enabled.contains(&CheckCode::Q001)
|
||||
|| settings.enabled.contains(&CheckCode::Q002)
|
||||
|| settings.enabled.contains(&CheckCode::Q003);
|
||||
let enforce_invalid_escape_sequence = settings.enabled.contains(&CheckCode::W605);
|
||||
|
||||
let mut state_machine: StateMachine = Default::default();
|
||||
for (start, tok, end) in tokens.iter().flatten() {
|
||||
let is_docstring = if enforce_ambiguous_unicode_character || enforce_quotes {
|
||||
state_machine.consume(tok)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// RUF001, RUF002
|
||||
if enforce_ambiguous_unicode_character {
|
||||
if matches!(tok, Tok::String { .. }) {
|
||||
for check in rules::checks::ambiguous_unicode_character(
|
||||
locator,
|
||||
start,
|
||||
end,
|
||||
is_docstring,
|
||||
autofix.patch(),
|
||||
) {
|
||||
if settings.enabled.contains(check.kind.code()) {
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flake8-quotes
|
||||
if enforce_quotes {
|
||||
if matches!(tok, Tok::String { .. }) {
|
||||
if let Some(check) = flake8_quotes::checks::quotes(
|
||||
locator,
|
||||
start,
|
||||
end,
|
||||
is_docstring,
|
||||
&settings.flake8_quotes,
|
||||
) {
|
||||
if settings.enabled.contains(check.kind.code()) {
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// W605
|
||||
if enforce_invalid_escape_sequence {
|
||||
if matches!(tok, Tok::String { .. }) {
|
||||
checks.extend(pycodestyle::checks::invalid_escape_sequence(
|
||||
locator, start, end,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
421
src/checks.rs
421
src/checks.rs
@@ -7,6 +7,7 @@ use strum_macros::{AsRefStr, EnumIter, EnumString};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::flake8_quotes::settings::Quote;
|
||||
use crate::pyupgrade::types::Primitive;
|
||||
|
||||
#[derive(
|
||||
@@ -41,6 +42,7 @@ pub enum CheckCode {
|
||||
E999,
|
||||
// pycodestyle warnings
|
||||
W292,
|
||||
W605,
|
||||
// pyflakes
|
||||
F401,
|
||||
F402,
|
||||
@@ -75,8 +77,18 @@ pub enum CheckCode {
|
||||
A002,
|
||||
A003,
|
||||
// flake8-bugbear
|
||||
B002,
|
||||
B003,
|
||||
B006,
|
||||
B007,
|
||||
B008,
|
||||
B011,
|
||||
B013,
|
||||
B014,
|
||||
B015,
|
||||
B016,
|
||||
B017,
|
||||
B018,
|
||||
B025,
|
||||
// flake8-comprehensions
|
||||
C400,
|
||||
@@ -98,6 +110,11 @@ pub enum CheckCode {
|
||||
// flake8-print
|
||||
T201,
|
||||
T203,
|
||||
// flake8-quotes
|
||||
Q000,
|
||||
Q001,
|
||||
Q002,
|
||||
Q003,
|
||||
// pyupgrade
|
||||
U001,
|
||||
U002,
|
||||
@@ -158,6 +175,19 @@ pub enum CheckCode {
|
||||
N803,
|
||||
N804,
|
||||
N805,
|
||||
N806,
|
||||
N807,
|
||||
N811,
|
||||
N812,
|
||||
N813,
|
||||
N814,
|
||||
N815,
|
||||
N816,
|
||||
N817,
|
||||
N818,
|
||||
// Ruff
|
||||
RUF001,
|
||||
RUF002,
|
||||
// Meta
|
||||
M001,
|
||||
}
|
||||
@@ -165,7 +195,8 @@ pub enum CheckCode {
|
||||
#[derive(EnumIter, Debug, PartialEq, Eq)]
|
||||
pub enum CheckCategory {
|
||||
Pyflakes,
|
||||
Pycodestyle,
|
||||
PycodestyleError,
|
||||
PycodestyleWarning,
|
||||
Pydocstyle,
|
||||
Pyupgrade,
|
||||
PEP8Naming,
|
||||
@@ -173,21 +204,26 @@ pub enum CheckCategory {
|
||||
Flake8Bugbear,
|
||||
Flake8Builtins,
|
||||
Flake8Print,
|
||||
Flake8Quotes,
|
||||
Ruff,
|
||||
Meta,
|
||||
}
|
||||
|
||||
impl CheckCategory {
|
||||
pub fn title(&self) -> &'static str {
|
||||
match self {
|
||||
CheckCategory::Pycodestyle => "pycodestyle",
|
||||
CheckCategory::PycodestyleError => "pycodestyle (error)",
|
||||
CheckCategory::PycodestyleWarning => "pycodestyle (warning)",
|
||||
CheckCategory::Pyflakes => "Pyflakes",
|
||||
CheckCategory::Flake8Builtins => "flake8-builtins",
|
||||
CheckCategory::Flake8Bugbear => "flake8-bugbear",
|
||||
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
|
||||
CheckCategory::Flake8Print => "flake8-print",
|
||||
CheckCategory::Flake8Quotes => "flake8-quotes",
|
||||
CheckCategory::Pyupgrade => "pyupgrade",
|
||||
CheckCategory::Pydocstyle => "pydocstyle",
|
||||
CheckCategory::PEP8Naming => "pep8-naming",
|
||||
CheckCategory::Ruff => "Ruff-specific rules",
|
||||
CheckCategory::Meta => "Meta rules",
|
||||
}
|
||||
}
|
||||
@@ -196,8 +232,9 @@ impl CheckCategory {
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub enum LintSource {
|
||||
AST,
|
||||
Lines,
|
||||
FileSystem,
|
||||
Lines,
|
||||
Tokens,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -225,6 +262,7 @@ pub enum CheckKind {
|
||||
TypeComparison,
|
||||
// pycodestyle warnings
|
||||
NoNewLineAtEndOfFile,
|
||||
InvalidEscapeSequence(char),
|
||||
// pyflakes
|
||||
AssertTuple,
|
||||
BreakOutsideLoop,
|
||||
@@ -251,7 +289,7 @@ pub enum CheckKind {
|
||||
UndefinedExport(String),
|
||||
UndefinedLocal(String),
|
||||
UndefinedName(String),
|
||||
UnusedImport(Vec<String>),
|
||||
UnusedImport(Vec<String>, bool),
|
||||
UnusedVariable(String),
|
||||
YieldOutsideFunction,
|
||||
// flake8-builtins
|
||||
@@ -259,8 +297,18 @@ pub enum CheckKind {
|
||||
BuiltinArgumentShadowing(String),
|
||||
BuiltinAttributeShadowing(String),
|
||||
// flake8-bugbear
|
||||
UnaryPrefixIncrement,
|
||||
AssignmentToOsEnviron,
|
||||
MutableArgumentDefault,
|
||||
UnusedLoopControlVariable(String),
|
||||
FunctionCallArgumentDefault,
|
||||
DoNotAssertFalse,
|
||||
RedundantTupleInExceptionHandler(String),
|
||||
DuplicateHandlerException(Vec<String>),
|
||||
UselessComparison,
|
||||
CannotRaiseLiteral,
|
||||
NoAssertRaisesException,
|
||||
UselessExpression,
|
||||
DuplicateTryBlockException(String),
|
||||
// flake8-comprehensions
|
||||
UnnecessaryGeneratorList,
|
||||
@@ -282,6 +330,11 @@ pub enum CheckKind {
|
||||
// flake8-print
|
||||
PrintFound,
|
||||
PPrintFound,
|
||||
// flake8-quotes
|
||||
BadQuotesInlineString(Quote),
|
||||
BadQuotesMultilineString(Quote),
|
||||
BadQuotesDocstring(Quote),
|
||||
AvoidQuoteEscape,
|
||||
// pyupgrade
|
||||
TypeOfPrimitive(Primitive),
|
||||
UnnecessaryAbspath,
|
||||
@@ -342,15 +395,36 @@ pub enum CheckKind {
|
||||
InvalidArgumentName(String),
|
||||
InvalidFirstArgumentNameForClassMethod,
|
||||
InvalidFirstArgumentNameForMethod,
|
||||
NonLowercaseVariableInFunction(String),
|
||||
DunderFunctionName,
|
||||
ConstantImportedAsNonConstant(String, String),
|
||||
LowercaseImportedAsNonLowercase(String, String),
|
||||
CamelcaseImportedAsLowercase(String, String),
|
||||
CamelcaseImportedAsConstant(String, String),
|
||||
MixedCaseVariableInClassScope(String),
|
||||
MixedCaseVariableInGlobalScope(String),
|
||||
CamelcaseImportedAsAcronym(String, String),
|
||||
ErrorSuffixOnExceptionName(String),
|
||||
// Ruff
|
||||
AmbiguousUnicodeCharacterString(char, char),
|
||||
AmbiguousUnicodeCharacterDocstring(char, char),
|
||||
// Meta
|
||||
UnusedNOQA(Option<Vec<String>>),
|
||||
}
|
||||
|
||||
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,
|
||||
CheckCode::Q000
|
||||
| CheckCode::Q001
|
||||
| CheckCode::Q002
|
||||
| CheckCode::Q003
|
||||
| CheckCode::W605
|
||||
| CheckCode::RUF001
|
||||
| CheckCode::RUF002 => &LintSource::Tokens,
|
||||
CheckCode::E902 => &LintSource::FileSystem,
|
||||
_ => &LintSource::AST,
|
||||
}
|
||||
@@ -376,8 +450,9 @@ impl CheckCode {
|
||||
CheckCode::E999 => CheckKind::SyntaxError("`...`".to_string()),
|
||||
// pycodestyle warnings
|
||||
CheckCode::W292 => CheckKind::NoNewLineAtEndOfFile,
|
||||
CheckCode::W605 => CheckKind::InvalidEscapeSequence('c'),
|
||||
// pyflakes
|
||||
CheckCode::F401 => CheckKind::UnusedImport(vec!["...".to_string()]),
|
||||
CheckCode::F401 => CheckKind::UnusedImport(vec!["...".to_string()], false),
|
||||
CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
|
||||
CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()),
|
||||
CheckCode::F404 => CheckKind::LateFutureImport,
|
||||
@@ -412,8 +487,20 @@ impl CheckCode {
|
||||
CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
|
||||
CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
|
||||
// flake8-bugbear
|
||||
CheckCode::B002 => CheckKind::UnaryPrefixIncrement,
|
||||
CheckCode::B003 => CheckKind::AssignmentToOsEnviron,
|
||||
CheckCode::B006 => CheckKind::MutableArgumentDefault,
|
||||
CheckCode::B007 => CheckKind::UnusedLoopControlVariable("i".to_string()),
|
||||
CheckCode::B008 => CheckKind::FunctionCallArgumentDefault,
|
||||
CheckCode::B011 => CheckKind::DoNotAssertFalse,
|
||||
CheckCode::B013 => {
|
||||
CheckKind::RedundantTupleInExceptionHandler("ValueError".to_string())
|
||||
}
|
||||
CheckCode::B014 => CheckKind::DuplicateHandlerException(vec!["ValueError".to_string()]),
|
||||
CheckCode::B015 => CheckKind::UselessComparison,
|
||||
CheckCode::B016 => CheckKind::CannotRaiseLiteral,
|
||||
CheckCode::B017 => CheckKind::NoAssertRaisesException,
|
||||
CheckCode::B018 => CheckKind::UselessExpression,
|
||||
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
|
||||
// flake8-comprehensions
|
||||
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
|
||||
@@ -448,6 +535,11 @@ impl CheckCode {
|
||||
// flake8-print
|
||||
CheckCode::T201 => CheckKind::PrintFound,
|
||||
CheckCode::T203 => CheckKind::PPrintFound,
|
||||
// flake8-quotes
|
||||
CheckCode::Q000 => CheckKind::BadQuotesInlineString(Quote::Double),
|
||||
CheckCode::Q001 => CheckKind::BadQuotesMultilineString(Quote::Double),
|
||||
CheckCode::Q002 => CheckKind::BadQuotesDocstring(Quote::Double),
|
||||
CheckCode::Q003 => CheckKind::AvoidQuoteEscape,
|
||||
// pyupgrade
|
||||
CheckCode::U001 => CheckKind::UselessMetaclassType,
|
||||
CheckCode::U002 => CheckKind::UnnecessaryAbspath,
|
||||
@@ -517,6 +609,29 @@ impl CheckCode {
|
||||
CheckCode::N803 => CheckKind::InvalidArgumentName("...".to_string()),
|
||||
CheckCode::N804 => CheckKind::InvalidFirstArgumentNameForClassMethod,
|
||||
CheckCode::N805 => CheckKind::InvalidFirstArgumentNameForMethod,
|
||||
CheckCode::N806 => CheckKind::NonLowercaseVariableInFunction("...".to_string()),
|
||||
CheckCode::N807 => CheckKind::DunderFunctionName,
|
||||
CheckCode::N811 => {
|
||||
CheckKind::ConstantImportedAsNonConstant("...".to_string(), "...".to_string())
|
||||
}
|
||||
CheckCode::N812 => {
|
||||
CheckKind::LowercaseImportedAsNonLowercase("...".to_string(), "...".to_string())
|
||||
}
|
||||
CheckCode::N813 => {
|
||||
CheckKind::CamelcaseImportedAsLowercase("...".to_string(), "...".to_string())
|
||||
}
|
||||
CheckCode::N814 => {
|
||||
CheckKind::CamelcaseImportedAsConstant("...".to_string(), "...".to_string())
|
||||
}
|
||||
CheckCode::N815 => CheckKind::MixedCaseVariableInClassScope("mixedCase".to_string()),
|
||||
CheckCode::N816 => CheckKind::MixedCaseVariableInGlobalScope("mixedCase".to_string()),
|
||||
CheckCode::N817 => {
|
||||
CheckKind::CamelcaseImportedAsAcronym("...".to_string(), "...".to_string())
|
||||
}
|
||||
CheckCode::N818 => CheckKind::ErrorSuffixOnExceptionName("...".to_string()),
|
||||
// Ruff
|
||||
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
|
||||
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
|
||||
// Meta
|
||||
CheckCode::M001 => CheckKind::UnusedNOQA(None),
|
||||
}
|
||||
@@ -524,21 +639,22 @@ impl CheckCode {
|
||||
|
||||
pub fn category(&self) -> CheckCategory {
|
||||
match self {
|
||||
CheckCode::E402 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E501 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E711 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E712 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E713 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E714 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E721 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E722 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E731 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E741 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E742 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E743 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E902 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E999 => CheckCategory::Pycodestyle,
|
||||
CheckCode::W292 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E402 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E501 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E711 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E712 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E713 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E714 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E721 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E722 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E731 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E741 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E742 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E743 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E902 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E999 => CheckCategory::PycodestyleError,
|
||||
CheckCode::W292 => CheckCategory::PycodestyleWarning,
|
||||
CheckCode::W605 => CheckCategory::PycodestyleWarning,
|
||||
CheckCode::F401 => CheckCategory::Pyflakes,
|
||||
CheckCode::F402 => CheckCategory::Pyflakes,
|
||||
CheckCode::F403 => CheckCategory::Pyflakes,
|
||||
@@ -570,8 +686,18 @@ impl CheckCode {
|
||||
CheckCode::A001 => CheckCategory::Flake8Builtins,
|
||||
CheckCode::A002 => CheckCategory::Flake8Builtins,
|
||||
CheckCode::A003 => CheckCategory::Flake8Builtins,
|
||||
CheckCode::B002 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B003 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B006 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B007 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B008 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B011 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B013 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B014 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B015 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B016 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B017 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B018 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B025 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::C400 => CheckCategory::Flake8Comprehensions,
|
||||
CheckCode::C401 => CheckCategory::Flake8Comprehensions,
|
||||
@@ -591,6 +717,10 @@ impl CheckCode {
|
||||
CheckCode::C417 => CheckCategory::Flake8Comprehensions,
|
||||
CheckCode::T201 => CheckCategory::Flake8Print,
|
||||
CheckCode::T203 => CheckCategory::Flake8Print,
|
||||
CheckCode::Q000 => CheckCategory::Flake8Quotes,
|
||||
CheckCode::Q001 => CheckCategory::Flake8Quotes,
|
||||
CheckCode::Q002 => CheckCategory::Flake8Quotes,
|
||||
CheckCode::Q003 => CheckCategory::Flake8Quotes,
|
||||
CheckCode::U001 => CheckCategory::Pyupgrade,
|
||||
CheckCode::U002 => CheckCategory::Pyupgrade,
|
||||
CheckCode::U003 => CheckCategory::Pyupgrade,
|
||||
@@ -648,6 +778,18 @@ impl CheckCode {
|
||||
CheckCode::N803 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N804 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N805 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N806 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N807 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N811 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N812 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N813 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N814 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N815 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N816 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N817 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N818 => CheckCategory::PEP8Naming,
|
||||
CheckCode::RUF001 => CheckCategory::Ruff,
|
||||
CheckCode::RUF002 => CheckCategory::Ruff,
|
||||
CheckCode::M001 => CheckCategory::Meta,
|
||||
}
|
||||
}
|
||||
@@ -673,14 +815,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,
|
||||
@@ -691,24 +833,35 @@ 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
|
||||
CheckKind::NoNewLineAtEndOfFile => &CheckCode::W292,
|
||||
CheckKind::InvalidEscapeSequence(_) => &CheckCode::W605,
|
||||
// flake8-builtins
|
||||
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
|
||||
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
|
||||
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
|
||||
// flake8-bugbear
|
||||
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
|
||||
CheckKind::AssignmentToOsEnviron => &CheckCode::B003,
|
||||
CheckKind::MutableArgumentDefault => &CheckCode::B006,
|
||||
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
|
||||
CheckKind::FunctionCallArgumentDefault => &CheckCode::B008,
|
||||
CheckKind::DoNotAssertFalse => &CheckCode::B011,
|
||||
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
|
||||
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
|
||||
CheckKind::UselessComparison => &CheckCode::B015,
|
||||
CheckKind::CannotRaiseLiteral => &CheckCode::B016,
|
||||
CheckKind::NoAssertRaisesException => &CheckCode::B017,
|
||||
CheckKind::UselessExpression => &CheckCode::B018,
|
||||
CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
|
||||
// flake8-comprehensions
|
||||
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
|
||||
@@ -730,11 +883,16 @@ impl CheckKind {
|
||||
// flake8-print
|
||||
CheckKind::PrintFound => &CheckCode::T201,
|
||||
CheckKind::PPrintFound => &CheckCode::T203,
|
||||
// flake8-quotes
|
||||
CheckKind::BadQuotesInlineString(_) => &CheckCode::Q000,
|
||||
CheckKind::BadQuotesMultilineString(_) => &CheckCode::Q001,
|
||||
CheckKind::BadQuotesDocstring(_) => &CheckCode::Q002,
|
||||
CheckKind::AvoidQuoteEscape => &CheckCode::Q003,
|
||||
// pyupgrade
|
||||
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,
|
||||
@@ -790,6 +948,19 @@ impl CheckKind {
|
||||
CheckKind::InvalidArgumentName(_) => &CheckCode::N803,
|
||||
CheckKind::InvalidFirstArgumentNameForClassMethod => &CheckCode::N804,
|
||||
CheckKind::InvalidFirstArgumentNameForMethod => &CheckCode::N805,
|
||||
CheckKind::NonLowercaseVariableInFunction(..) => &CheckCode::N806,
|
||||
CheckKind::DunderFunctionName => &CheckCode::N807,
|
||||
CheckKind::ConstantImportedAsNonConstant(..) => &CheckCode::N811,
|
||||
CheckKind::LowercaseImportedAsNonLowercase(..) => &CheckCode::N812,
|
||||
CheckKind::CamelcaseImportedAsLowercase(..) => &CheckCode::N813,
|
||||
CheckKind::CamelcaseImportedAsConstant(..) => &CheckCode::N814,
|
||||
CheckKind::MixedCaseVariableInClassScope(..) => &CheckCode::N815,
|
||||
CheckKind::MixedCaseVariableInGlobalScope(..) => &CheckCode::N816,
|
||||
CheckKind::CamelcaseImportedAsAcronym(..) => &CheckCode::N817,
|
||||
CheckKind::ErrorSuffixOnExceptionName(..) => &CheckCode::N818,
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
|
||||
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
|
||||
// Meta
|
||||
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
|
||||
}
|
||||
@@ -916,18 +1087,25 @@ impl CheckKind {
|
||||
CheckKind::UndefinedName(name) => {
|
||||
format!("Undefined name `{name}`")
|
||||
}
|
||||
CheckKind::UnusedImport(names) => {
|
||||
CheckKind::UnusedImport(names, in_init_py) => {
|
||||
let names = names.iter().map(|name| format!("`{name}`")).join(", ");
|
||||
format!("{names} imported but unused")
|
||||
if *in_init_py {
|
||||
format!("{names} imported but unused and missing from `__all__`")
|
||||
} else {
|
||||
format!("{names} imported but unused")
|
||||
}
|
||||
}
|
||||
CheckKind::UnusedVariable(name) => {
|
||||
format!("Local variable `{name}` is assigned to but never used")
|
||||
}
|
||||
CheckKind::YieldOutsideFunction => {
|
||||
"`yield` or `yield from` statement outside of a function/method".to_string()
|
||||
"`yield` or `yield from` statement outside of a function".to_string()
|
||||
}
|
||||
// pycodestyle warnings
|
||||
CheckKind::NoNewLineAtEndOfFile => "No newline at end of file".to_string(),
|
||||
CheckKind::InvalidEscapeSequence(char) => {
|
||||
format!("Invalid escape sequence: '\\{char}'")
|
||||
}
|
||||
// flake8-builtins
|
||||
CheckKind::BuiltinVariableShadowing(name) => {
|
||||
format!("Variable `{name}` is shadowing a python builtin")
|
||||
@@ -939,10 +1117,39 @@ impl CheckKind {
|
||||
format!("Class attribute `{name}` is shadowing a python builtin")
|
||||
}
|
||||
// flake8-bugbear
|
||||
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::AssignmentToOsEnviron => {
|
||||
"Assigning to `os.environ` doesn't clear the environment.".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::FunctionCallArgumentDefault => {
|
||||
"Do not perform function calls in argument defaults.".to_string()
|
||||
}
|
||||
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},):`."
|
||||
)
|
||||
}
|
||||
CheckKind::UselessComparison => "Pointless comparison. This comparison does nothing \
|
||||
but waste CPU instructions. Either prepend `assert` \
|
||||
or remove it."
|
||||
.to_string(),
|
||||
CheckKind::CannotRaiseLiteral => "Cannot raise a literal. Did you intend to return it \
|
||||
or raise an Exception?"
|
||||
.to_string(),
|
||||
CheckKind::DuplicateHandlerException(names) => {
|
||||
if names.len() == 1 {
|
||||
let name = &names[0];
|
||||
@@ -952,6 +1159,16 @@ impl CheckKind {
|
||||
format!("Exception handler with duplicate exceptions: {names}")
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
CheckKind::UselessExpression => {
|
||||
"Found useless expression. Either assign it to a variable or remove it.".to_string()
|
||||
}
|
||||
CheckKind::DuplicateTryBlockException(name) => {
|
||||
format!("try-except block with duplicate exception `{name}`")
|
||||
}
|
||||
@@ -983,22 +1200,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)"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1015,7 +1236,7 @@ impl CheckKind {
|
||||
format!("Unnecessary subscript reversal of iterable within `{func}()`")
|
||||
}
|
||||
CheckKind::UnnecessaryComprehension(obj_type) => {
|
||||
format!(" Unnecessary `{obj_type}` comprehension (rewrite using `{obj_type}()`)")
|
||||
format!("Unnecessary `{obj_type}` comprehension (rewrite using `{obj_type}()`)")
|
||||
}
|
||||
CheckKind::UnnecessaryMap(obj_type) => {
|
||||
if obj_type == "generator" {
|
||||
@@ -1027,6 +1248,30 @@ impl CheckKind {
|
||||
// flake8-print
|
||||
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::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::AvoidQuoteEscape => {
|
||||
"Change outer quotes to avoid escaping inner quotes".to_string()
|
||||
}
|
||||
// pyupgrade
|
||||
CheckKind::TypeOfPrimitive(primitive) => {
|
||||
format!("Use `{}` instead of `type(...)`", primitive.builtin())
|
||||
@@ -1036,7 +1281,7 @@ impl CheckKind {
|
||||
}
|
||||
CheckKind::UselessMetaclassType => "`__metaclass__ = type` is implied".to_string(),
|
||||
CheckKind::DeprecatedUnittestAlias(alias, target) => {
|
||||
format!("`{}` is deprecated, use `{}` instead", alias, target)
|
||||
format!("`{alias}` is deprecated, use `{target}` instead")
|
||||
}
|
||||
CheckKind::UselessObjectInheritance(name) => {
|
||||
format!("Class `{name}` inherits from object")
|
||||
@@ -1065,10 +1310,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()
|
||||
}
|
||||
@@ -1080,7 +1324,7 @@ impl CheckKind {
|
||||
"Multi-line docstring summary should start at the second line".to_string()
|
||||
}
|
||||
CheckKind::NoSignature => {
|
||||
"First line should not be the function's 'signature'".to_string()
|
||||
"First line should not be the function's signature".to_string()
|
||||
}
|
||||
CheckKind::NoBlankLineBeforeFunction(num_lines) => {
|
||||
format!("No blank lines allowed before function docstring (found {num_lines})")
|
||||
@@ -1106,10 +1350,10 @@ impl CheckKind {
|
||||
CheckKind::PublicNestedClass => "Missing docstring in public nested class".to_string(),
|
||||
CheckKind::PublicInit => "Missing docstring in `__init__`".to_string(),
|
||||
CheckKind::NoThisPrefix => {
|
||||
"First word of the docstring should not be `This`".to_string()
|
||||
"First word of the docstring should not be 'This'".to_string()
|
||||
}
|
||||
CheckKind::SkipDocstring => {
|
||||
"Function decorated with @overload shouldn't contain a docstring".to_string()
|
||||
"Function decorated with `@overload` shouldn't contain a docstring".to_string()
|
||||
}
|
||||
CheckKind::CapitalizeSectionName(name) => {
|
||||
format!("Section name should be properly capitalized (\"{name}\")")
|
||||
@@ -1130,7 +1374,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}\")")
|
||||
@@ -1180,6 +1427,49 @@ impl CheckKind {
|
||||
CheckKind::InvalidFirstArgumentNameForMethod => {
|
||||
"First argument of a method should be named `self`".to_string()
|
||||
}
|
||||
CheckKind::NonLowercaseVariableInFunction(name) => {
|
||||
format!("Variable `{name}` in function should be lowercase")
|
||||
}
|
||||
CheckKind::DunderFunctionName => {
|
||||
"Function name should not start and end with `__`".to_string()
|
||||
}
|
||||
CheckKind::ConstantImportedAsNonConstant(name, asname) => {
|
||||
format!("Constant `{name}` imported as non-constant `{asname}`")
|
||||
}
|
||||
CheckKind::LowercaseImportedAsNonLowercase(name, asname) => {
|
||||
format!("Lowercase `{name}` imported as non-lowercase `{asname}`")
|
||||
}
|
||||
CheckKind::CamelcaseImportedAsLowercase(name, asname) => {
|
||||
format!("Camelcase `{name}` imported as lowercase `{asname}`")
|
||||
}
|
||||
CheckKind::CamelcaseImportedAsConstant(name, asname) => {
|
||||
format!("Camelcase `{name}` imported as constant `{asname}`")
|
||||
}
|
||||
CheckKind::MixedCaseVariableInClassScope(name) => {
|
||||
format!("Variable `{name}` in class scope should not be mixedCase")
|
||||
}
|
||||
CheckKind::MixedCaseVariableInGlobalScope(name) => {
|
||||
format!("Variable `{name}` in global scope should not be mixedCase")
|
||||
}
|
||||
CheckKind::CamelcaseImportedAsAcronym(name, asname) => {
|
||||
format!("Camelcase `{name}` imported as acronym `{asname}`")
|
||||
}
|
||||
CheckKind::ErrorSuffixOnExceptionName(name) => {
|
||||
format!("Exception name `{name}` should be named with an Error suffix")
|
||||
}
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => {
|
||||
format!(
|
||||
"String contains ambiguous unicode character '{confusable}' (did you mean \
|
||||
'{representant}'?)"
|
||||
)
|
||||
}
|
||||
CheckKind::AmbiguousUnicodeCharacterDocstring(confusable, representant) => {
|
||||
format!(
|
||||
"Docstring contains ambiguous unicode character '{confusable}' (did you mean \
|
||||
'{representant}'?)"
|
||||
)
|
||||
}
|
||||
// Meta
|
||||
CheckKind::UnusedNOQA(codes) => match codes {
|
||||
None => "Unused `noqa` directive".to_string(),
|
||||
@@ -1200,11 +1490,30 @@ impl CheckKind {
|
||||
}
|
||||
}
|
||||
|
||||
/// The summary text for the check. Typically a truncated form of the body
|
||||
/// text.
|
||||
pub fn summary(&self) -> String {
|
||||
match self {
|
||||
CheckKind::UnaryPrefixIncrement => {
|
||||
"Python does not support the unary prefix increment.".to_string()
|
||||
}
|
||||
CheckKind::UnusedLoopControlVariable(name) => {
|
||||
format!("Loop control variable `{name}` not used within the loop body.")
|
||||
}
|
||||
CheckKind::NoAssertRaisesException => {
|
||||
"`assertRaises(Exception):` should be considered evil.".to_string()
|
||||
}
|
||||
_ => self.body(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the check kind is (potentially) fixable.
|
||||
pub fn fixable(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
CheckKind::BlankLineAfterLastSection(_)
|
||||
CheckKind::AmbiguousUnicodeCharacterString(_, _)
|
||||
| CheckKind::AmbiguousUnicodeCharacterDocstring(_, _)
|
||||
| CheckKind::BlankLineAfterLastSection(_)
|
||||
| CheckKind::BlankLineAfterSection(_)
|
||||
| CheckKind::BlankLineAfterSummary
|
||||
| CheckKind::BlankLineBeforeSection(_)
|
||||
@@ -1226,14 +1535,28 @@ impl CheckKind {
|
||||
| CheckKind::OneBlankLineBeforeClass(_)
|
||||
| CheckKind::PPrintFound
|
||||
| CheckKind::PrintFound
|
||||
| CheckKind::RaiseNotImplemented
|
||||
| CheckKind::SectionNameEndsInColon(_)
|
||||
| CheckKind::SectionNotOverIndented(_)
|
||||
| CheckKind::SectionUnderlineAfterName(_)
|
||||
| CheckKind::SectionUnderlineMatchesSectionLength(_)
|
||||
| CheckKind::SectionUnderlineNotOverIndented(_)
|
||||
| CheckKind::SuperCallWithParameters
|
||||
| CheckKind::TypeOfPrimitive(_)
|
||||
| CheckKind::UnnecessaryAbspath
|
||||
| CheckKind::UnusedImport(_)
|
||||
| CheckKind::UnnecessaryCollectionCall(_)
|
||||
| CheckKind::UnnecessaryComprehension(_)
|
||||
| CheckKind::UnnecessaryGeneratorDict
|
||||
| CheckKind::UnnecessaryGeneratorList
|
||||
| CheckKind::UnnecessaryGeneratorSet
|
||||
| CheckKind::UnnecessaryListCall
|
||||
| CheckKind::UnnecessaryListComprehensionSet
|
||||
| CheckKind::UnnecessaryLiteralDict(_)
|
||||
| CheckKind::UnnecessaryLiteralSet(_)
|
||||
| CheckKind::UnnecessaryLiteralWithinListCall(_)
|
||||
| CheckKind::UnnecessaryLiteralWithinTupleCall(_)
|
||||
| CheckKind::UnusedImport(_, false)
|
||||
| CheckKind::UnusedLoopControlVariable(_)
|
||||
| CheckKind::UnusedNOQA(_)
|
||||
| CheckKind::UsePEP585Annotation(_)
|
||||
| CheckKind::UsePEP604Annotation
|
||||
|
||||
1155
src/checks_gen.rs
Normal file
1155
src/checks_gen.rs
Normal file
File diff suppressed because it is too large
Load Diff
82
src/cli.rs
82
src/cli.rs
@@ -1,3 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -5,11 +6,11 @@ use clap::{command, Parser};
|
||||
use log::warn;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::logging::LogLevel;
|
||||
use crate::printer::SerializationFormat;
|
||||
use crate::pyproject::StrCheckCodePair;
|
||||
use crate::settings::PythonVersion;
|
||||
use crate::RawSettings;
|
||||
use crate::settings::configuration::Configuration;
|
||||
use crate::settings::types::{PatternPrefixPair, PythonVersion};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, about = "ruff: An extremely fast Python linter.")]
|
||||
@@ -21,11 +22,15 @@ pub struct Cli {
|
||||
#[arg(long)]
|
||||
pub config: Option<PathBuf>,
|
||||
/// Enable verbose logging.
|
||||
#[arg(short, long)]
|
||||
#[arg(short, long, group = "verbosity")]
|
||||
pub verbose: bool,
|
||||
/// Disable all logging (but still exit with status code "1" upon detecting errors).
|
||||
#[arg(short, long)]
|
||||
/// Only log errors.
|
||||
#[arg(short, long, group = "verbosity")]
|
||||
pub quiet: bool,
|
||||
/// 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.
|
||||
#[arg(short, long)]
|
||||
pub exit_zero: bool,
|
||||
@@ -40,25 +45,28 @@ pub struct Cli {
|
||||
pub no_cache: bool,
|
||||
/// List of error codes to enable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub select: Vec<CheckCode>,
|
||||
/// Like --select, but adds additional error codes on top of the selected ones.
|
||||
pub select: Vec<CheckCodePrefix>,
|
||||
/// Like --select, but adds additional error codes on top of the selected
|
||||
/// ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_select: Vec<CheckCode>,
|
||||
pub extend_select: Vec<CheckCodePrefix>,
|
||||
/// List of error codes to ignore.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub ignore: Vec<CheckCode>,
|
||||
/// Like --ignore, but adds additional error codes on top of the ignored ones.
|
||||
pub ignore: Vec<CheckCodePrefix>,
|
||||
/// Like --ignore, but adds additional error codes on top of the ignored
|
||||
/// ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_ignore: Vec<CheckCode>,
|
||||
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,
|
||||
@@ -86,6 +94,19 @@ pub struct Cli {
|
||||
pub stdin_filename: Option<String>,
|
||||
}
|
||||
|
||||
/// Map the CLI settings to a `LogLevel`.
|
||||
pub fn extract_log_level(cli: &Cli) -> LogLevel {
|
||||
if cli.silent {
|
||||
LogLevel::Silent
|
||||
} else if cli.quiet {
|
||||
LogLevel::Quiet
|
||||
} else if cli.verbose {
|
||||
LogLevel::Verbose
|
||||
} else {
|
||||
LogLevel::Default
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Warnable {
|
||||
Select,
|
||||
ExtendSelect,
|
||||
@@ -103,10 +124,10 @@ impl fmt::Display for Warnable {
|
||||
/// Warn the user if they attempt to enable a code that won't be respected.
|
||||
pub fn warn_on(
|
||||
flag: Warnable,
|
||||
codes: &[CheckCode],
|
||||
cli_ignore: &[CheckCode],
|
||||
cli_extend_ignore: &[CheckCode],
|
||||
pyproject_settings: &RawSettings,
|
||||
codes: &[CheckCodePrefix],
|
||||
cli_ignore: &[CheckCodePrefix],
|
||||
cli_extend_ignore: &[CheckCodePrefix],
|
||||
pyproject_configuration: &Configuration,
|
||||
pyproject_path: &Option<PathBuf>,
|
||||
) {
|
||||
for code in codes {
|
||||
@@ -114,7 +135,7 @@ pub fn warn_on(
|
||||
if cli_ignore.contains(code) {
|
||||
warn!("{code:?} was passed to {flag}, but ignored via --ignore")
|
||||
}
|
||||
} else if pyproject_settings.ignore.contains(code) {
|
||||
} else if pyproject_configuration.ignore.contains(code) {
|
||||
if let Some(path) = pyproject_path {
|
||||
warn!(
|
||||
"{code:?} was passed to {flag}, but ignored by the `ignore` field in {}",
|
||||
@@ -128,15 +149,32 @@ pub fn warn_on(
|
||||
if cli_extend_ignore.contains(code) {
|
||||
warn!("{code:?} was passed to {flag}, but ignored via --extend-ignore")
|
||||
}
|
||||
} else if pyproject_settings.extend_ignore.contains(code) {
|
||||
} else if pyproject_configuration.extend_ignore.contains(code) {
|
||||
if let Some(path) = pyproject_path {
|
||||
warn!(
|
||||
"{code:?} was passed to {flag}, but ignored by the `extend_ignore` field in {}",
|
||||
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
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user