Compare commits

..

2 Commits

Author SHA1 Message Date
Charlie Marsh
7479c943a4 Add warning 2024-05-26 14:48:54 -04:00
Charlie Marsh
e4303e2c68 Add warning 2024-05-26 14:48:51 -04:00
2547 changed files with 39254 additions and 115830 deletions

View File

@@ -1,28 +1,2 @@
[alias]
dev = "run --package ruff_dev --bin ruff_dev"
[target.'cfg(all())']
rustflags = [
# CLIPPY LINT SETTINGS
# This is a workaround to configure lints for the entire workspace, pending the ability to configure this via TOML.
# See: `https://github.com/rust-lang/cargo/issues/5034`
# `https://github.com/EmbarkStudios/rust-ecosystem/issues/22#issuecomment-947011395`
"-Dunsafe_code",
"-Wclippy::pedantic",
# Allowed pedantic lints
"-Wclippy::char_lit_as_u8",
"-Aclippy::collapsible_else_if",
"-Aclippy::collapsible_if",
"-Aclippy::implicit_hasher",
"-Aclippy::match_same_arms",
"-Aclippy::missing_errors_doc",
"-Aclippy::missing_panics_doc",
"-Aclippy::module_name_repetitions",
"-Aclippy::must_use_candidate",
"-Aclippy::similar_names",
"-Aclippy::too_many_lines",
# Disallowed restriction lints
"-Wclippy::print_stdout",
"-Wclippy::print_stderr",
"-Wclippy::dbg_macro",
]

6
.gitattributes vendored
View File

@@ -1,6 +0,0 @@
* text=auto eol=lf
crates/ruff/resources/test/fixtures/isort/line_ending_crlf.py text eol=crlf
crates/ruff/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf
ruff.schema.json linguist-generated=true text=auto eol=lf

View File

@@ -3,8 +3,8 @@ Thank you for taking the time to report an issue! We're glad to have you involve
If you're filing a bug report, please consider including the following information:
* A minimal code snippet that reproduces the bug.
* The command you invoked (e.g., `ruff /path/to/file.py --fix`), ideally including the `--isolated` flag.
* The current Ruff settings (any relevant sections from your `pyproject.toml`).
* The current Ruff version (`ruff --version`).
- A minimal code snippet that reproduces the bug.
- The command you invoked (e.g., `ruff /path/to/file.py --fix`), ideally including the `--isolated` flag.
- The current Ruff settings (any relevant sections from your `pyproject.toml`).
- The current Ruff version (`ruff --version`).
-->

19
.github/release.yml vendored
View File

@@ -1,19 +0,0 @@
# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes
changelog:
categories:
- title: Breaking Changes
labels:
- breaking
- title: Rules
labels:
- rule
- autofix
- title: Settings
labels:
- configuration
- title: Bug Fixes
labels:
- bug
- title: Other Changes
labels:
- "*"

View File

@@ -4,7 +4,7 @@ on:
push:
branches: [main]
pull_request:
workflow_dispatch:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
@@ -13,7 +13,6 @@ concurrency:
env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
jobs:
@@ -22,113 +21,141 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
- uses: Swatinem/rust-cache@v1
- run: cargo build --all
- run: ./target/debug/ruff_dev generate-all
- run: git diff --quiet README.md || echo "::error file=README.md::This file is outdated. Run 'cargo dev generate-all'."
- run: git diff --quiet ruff.schema.json || echo "::error file=ruff.schema.json::This file is outdated. Run 'cargo dev generate-all'."
- run: git diff --exit-code -- README.md ruff.schema.json docs
- run: git diff --quiet README.md || echo "::error file=README.md::This file is outdated. Run 'cargo +nightly dev generate-all'."
- run: git diff --quiet ruff.schema.json || echo "::error file=ruff.schema.json::This file is outdated. Run 'cargo +nightly dev generate-all'."
- run: git diff --exit-code -- README.md ruff.schema.json
cargo-fmt:
name: "cargo fmt"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup component add rustfmt
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
components: rustfmt
- run: cargo fmt --all --check
cargo-clippy:
cargo_clippy:
name: "cargo clippy"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: |
rustup component add clippy
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
components: clippy
target: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v1
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::pedantic
- run: cargo clippy -p ruff --target wasm32-unknown-unknown --all-features -- -D warnings -W clippy::pedantic
cargo-clippy-wasm:
name: "cargo clippy (wasm)"
cargo-test:
name: "cargo test"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: |
rustup component add clippy
rustup target add wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v1
- run: cargo clippy -p ruff --target wasm32-unknown-unknown --all-features -- -D warnings
cargo-test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
name: "cargo test | ${{ matrix.os }}"
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.65.0
override: true
- uses: Swatinem/rust-cache@v1
- run: cargo install cargo-insta
- run: pip install black[d]==22.12.0
- name: "Run tests (Ubuntu)"
if: ${{ matrix.os == 'ubuntu-latest' }}
- name: Run tests
run: |
cargo insta test --all --all-features --delete-unreferenced-snapshots
git diff --exit-code
- name: "Run tests (Windows)"
if: ${{ matrix.os == 'windows-latest' }}
shell: bash
run: |
cargo insta test --all --all-features
cargo insta test --all --delete-unreferenced-snapshots
git diff --exit-code
- run: cargo test --package ruff_cli --test black_compatibility_test -- --ignored
# Check for broken links in the documentation.
- run: cargo doc --all --no-deps
env:
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
RUSTDOCFLAGS: "-D warnings"
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
- run: RUSTDOCFLAGS="-D warnings" cargo doc --all --no-deps
scripts:
name: "test scripts"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- uses: actions-rs/toolchain@v1
with:
profile: minimal
override: true
- uses: Swatinem/rust-cache@v1
- run: ./scripts/add_rule.py --name DoTheThing --code PLC999 --linter pylint
- run: cargo check
- run: |
./scripts/add_plugin.py test --url https://pypi.org/project/-test/0.1.0/ --prefix TST
./scripts/add_rule.py --name FirstRule --code TST001 --linter test
./scripts/add_plugin.py test --url https://pypi.org/project/-test/0.1.0/ --prefix TST
./scripts/add_rule.py --name FirstRule --code TST001 --linter test
- run: cargo check
# TODO(charlie): Re-enable the `wasm-pack` tests.
# See: https://github.com/charliermarsh/ruff/issues/1425
# wasm-pack-test:
# name: "wasm-pack test"
# runs-on: ubuntu-latest
# env:
# WASM_BINDGEN_TEST_TIMEOUT: 60
# steps:
# - uses: actions/checkout@v3
# - uses: actions-rs/toolchain@v1
# with:
# profile: minimal
# toolchain: nightly-2022-11-01
# override: true
# - uses: actions/cache@v3
# env:
# cache-name: cache-cargo
# with:
# path: |
# ~/.cargo/registry
# ~/.cargo/git
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
# restore-keys: |
# ${{ runner.os }}-build-${{ env.cache-name }}-
# ${{ runner.os }}-build-
# ${{ runner.os }}-
# - uses: jetli/wasm-pack-action@v0.4.0
# - uses: jetli/wasm-bindgen-action@v0.2.0
# - run: wasm-pack test --node
maturin-build:
name: "maturin build"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.65.0
override: true
- uses: Swatinem/rust-cache@v1
- uses: actions/setup-python@v4
with:
python-version: "3.11"
- run: pip install maturin
- run: maturin build -b bin
- run: python scripts/transform_readme.py --target pypi
typos:
name: "spell check"
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: crate-ci/typos@master
with:
files: .
- name: Checkout Actions Repository
uses: actions/checkout@v2
- name: Check spelling of file.txt
uses: crate-ci/typos@master
with:
files: .

View File

@@ -1,33 +0,0 @@
name: mkdocs
on:
release:
types: [published]
workflow_dispatch:
jobs:
mkdocs:
runs-on: ubuntu-latest
env:
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- name: "Install dependencies"
run: |
pip install -r docs/requirements.txt
- name: "Copy README File"
run: |
python scripts/transform_readme.py --target mkdocs
python scripts/generate_mkdocs.py
mkdocs build --strict
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@2.0.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages publish site --project-name=ruff-docs --branch ${GITHUB_HEAD_REF} --commit-hash ${GITHUB_SHA}

View File

@@ -1,6 +1,7 @@
name: "[flake8-to-ruff] Release"
on: workflow_dispatch
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -9,55 +10,63 @@ concurrency:
env:
PACKAGE_NAME: flake8-to-ruff
CRATE_NAME: flake8_to_ruff
PYTHON_VERSION: "3.7" # to build abi3 wheels
PYTHON_VERSION: "3.11"
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
jobs:
macos-x86_64:
runs-on: macos-latest
runs-on: macos-12
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: "Install Rust toolchain"
run: rustup show
- name: "Build wheels - x86_64"
uses: PyO3/maturin-action@v1
- 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
- name: "Install built wheel - x86_64"
- name: Install built wheel - x86_64
run: |
pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
- name: "Upload wheels"
uses: actions/upload-artifact@v3
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
macos-universal:
runs-on: macos-latest
runs-on: macos-14
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: "Install Rust toolchain"
run: rustup show
- name: "Build wheels - universal2"
uses: PyO3/maturin-action@v1
- 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
- name: "Install built wheel - universal2"
- name: Install built wheel - universal2
run: |
pip install dist/${{ env.CRATE_NAME }}-*universal2.whl --force-reinstall
- name: "Upload wheels"
uses: actions/upload-artifact@v3
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
@@ -73,19 +82,23 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: ${{ matrix.target }}
- name: "Install Rust toolchain"
run: rustup show
- name: "Build wheels"
uses: PyO3/maturin-action@v1
- 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
- name: "Install built wheel"
- 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@v3
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
@@ -101,18 +114,18 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: "Build wheels"
uses: PyO3/maturin-action@v1
- name: Build wheels
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
- name: "Install built wheel"
- 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@v3
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
@@ -127,13 +140,13 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Build wheels"
uses: PyO3/maturin-action@v1
- 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
- uses: uraimo/run-on-arch-action@v2.5.0
- uses: uraimo/run-on-arch-action@v2.0.5
if: matrix.target != 'ppc64'
name: Install built wheel
with:
@@ -146,8 +159,8 @@ jobs:
pip3 install -U pip
run: |
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
- name: "Upload wheels"
uses: actions/upload-artifact@v3
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
@@ -165,13 +178,13 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: "Build wheels"
uses: PyO3/maturin-action@v1
- 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
- name: "Install built wheel"
- name: Install built wheel
if: matrix.target == 'x86_64-unknown-linux-musl'
uses: addnab/docker-run-action@v3
with:
@@ -180,8 +193,8 @@ jobs:
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@v3
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
@@ -200,8 +213,8 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Build wheels"
uses: PyO3/maturin-action@v1
- name: Build wheels
uses: messense/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2
@@ -216,32 +229,8 @@ jobs:
apk add py3-pip
run: |
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
- name: "Upload wheels"
uses: actions/upload-artifact@v3
- 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
steps:
- uses: actions/download-artifact@v3
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 *

View File

@@ -8,18 +8,21 @@ on:
env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
jobs:
publish:
runs-on: ubuntu-latest
env:
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
target: wasm32-unknown-unknown
- uses: actions/setup-node@v3
with:
node-version: 18
@@ -28,7 +31,7 @@ jobs:
- uses: jetli/wasm-pack-action@v0.4.0
- uses: jetli/wasm-bindgen-action@v0.2.0
- name: "Run wasm-pack"
run: wasm-pack build --target web --out-dir ../../playground/src/pkg crates/ruff
run: wasm-pack build --target web --out-dir playground/src/pkg . -- -p ruff
- name: "Install Node dependencies"
run: npm ci
working-directory: playground

View File

@@ -1,7 +1,6 @@
name: "[ruff] Release"
on:
workflow_dispatch:
release:
types: [published]
@@ -14,7 +13,6 @@ env:
PYTHON_VERSION: "3.7" # to build abi3 wheels
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
jobs:
@@ -26,34 +24,25 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels - x86_64"
uses: PyO3/maturin-action@v1
- 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
- name: "Install built wheel - x86_64"
- name: Install built wheel - x86_64
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
- name: "Upload wheels"
uses: actions/upload-artifact@v3
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
- name: "Archive binary"
run: |
ARCHIVE_FILE=ruff-x86_64-apple-darwin.tar.gz
tar czvf $ARCHIVE_FILE -C target/x86_64-apple-darwin/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@v3
with:
name: binaries
path: |
*.tar.gz
*.sha256
macos-universal:
runs-on: macos-latest
steps:
@@ -62,157 +51,105 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels - universal2"
uses: PyO3/maturin-action@v1
- 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
- name: "Install built wheel - universal2"
- name: Install built wheel - universal2
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*universal2.whl --force-reinstall
- name: "Upload wheels"
uses: actions/upload-artifact@v3
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
- name: "Archive binary"
run: |
ARCHIVE_FILE=ruff-aarch64-apple-darwin.tar.gz
tar czvf $ARCHIVE_FILE -C target/aarch64-apple-darwin/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@v3
with:
name: binaries
path: |
*.tar.gz
*.sha256
windows:
runs-on: windows-latest
strategy:
matrix:
platform:
- target: x86_64-pc-windows-msvc
arch: x64
- target: i686-pc-windows-msvc
arch: x86
- target: aarch64-pc-windows-msvc
arch: x64
target: [x64, x86]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: ${{ matrix.platform.arch }}
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@v1
architecture: ${{ matrix.target }}
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
target: ${{ matrix.platform.target }}
toolchain: stable
profile: minimal
default: true
- name: Build wheels
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist
- name: "Install built wheel"
if: ${{ !startsWith(matrix.platform.target, 'aarch64') }}
- name: Install built wheel
shell: bash
run: |
python -m pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
- name: "Upload wheels"
uses: actions/upload-artifact@v3
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
- name: "Archive binary"
shell: bash
run: |
ARCHIVE_FILE=ruff-${{ matrix.platform.target }}.zip
7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/ruff.exe
sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@v3
with:
name: binaries
path: |
*.zip
*.sha256
linux:
runs-on: ubuntu-latest
strategy:
matrix:
target:
- x86_64-unknown-linux-gnu
- i686-unknown-linux-gnu
target: [x86_64, i686]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@v1
- name: Build wheels
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist
- name: "Install built wheel"
if: ${{ startsWith(matrix.target, 'x86_64') }}
- name: Install built wheel
if: matrix.target == 'x86_64'
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
- name: "Upload wheels"
uses: actions/upload-artifact@v3
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
- name: "Archive binary"
run: |
ARCHIVE_FILE=ruff-${{ matrix.target }}.tar.gz
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@v3
with:
name: binaries
path: |
*.tar.gz
*.sha256
linux-cross:
runs-on: ubuntu-latest
strategy:
matrix:
platform:
- target: aarch64-unknown-linux-gnu
arch: aarch64
- target: armv7-unknown-linux-gnueabihf
arch: armv7
- target: s390x-unknown-linux-gnu
arch: s390x
- target: powerpc64le-unknown-linux-gnu
arch: ppc64le
- target: powerpc64-unknown-linux-gnu
arch: ppc64
target: [aarch64, armv7, s390x, ppc64le, ppc64]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@v1
- name: Build wheels
uses: messense/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist
- uses: uraimo/run-on-arch-action@v2.5.0
if: matrix.platform.arch != 'ppc64'
args: --no-default-features --release --out dist
- uses: uraimo/run-on-arch-action@v2.0.5
if: matrix.target != 'ppc64'
name: Install built wheel
with:
arch: ${{ matrix.platform.arch }}
arch: ${{ matrix.target }}
distro: ubuntu20.04
githubToken: ${{ github.token }}
install: |
@@ -221,23 +158,11 @@ jobs:
pip3 install -U pip
run: |
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
- name: "Upload wheels"
uses: actions/upload-artifact@v3
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
- name: "Archive binary"
run: |
ARCHIVE_FILE=ruff-${{ matrix.platform.target }}.tar.gz
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@v3
with:
name: binaries
path: |
*.tar.gz
*.sha256
musllinux:
runs-on: ubuntu-latest
@@ -252,15 +177,13 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@v1
- name: Build wheels
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: musllinux_1_2
args: --release --out dist
- name: "Install built wheel"
- name: Install built wheel
if: matrix.target == 'x86_64-unknown-linux-musl'
uses: addnab/docker-run-action@v3
with:
@@ -269,23 +192,11 @@ jobs:
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@v3
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
- name: "Archive binary"
run: |
ARCHIVE_FILE=ruff-${{ matrix.target }}.tar.gz
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@v3
with:
name: binaries
path: |
*.tar.gz
*.sha256
musllinux-cross:
runs-on: ubuntu-latest
@@ -301,10 +212,8 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@v1
- name: Build wheels
uses: messense/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2
@@ -319,23 +228,45 @@ jobs:
apk add py3-pip
run: |
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
- name: "Upload wheels"
uses: actions/upload-artifact@v3
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
- name: "Archive binary"
run: |
ARCHIVE_FILE=ruff-${{ matrix.platform.target }}.tar.gz
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@v3
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:
name: binaries
path: |
*.tar.gz
*.sha256
python-version: pypy${{ matrix.python-version }}
- name: Build wheels
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist -i pypy${{ matrix.python-version }}
- name: Install built wheel
if: matrix.target == 'x86_64'
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
release:
name: Release
@@ -348,27 +279,20 @@ jobs:
- linux-cross
- musllinux
- musllinux-cross
- pypy
if: "startsWith(github.ref, 'refs/tags/')"
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v2
with:
name: wheels
- uses: actions/setup-python@v4
- name: "Publish to PyPi"
- name: Publish to PyPi
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.RUFF_TOKEN }}
run: |
pip install --upgrade twine
twine upload --skip-existing *
- name: "Update pre-commit mirror"
- name: Update pre-commit mirror
run: |
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.RUFF_PRE_COMMIT_PAT }}" -H "X-GitHub-Api-Version: 2022-11-28" https://api.github.com/repos/charliermarsh/ruff-pre-commit/dispatches --data '{"event_type": "pypi_release"}'
- uses: actions/download-artifact@v3
with:
name: binaries
path: binaries
- name: Release
uses: softprops/action-gh-release@v1
with:
files: binaries/*

11
.gitignore vendored
View File

@@ -1,8 +1,6 @@
# Local cache
.ruff_cache
crates/ruff/resources/test/cpython
mkdocs.yml
.overrides
resources/test/cpython
###
# Rust.gitignore
@@ -184,10 +182,3 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
.vimspector.json
# Visual Studio Code
.vscode/
# VIM
.*.sw?
.sw?

View File

@@ -1,28 +1,14 @@
fail_fast: true
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.231
hooks:
- id: ruff
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.10.1
hooks:
- id: validate-pyproject
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.16
hooks:
- id: mdformat
additional_dependencies:
- mdformat-black
- black==23.1.0 # Must be the latest version of Black
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.33.0
hooks:
- id: markdownlint-fix
args:
- --disable
- MD013 # line-length
- MD033 # no-inline-html
- --
- repo: local
hooks:
- id: cargo-fmt
@@ -30,39 +16,3 @@ repos:
entry: cargo fmt --
language: rust
types: [rust]
- id: clippy
name: clippy
entry: cargo clippy --workspace --all-targets --all-features -- -D warnings
language: rust
pass_filenames: false
- id: ruff
name: ruff
entry: cargo run -p ruff_cli -- check --no-cache --force-exclude --fix --exit-non-zero-on-fix
language: rust
types_or: [python, pyi]
require_serial: true
exclude: |
(?x)^(
crates/ruff/resources/.*|
crates/ruff_python_formatter/resources/.*
)$
- id: dev-generate-all
name: dev-generate-all
entry: cargo dev generate-all
language: rust
pass_filenames: false
exclude: target
# Black
- repo: https://github.com/psf/black
rev: 23.1.0
hooks:
- id: black
exclude: |
(?x)^(
crates/ruff/resources/.*|
crates/ruff_python_formatter/resources/.*
)$
ci:
skip: [cargo-fmt, clippy, dev-generate-all]

View File

@@ -1,99 +1,5 @@
# Breaking Changes
## 0.0.246
### `multiple-statements-on-one-line-def` (`E704`) was removed ([#2773](https://github.com/charliermarsh/ruff/pull/2773))
This rule was introduced in v0.0.245. However, it turns out that pycodestyle and Flake8 ignore this
rule by default, as it is not part of PEP 8. As such, we've removed it from Ruff.
## 0.0.245
### Ruff's public `check` method was removed ([#2709](https://github.com/charliermarsh/ruff/pull/2709))
Previously, Ruff exposed a `check` method as a public Rust API. This method was used by few,
if any clients, and was not well documented or supported. As such, it has been removed, with
the intention of adding a stable public API in the future.
## 0.0.238
### `select`, `extend-select`, `ignore`, and `extend-ignore` have new semantics ([#2312](https://github.com/charliermarsh/ruff/pull/2312))
Previously, the interplay between `select` and its related options could lead to unexpected
behavior. For example, `ruff --select E501 --ignore ALL` and `ruff --select E501 --extend-ignore ALL` behaved differently. (See [#2312](https://github.com/charliermarsh/ruff/pull/2312) for more
examples.)
When Ruff determines the enabled rule set, it has to reconcile `select` and `ignore` from a variety
of sources, including the current `pyproject.toml`, any inherited `pyproject.toml` files, and the
CLI.
The new semantics are such that Ruff uses the "highest-priority" `select` as the basis for the rule
set, and then applies any `extend-select`, `ignore`, and `extend-ignore` adjustments. CLI options
are given higher priority than `pyproject.toml` options, and the current `pyproject.toml` file is
given higher priority than any inherited `pyproject.toml` files.
`extend-select` and `extend-ignore` are no longer given "top priority"; instead, they merely append
to the `select` and `ignore` lists, as in Flake8.
This change is largely backwards compatible -- most users should experience no change in behavior.
However, as an example of a breaking change, consider the following:
```toml
[tool.ruff]
ignore = ["F401"]
```
Running `ruff --select F` would previously have enabled all `F` rules, apart from `F401`. Now, it
will enable all `F` rules, including `F401`, as the command line's `--select` resets the resolution.
### `remove-six-compat` (`UP016`) has been removed ([#2332](https://github.com/charliermarsh/ruff/pull/2332))
The `remove-six-compat` rule has been removed. This rule was only useful for one-time Python 2-to-3
upgrades.
## 0.0.237
### `--explain`, `--clean`, and `--generate-shell-completion` are now subcommands ([#2190](https://github.com/charliermarsh/ruff/pull/2190))
`--explain`, `--clean`, and `--generate-shell-completion` are now implemented as subcommands:
```console
ruff . # Still works! And will always work.
ruff check . # New! Also works.
ruff --explain E402 # Still works.
ruff rule E402 # New! Also works. (And preferred.)
# Oops! The command has to come first.
ruff --format json --explain E402 # No longer works.
ruff --explain E402 --format json # Still works!
ruff rule E402 --format json # Works! (And preferred.)
```
This change is largely backwards compatible -- most users should experience
no change in behavior. However, please note the following exceptions:
- Subcommands will now fail when invoked with unsupported arguments, instead
of silently ignoring them. For example, the following will now fail:
```console
ruff --clean --respect-gitignore
```
(the `clean` command doesn't support `--respect-gitignore`.)
- The semantics of `ruff <arg>` have changed slightly when `<arg>` is a valid subcommand.
For example, prior to this release, running `ruff rule` would run `ruff` over a file or
directory called `rule`. Now, `ruff rule` would invoke the `rule` subcommand. This should
only impact projects with files or directories named `rule`, `check`, `explain`, `clean`,
or `generate-shell-completion`.
- Scripts that invoke ruff should supply `--` before any positional arguments.
(The semantics of `ruff -- <arg>` have not changed.)
- `--explain` previously treated `--format grouped` as a synonym for `--format text`.
This is no longer supported; instead, use `--format text`.
## 0.0.226
### `misplaced-comparison-constant` (`PLC2201`) was deprecated in favor of `SIM300` ([#1980](https://github.com/charliermarsh/ruff/pull/1980))

View File

@@ -1,17 +1,5 @@
# Contributor Covenant Code of Conduct
- [Our Pledge](#our-pledge)
- [Our Standards](#our-standards)
- [Enforcement Responsibilities](#enforcement-responsibilities)
- [Scope](#scope)
- [Enforcement](#enforcement)
- [Enforcement Guidelines](#enforcement-guidelines)
- [1. Correction](#1-correction)
- [2. Warning](#2-warning)
- [3. Temporary Ban](#3-temporary-ban)
- [4. Permanent Ban](#4-permanent-ban)
- [Attribution](#attribution)
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
@@ -29,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -118,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
@@ -127,12 +115,14 @@ the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available [here](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html).
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
For answers to common questions about this code of conduct, see the [FAQ](https://www.contributor-covenant.org/faq).
Translations are available [here](https://www.contributor-covenant.org/translations).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -2,36 +2,22 @@
Welcome! We're happy to have you here. Thank you in advance for your contribution to Ruff.
- [The Basics](#the-basics)
- [Prerequisites](#prerequisites)
- [Development](#development)
- [Project Structure](#project-structure)
- [Example: Adding a new lint rule](#example-adding-a-new-lint-rule)
- [Rule naming convention](#rule-naming-convention)
- [Example: Adding a new configuration option](#example-adding-a-new-configuration-option)
- [MkDocs](#mkdocs)
- [Release Process](#release-process)
- [Benchmarks](#benchmarks)
## The basics
## The Basics
Ruff welcomes contributions in the form of Pull Requests.
For small changes (e.g., bug fixes), feel free to submit a PR.
For larger changes (e.g., new lint rules, new functionality, new configuration options), consider
creating an [**issue**](https://github.com/charliermarsh/ruff/issues) outlining your proposed
change. You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5) to discuss your idea with
the community.
Ruff welcomes contributions in the form of Pull Requests. For small changes (e.g., bug fixes), feel
free to submit a PR. For larger changes (e.g., new lint rules, new functionality, new configuration
options), consider submitting an [Issue](https://github.com/charliermarsh/ruff/issues) outlining
your proposed change.
If you're looking for a place to start, we recommend implementing a new lint rule (see:
[_Adding a new lint rule_](#example-adding-a-new-lint-rule), which will allow you to learn from and
pattern-match against the examples in the existing codebase. Many lint rules are inspired by
existing Python plugins, which can be used as a reference implementation.
As a concrete example: consider taking on one of the rules from the [`flake8-pyi`](https://github.com/charliermarsh/ruff/issues/848)
plugin, and looking to the originating [Python source](https://github.com/PyCQA/flake8-pyi)
for guidance.
As a concrete example: consider taking on one of the rules from the [`tryceratops`](https://github.com/charliermarsh/ruff/issues/2056)
plugin, and looking to the originating [Python source](https://github.com/guilatrova/tryceratops)
for guidance. [`flake8-simplify`](https://github.com/charliermarsh/ruff/issues/998) has a few rules
left too.
### Prerequisites
@@ -49,123 +35,79 @@ cargo install cargo-insta
After cloning the repository, run Ruff locally with:
```shell
cargo run -p ruff_cli -- check /path/to/file.py --no-cache
cargo run resources/test/fixtures --no-cache
```
Prior to opening a pull request, ensure that your code has been auto-formatted,
and that it passes both the lint and test validation checks:
and that it passes both the lint and test validation checks.
For rustfmt and Clippy, we use [nightly Rust][nightly], as it is stricter than stable Rust.
(However, tests and builds use stable Rust.)
```shell
cargo fmt # Auto-formatting...
cargo clippy --fix --workspace --all-targets --all-features # Linting...
cargo test # Testing...
cargo +nightly fmt --all # Auto-formatting...
cargo +nightly clippy --fix --workspace --all-targets --all-features -- -W clippy::pedantic # Linting...
cargo test --all # Testing...
```
These checks will run on GitHub Actions when you open your Pull Request, but running them locally
will save you time and expedite the merge process.
Note that many code changes also require updating the snapshot tests, which is done interactively
after running `cargo test` like so:
```shell
cargo insta review
```
If you have `pre-commit` [installed](https://pre-commit.com/#installation) then you can use it to
assist with formatting and linting. The following command will run the `pre-commit` hooks:
```shell
pre-commit run --all-files
```
Your Pull Request will be reviewed by a maintainer, which may involve a few rounds of iteration
prior to merging.
### Project Structure
Ruff is structured as a monorepo with a [flat crate structure](https://matklad.github.io/2021/08/22/large-rust-workspaces.html),
such that all crates are contained in a flat `crates` directory.
The vast majority of the code, including all lint rules, lives in the `ruff` crate (located at
`crates/ruff`). As a contributor, that's the crate that'll be most relevant to you.
At time of writing, the repository includes the following crates:
- `crates/ruff`: library crate containing all lint rules and the core logic for running them.
- `crates/ruff_cli`: binary crate containing Ruff's command-line interface.
- `crates/ruff_dev`: binary crate containing utilities used in the development of Ruff itself (e.g., `cargo dev generate-all`).
- `crates/ruff_macros`: library crate containing macros used by Ruff.
- `crates/ruff_python`: library crate implementing Python-specific functionality (e.g., lists of standard library modules by versionb).
- `crates/flake8_to_ruff`: binary crate for generating Ruff configuration from Flake8 configuration.
### Example: Adding a new lint rule
At a high level, the steps involved in adding a new lint rule are as follows:
1. Determine a name for the new rule as per our [rule naming convention](#rule-naming-convention).
1. Create a file for your rule (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs`).
1. In that file, define a violation struct. You can grep for `define_violation!` to see examples.
1. Map the violation struct to a rule code in `crates/ruff/src/registry.rs` (e.g., `E402`).
1. Define the logic for triggering the violation in `crates/ruff/src/checkers/ast.rs` (for AST-based
checks), `crates/ruff/src/checkers/tokens.rs` (for token-based checks), `crates/ruff/src/checkers/lines.rs`
(for text-based checks), or `crates/ruff/src/checkers/filesystem.rs` (for filesystem-based
checks).
1. Add a test fixture.
1. Update the generated files (documentation and generated code).
1. Create a file for your rule (e.g., `src/rules/flake8_bugbear/rules/abstract_base_class.rs`).
2. In that file, define a violation struct. You can grep for `define_violation!` to see examples.
3. Map the violation struct to a rule code in `src/registry.rs` (e.g., `E402`).
4. Define the logic for triggering the violation in `src/checkers/ast.rs` (for AST-based checks),
`src/checkers/tokens.rs` (for token-based checks), `src/checkers/lines.rs` (for text-based
checks), or `src/checkers/filesystem.rs` (for filesystem-based checks).
5. Add a test fixture.
6. Update the generated files (documentation and generated code).
To define the violation, start by creating a dedicated file for your rule under the appropriate
rule linter (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs`). That file should
rule linter (e.g., `src/rules/flake8_bugbear/rules/abstract_base_class.rs`). That file should
contain a struct defined via `define_violation!`, along with a function that creates the violation
based on any required inputs. (Many of the existing examples live in `crates/ruff/src/violations.rs`,
but we're looking to place new rules in their own files.)
based on any required inputs. (Many of the existing examples live in `src/violations.rs`, but we're
looking to place new rules in their own files.)
To trigger the violation, you'll likely want to augment the logic in `crates/ruff/src/checkers/ast.rs`,
which defines the Python AST visitor, responsible for iterating over the abstract syntax tree and
To trigger the violation, you'll likely want to augment the logic in `src/checkers/ast.rs`, which
defines the Python AST visitor, responsible for iterating over the abstract syntax tree and
collecting diagnostics as it goes.
If you need to inspect the AST, you can run `cargo dev print-ast` with a Python file. Grep
If you need to inspect the AST, you can run `cargo +nightly dev print-ast` with a Python file. Grep
for the `Check::new` invocations to understand how other, similar rules are implemented.
To add a test fixture, create a file under `crates/ruff/resources/test/fixtures/[linter]`, named to match
the code you defined earlier (e.g., `crates/ruff/resources/test/fixtures/pycodestyle/E402.py`). This file should
To add a test fixture, create a file under `resources/test/fixtures/[linter]`, named to match
the code you defined earlier (e.g., `resources/test/fixtures/pycodestyle/E402.py`). This file should
contain a variety of violations and non-violations designed to evaluate and demonstrate the behavior
of your lint rule.
Run `cargo dev generate-all` to generate the code for your new fixture. Then run Ruff
locally with (e.g.) `cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402`.
Run `cargo +nightly dev generate-all` to generate the code for your new fixture. Then run Ruff
locally with (e.g.) `cargo run resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402`.
Once you're satisfied with the output, codify the behavior as a snapshot test by adding a new
`test_case` macro in the relevant `crates/ruff/src/[linter]/mod.rs` file. Then, run `cargo test`.
`test_case` macro in the relevant `src/[linter]/mod.rs` file. Then, run `cargo test --all`.
Your test will fail, but you'll be prompted to follow-up with `cargo insta review`. Accept the
generated snapshot, then commit the snapshot file alongside the rest of your changes.
Finally, regenerate the documentation and generated code with `cargo dev generate-all`.
#### Rule naming convention
The rule name should make sense when read as "allow _rule-name_" or "allow _rule-name_ items".
This implies that rule names:
- should state the bad thing being checked for
- should not contain instructions on what you what you should use instead
(these belong in the rule documentation and the `autofix_title` for rules that have autofix)
When re-implementing rules from other linters, this convention is given more importance than
preserving the original rule name.
Finally, regenerate the documentation and generated code with `cargo +nightly dev generate-all`.
### Example: Adding a new configuration option
Ruff's user-facing settings live in a few different places.
First, the command-line options are defined via the `Cli` struct in `crates/ruff/src/cli.rs`.
First, the command-line options are defined via the `Cli` struct in `src/cli.rs`.
Second, the `pyproject.toml` options are defined in `crates/ruff/src/settings/options.rs` (via the
`Options` struct), `crates/ruff/src/settings/configuration.rs` (via the `Configuration` struct), and
`crates/ruff/src/settings/mod.rs` (via the `Settings` struct). These represent, respectively: the
schema used to parse the `pyproject.toml` file; an internal, intermediate representation; and the
final, internal representation used to power Ruff.
Second, the `pyproject.toml` options are defined in `src/settings/options.rs` (via the `Options`
struct), `src/settings/configuration.rs` (via the `Configuration` struct), and `src/settings/mod.rs`
(via the `Settings` struct). These represent, respectively: the schema used to parse the
`pyproject.toml` file; an internal, intermediate representation; and the final, internal
representation used to power Ruff.
To add a new configuration option, you'll likely want to modify these latter few files (along with
`cli.rs`, if appropriate). If you want to pattern-match against an existing example, grep for
@@ -173,40 +115,15 @@ To add a new configuration option, you'll likely want to modify these latter few
variables (e.g., `_`).
Note that plugin-specific configuration options are defined in their own modules (e.g.,
`crates/ruff/src/flake8_unused_arguments/settings.rs`).
`src/flake8_unused_arguments/settings.rs`).
You may also want to add the new configuration option to the `flake8-to-ruff` tool, which is
responsible for converting `flake8` configuration files to Ruff's TOML format. This logic
lives in `crates/ruff/src/flake8_to_ruff/converter.rs`.
lives in `flake8_to_ruff/src/converter.rs`.
Finally, regenerate the documentation and generated code with `cargo dev generate-all`.
Finally, regenerate the documentation and generated code with `cargo +nightly dev generate-all`.
## MkDocs
To preview any changes to the documentation locally:
1. Install MkDocs and Material for MkDocs with:
```shell
pip install -r docs/requirements.txt
```
1. Generate the MkDocs site with:
```shell
python scripts/generate_mkdocs.py
```
1. Run the development server with:
```shell
mkdocs serve
```
The documentation should then be available locally at
[http://127.0.0.1:8000/docs/](http://127.0.0.1:8000/docs/).
## Release Process
## Release process
As of now, Ruff has an ad hoc release process: releases are cut with high frequency via GitHub
Actions, which automatically generates the appropriate wheels across architectures and publishes
@@ -215,125 +132,4 @@ them to [PyPI](https://pypi.org/project/ruff/).
Ruff follows the [semver](https://semver.org/) versioning standard. However, as pre-1.0 software,
even patch releases may contain [non-backwards-compatible changes](https://semver.org/#spec-item-4).
## Benchmarks
First, clone [CPython](https://github.com/python/cpython). It's a large and diverse Python codebase,
which makes it a good target for benchmarking.
```shell
git clone --branch 3.10 https://github.com/python/cpython.git crates/ruff/resources/test/cpython
```
To benchmark the release build:
```shell
cargo build --release && hyperfine --ignore-failure --warmup 10 \
"./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache" \
"./target/release/ruff ./crates/ruff/resources/test/cpython/"
Benchmark 1: ./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache
Time (mean ± σ): 293.8 ms ± 3.2 ms [User: 2384.6 ms, System: 90.3 ms]
Range (min … max): 289.9 ms … 301.6 ms 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 2: ./target/release/ruff ./crates/ruff/resources/test/cpython/
Time (mean ± σ): 48.0 ms ± 3.1 ms [User: 65.2 ms, System: 124.7 ms]
Range (min … max): 45.0 ms … 66.7 ms 62 runs
Warning: Ignoring non-zero exit code.
Summary
'./target/release/ruff ./crates/ruff/resources/test/cpython/' ran
6.12 ± 0.41 times faster than './target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache'
```
To benchmark against the ecosystem's existing tools:
```shell
hyperfine --ignore-failure --warmup 5 \
"./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache" \
"pyflakes crates/ruff/resources/test/cpython" \
"autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython" \
"pycodestyle crates/ruff/resources/test/cpython" \
"flake8 crates/ruff/resources/test/cpython"
Benchmark 1: ./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache
Time (mean ± σ): 294.3 ms ± 3.3 ms [User: 2467.5 ms, System: 89.6 ms]
Range (min … max): 291.1 ms … 302.8 ms 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 2: pyflakes crates/ruff/resources/test/cpython
Time (mean ± σ): 15.786 s ± 0.143 s [User: 15.560 s, System: 0.214 s]
Range (min … max): 15.640 s … 16.157 s 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 3: autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython
Time (mean ± σ): 6.175 s ± 0.169 s [User: 54.102 s, System: 1.057 s]
Range (min … max): 5.950 s … 6.391 s 10 runs
Benchmark 4: pycodestyle crates/ruff/resources/test/cpython
Time (mean ± σ): 46.921 s ± 0.508 s [User: 46.699 s, System: 0.202 s]
Range (min … max): 46.171 s … 47.863 s 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 5: flake8 crates/ruff/resources/test/cpython
Time (mean ± σ): 12.260 s ± 0.321 s [User: 102.934 s, System: 1.230 s]
Range (min … max): 11.848 s … 12.933 s 10 runs
Warning: Ignoring non-zero exit code.
Summary
'./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache' ran
20.98 ± 0.62 times faster than 'autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython'
41.66 ± 1.18 times faster than 'flake8 crates/ruff/resources/test/cpython'
53.64 ± 0.77 times faster than 'pyflakes crates/ruff/resources/test/cpython'
159.43 ± 2.48 times faster than 'pycodestyle crates/ruff/resources/test/cpython'
```
You can run `poetry install` from `./scripts` to create a working environment for the above. All
reported benchmarks were computed using the versions specified by `./scripts/pyproject.toml`
on Python 3.11.
To benchmark Pylint, remove the following files from the CPython repository:
```shell
rm Lib/test/bad_coding.py \
Lib/test/bad_coding2.py \
Lib/test/bad_getattr.py \
Lib/test/bad_getattr2.py \
Lib/test/bad_getattr3.py \
Lib/test/badcert.pem \
Lib/test/badkey.pem \
Lib/test/badsyntax_3131.py \
Lib/test/badsyntax_future10.py \
Lib/test/badsyntax_future3.py \
Lib/test/badsyntax_future4.py \
Lib/test/badsyntax_future5.py \
Lib/test/badsyntax_future6.py \
Lib/test/badsyntax_future7.py \
Lib/test/badsyntax_future8.py \
Lib/test/badsyntax_future9.py \
Lib/test/badsyntax_pep3120.py \
Lib/test/test_asyncio/test_runners.py \
Lib/test/test_copy.py \
Lib/test/test_inspect.py \
Lib/test/test_typing.py
```
Then, from `crates/ruff/resources/test/cpython`, run: `time pylint -j 0 -E $(git ls-files '*.py')`. This
will execute Pylint with maximum parallelism and only report errors.
To benchmark Pyupgrade, run the following from `crates/ruff/resources/test/cpython`:
```shell
hyperfine --ignore-failure --warmup 5 --prepare "git reset --hard HEAD" \
"find . -type f -name \"*.py\" | xargs -P 0 pyupgrade --py311-plus"
Benchmark 1: find . -type f -name "*.py" | xargs -P 0 pyupgrade --py311-plus
Time (mean ± σ): 30.119 s ± 0.195 s [User: 28.638 s, System: 0.390 s]
Range (min … max): 29.813 s … 30.356 s 10 runs
```
[nightly]: https://rust-lang.github.io/rustup/concepts/channels.html#working-with-nightly-rust

856
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,85 @@
[workspace]
members = ["crates/*"]
members = [
"flake8_to_ruff",
"ruff_dev",
"ruff_cli",
]
default-members = [".", "ruff_cli"]
[workspace.package]
[package]
name = "ruff"
version = "0.0.231"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.67.0"
rust-version = "1.65.0"
documentation = "https://github.com/charliermarsh/ruff"
homepage = "https://github.com/charliermarsh/ruff"
repository = "https://github.com/charliermarsh/ruff"
readme = "README.md"
license = "MIT"
[workspace.dependencies]
[lib]
name = "ruff"
crate-type = ["cdylib", "rlib"]
doctest = false
[dependencies]
anyhow = { version = "1.0.66" }
clap = { version = "4.0.1", features = ["derive"] }
bitflags = { version = "1.3.2" }
cfg-if = { version = "1.0.0" }
chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
clap = { version = "4.0.1", features = ["derive", "env"] }
colored = { version = "2.0.0" }
dirs = { version = "4.0.0" }
fern = { version = "0.6.1" }
glob = { version = "0.3.0" }
globset = { version = "0.4.9" }
ignore = { version = "0.4.18" }
imperative = { version = "1.0.3" }
itertools = { version = "0.10.5" }
is-macro = { version = "0.2.2" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e5beb532fdd1e209ad2dbb470438" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
log = { version = "0.4.17" }
natord = { version = "1.0.9" }
nohash-hasher = { version = "0.2.0" }
num-bigint = { version = "0.4.3" }
num-traits = "0.2.15"
once_cell = { version = "1.16.0" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
regex = { version = "1.6.0" }
ruff_macros = { version = "0.0.231", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "aa8336ee94492b52458ed8e1517238e5c6c2914c" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "aa8336ee94492b52458ed8e1517238e5c6c2914c" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
schemars = { version = "0.8.11" }
semver = { version = "1.0.16" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
shellexpand = { version = "3.0.0" }
smallvec = { version = "1.10.0" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
toml = { version = "0.6.0" }
textwrap = { version = "0.16.0" }
thiserror = { version = "1.0" }
titlecase = { version = "2.2.1" }
toml_edit = { version = "0.17.1", features = ["easy"] }
# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support
# For (future) wasm-pack support
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
getrandom = { version = "0.2.7", features = ["js"] }
console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "0.2.0" }
serde-wasm-bindgen = { version = "0.4" }
js-sys = { version = "0.3.60" }
wasm-bindgen = { version = "0.2.83" }
[dev-dependencies]
insta = { version = "1.19.1", features = ["yaml"] }
test-case = { version = "2.2.2" }
wasm-bindgen-test = { version = "0.3.33" }
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
criterion = { version = "0.4.0" }
[profile.release]
panic = "abort"

1095
LICENSE

File diff suppressed because it is too large Load Diff

3477
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
[files]
extend-exclude = ["snapshots", "black"]
[default.extend-words]
trivias = "trivias"
hel = "hel"
whos = "whos"

View File

@@ -1,7 +0,0 @@
doc-valid-idents = [
"StackOverflow",
"CodeQL",
"IPython",
"NumPy",
"..",
]

View File

@@ -1,20 +0,0 @@
[package]
name = "flake8-to-ruff"
version = "0.0.253"
edition = { workspace = true }
rust-version = { workspace = true }
[dependencies]
anyhow = { workspace = true }
clap = { workspace = true }
colored = { version = "2.0.0" }
configparser = { version = "3.0.2" }
once_cell = { workspace = true }
regex = { workspace = true }
ruff = { path = "../ruff", default-features = false }
rustc-hash = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
toml = { workspace = true }

View File

@@ -1,83 +0,0 @@
[package]
name = "ruff"
version = "0.0.253"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = { workspace = true }
rust-version = { workspace = true }
documentation = "https://github.com/charliermarsh/ruff"
homepage = "https://github.com/charliermarsh/ruff"
repository = "https://github.com/charliermarsh/ruff"
readme = "README.md"
license = "MIT"
[lib]
name = "ruff"
crate-type = ["cdylib", "rlib"]
doctest = false
[dependencies]
ruff_macros = { path = "../ruff_macros" }
ruff_python = { path = "../ruff_python" }
ruff_rustpython = { path = "../ruff_rustpython" }
anyhow = { workspace = true }
bisection = { version = "0.1.0" }
bitflags = { version = "1.3.2" }
cfg-if = { version = "1.0.0" }
chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
clap = { workspace = true, features = ["derive", "env", "string"] }
colored = { version = "2.0.0" }
derivative = { version = "2.2.0" }
dirs = { version = "4.0.0" }
fern = { version = "0.6.1" }
glob = { version = "0.3.0" }
globset = { version = "0.4.9" }
ignore = { version = "0.4.18" }
imperative = { version = "1.0.3" }
is-macro = { workspace = true }
itertools = { workspace = true }
libcst = { workspace = true }
log = { version = "0.4.17" }
natord = { version = "1.0.9" }
nohash-hasher = { version = "0.2.0" }
num-bigint = { version = "0.4.3" }
num-traits = "0.2.15"
once_cell = { workspace = true }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
regex = { workspace = true }
result-like = "0.4.6"
rustc-hash = { workspace = true }
rustpython-common = { workspace = true }
rustpython-parser = { workspace = true }
schemars = { workspace = true }
semver = { version = "1.0.16" }
serde = { workspace = true }
shellexpand = { version = "3.0.0" }
smallvec = { version = "1.10.0" }
strum = { workspace = true }
strum_macros = { workspace = true }
textwrap = { version = "0.16.0" }
thiserror = { version = "1.0" }
titlecase = { version = "2.2.1" }
toml = { workspace = true }
# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
getrandom = { version = "0.2.7", features = ["js"] }
console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "0.2.0" }
serde-wasm-bindgen = { version = "0.4" }
js-sys = { version = "0.3.60" }
wasm-bindgen = { version = "0.2.83" }
[dev-dependencies]
insta = { version = "1.19.0", features = ["yaml", "redactions"] }
test-case = { version = "2.2.2" }
wasm-bindgen-test = { version = "0.3.33" }
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
criterion = { version = "0.4.0" }
[features]
default = []
logical_lines = []

View File

@@ -1,4 +0,0 @@
avoid-*
do-not-*
uses-*
*-used

View File

@@ -1,44 +0,0 @@
"""Test case expected to be run with `ignore_fully_untyped = True`."""
def ok_fully_untyped_1(a, b):
pass
def ok_fully_untyped_2():
pass
def ok_fully_typed_1(a: int, b: int) -> int:
pass
def ok_fully_typed_2() -> int:
pass
def ok_fully_typed_3(a: int, *args: str, **kwargs: str) -> int:
pass
def error_partially_typed_1(a: int, b):
pass
def error_partially_typed_2(a: int, b) -> int:
pass
def error_partially_typed_3(a: int, b: int):
pass
class X:
def ok_untyped_method_with_arg(self, a):
pass
def ok_untyped_method(self):
pass
def error_typed_self(self: X):
pass

View File

@@ -1,14 +0,0 @@
try:
pass
except Exception:
pass
try:
pass
except:
pass
try:
pass
except ValueError:
pass

View File

@@ -1,29 +0,0 @@
try:
pass
except Exception:
continue
try:
pass
except:
continue
try:
pass
except (Exception,):
continue
try:
pass
except (Exception, ValueError):
continue
try:
pass
except ValueError:
continue
try:
pass
except (ValueError,):
continue

View File

@@ -1,95 +0,0 @@
# single-line failures
query1 = "SELECT %s FROM table" % (var,) # bad
query2 = "SELECT var FROM " + table
query3 = "SELECT " + val + " FROM " + table
query4 = "SELECT {} FROM table;".format(var)
query5 = f"SELECT * FROM table WHERE var = {var}"
query6 = "DELETE FROM table WHERE var = %s" % (var,)
query7 = "DELETE FROM table WHERE VAR = " + var
query8 = "DELETE FROM " + table + "WHERE var = " + var
query9 = "DELETE FROM table WHERE var = {}".format(var)
query10 = f"DELETE FROM table WHERE var = {var}"
query11 = "INSERT INTO table VALUES (%s)" % (var,)
query12 = "INSERT INTO TABLE VALUES (" + var + ")"
query13 = "INSERT INTO {} VALUES ({})".format(table, var)
query14 = f"INSERT INTO {table} VALUES var = {var}"
query15 = "UPDATE %s SET var = %s" % (table, var)
query16 = "UPDATE " + table + " SET var = " + var
query17 = "UPDATE {} SET var = {}".format(table, var)
query18 = f"UPDATE {table} SET var = {var}"
query19 = "select %s from table" % (var,)
query20 = "select var from " + table
query21 = "select " + val + " from " + table
query22 = "select {} from table;".format(var)
query23 = f"select * from table where var = {var}"
query24 = "delete from table where var = %s" % (var,)
query25 = "delete from table where var = " + var
query26 = "delete from " + table + "where var = " + var
query27 = "delete from table where var = {}".format(var)
query28 = f"delete from table where var = {var}"
query29 = "insert into table values (%s)" % (var,)
query30 = "insert into table values (" + var + ")"
query31 = "insert into {} values ({})".format(table, var)
query32 = f"insert into {table} values var = {var}"
query33 = "update %s set var = %s" % (table, var)
query34 = "update " + table + " set var = " + var
query35 = "update {} set var = {}".format(table, var)
query36 = f"update {table} set var = {var}"
# multi-line failures
def query37():
return """
SELECT *
FROM table
WHERE var = %s
""" % var
def query38():
return """
SELECT *
FROM TABLE
WHERE var =
""" + var
def query39():
return """
SELECT *
FROM table
WHERE var = {}
""".format(var)
def query40():
return f"""
SELECT *
FROM table
WHERE var = {var}
"""
def query41():
return (
"SELECT *"
"FROM table"
f"WHERE var = {var}"
)
# # cursor-wrapped failures
query42 = cursor.execute("SELECT * FROM table WHERE var = %s" % var)
query43 = cursor.execute(f"SELECT * FROM table WHERE var = {var}")
query44 = cursor.execute("SELECT * FROM table WHERE var = {}".format(var))
query45 = cursor.executemany("SELECT * FROM table WHERE var = %s" % var, [])
# # pass
query = "SELECT * FROM table WHERE id = 1"
query = "DELETE FROM table WHERE id = 1"
query = "INSERT INTO table VALUES (1)"
query = "UPDATE table SET id = 1"
cursor.execute('SELECT * FROM table WHERE id = %s', var)
cursor.execute('SELECT * FROM table WHERE id = 1')
cursor.executemany('SELECT * FROM table WHERE id = %s', [var, var2])

View File

@@ -1,88 +0,0 @@
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)
FMT = "{foo} {bar}"
for foo, bar in [(1, 2)]:
if foo:
print(FMT.format(**locals()))
for foo, bar in [(1, 2)]:
if foo:
print(FMT.format(**globals()))
for foo, bar in [(1, 2)]:
if foo:
print(FMT.format(**vars()))
for foo, bar in [(1, 2)]:
print(FMT.format(foo=foo, bar=eval("bar")))
def f():
# Fixable.
for foo, bar, baz in (["1", "2", "3"],):
if foo or baz:
break
def f():
# Unfixable due to usage of `bar` outside of loop.
for foo, bar, baz in (["1", "2", "3"],):
if foo or baz:
break
print(bar)
def f():
# Fixable.
for foo, bar, baz in (["1", "2", "3"],):
if foo or baz:
break
bar = 1
def f():
# Fixable.
for foo, bar, baz in (["1", "2", "3"],):
if foo or baz:
break
bar = 1
print(bar)
# Unfixable due to trailing underscore (`_line_` wouldn't be considered an ignorable
# variable name).
for line_ in range(self.header_lines):
fp.readline()

View File

@@ -1,101 +0,0 @@
"""
Should emit:
B027 - on lines 13, 16, 19, 23
"""
import abc
from abc import ABC
from abc import abstractmethod, abstractproperty
from abc import abstractmethod as notabstract
from abc import abstractproperty as notabstract_property
class AbstractClass(ABC):
def empty_1(self): # error
...
def empty_2(self): # error
pass
def empty_3(self): # error
"""docstring"""
...
def empty_4(self): # error
"""multiple ellipsis/pass"""
...
pass
...
pass
@notabstract
def abstract_0(self):
...
@abstractmethod
def abstract_1(self):
...
@abstractmethod
def abstract_2(self):
pass
@abc.abstractmethod
def abstract_3(self):
...
@abc.abstractproperty
def abstract_4(self):
...
@abstractproperty
def abstract_5(self):
...
@notabstract_property
def abstract_6(self):
...
def body_1(self):
print("foo")
...
def body_2(self):
self.body_1()
class NonAbstractClass:
def empty_1(self): # safe
...
def empty_2(self): # safe
pass
# ignore @overload, fixes issue #304
# ignore overload with other imports, fixes #308
import typing
import typing as t
import typing as anything
from typing import Union, overload
class AbstractClass(ABC):
@overload
def empty_1(self, foo: str):
...
@typing.overload
def empty_1(self, foo: int):
...
@t.overload
def empty_1(self, foo: list):
...
@anything.overload
def empty_1(self, foo: float):
...
@abstractmethod
def empty_1(self, foo: Union[str, int, list, float]):
...

View File

@@ -1,14 +0,0 @@
"""
Should emit:
B029 - on lines 8 and 13
"""
try:
pass
except ():
pass
try:
pass
except () as e:
pass

View File

@@ -1,29 +0,0 @@
"""
Should emit:
B032 - on lines 9, 10, 12, 13, 16-19
"""
# Flag these
dct = {"a": 1}
dct["b"]: 2
dct.b: 2
dct["b"]: "test"
dct.b: "test"
test = "test"
dct["b"]: test
dct["b"]: test.lower()
dct.b: test
dct.b: test.lower()
# Do not flag below
typed_dct: dict[str, int] = {"a": 1}
typed_dct["b"] = 2
typed_dct.b = 2
class TestClass:
def test_self(self):
self.test: int

View File

@@ -1,13 +0,0 @@
x = set(x for x in range(3))
x = set(
x for x in range(3)
)
y = f'{set(a if a < 6 else 0 for a in range(3))}'
_ = '{}'.format(set(a if a < 6 else 0 for a in range(3)))
print(f'Hello {set(a for a in range(3))} World')
def set(*args, **kwargs):
return None
set(x for x in range(3))

View File

@@ -1,34 +0,0 @@
# Errors.
nums = [1, 2, 3]
map(lambda x: x + 1, nums)
map(lambda x: str(x), nums)
list(map(lambda x: x * 2, nums))
set(map(lambda x: x % 2 == 0, nums))
dict(map(lambda v: (v, v**2), nums))
map(lambda: "const", nums)
map(lambda _: 3.0, nums)
_ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
all(map(lambda v: isinstance(v, dict), nums))
filter(func, map(lambda v: v, nums))
# When inside f-string, then the fix should be surrounded by whitespace
_ = f"{set(map(lambda x: x % 2 == 0, nums))}"
_ = f"{dict(map(lambda v: (v, v**2), nums))}"
# Error, but unfixable.
# For simple expressions, this could be: `(x if x else 1 for x in nums)`.
# For more complex expressions, this would differ: `(x + 2 if x else 3 for x in nums)`.
map(lambda x=1: x, nums)
# False negatives.
map(lambda x=2, y=1: x + y, nums, nums)
set(map(lambda x, y: x, nums, nums))
def myfunc(arg1: int, arg2: int = 4):
return 2 * arg1 + arg2
list(map(myfunc, nums))
[x for x in nums]

View File

@@ -1,48 +0,0 @@
from django.db.models import Model as DjangoModel
from django.db import models
from django.db.models import CharField as SmthCharField
class IncorrectModel(models.Model):
charfield = models.CharField(max_length=255, null=True)
textfield = models.TextField(max_length=255, null=True)
slugfield = models.SlugField(max_length=255, null=True)
emailfield = models.EmailField(max_length=255, null=True)
filepathfield = models.FilePathField(max_length=255, null=True)
urlfield = models.URLField(max_length=255, null=True)
class IncorrectModelWithAlias(DjangoModel):
charfield = DjangoModel.CharField(max_length=255, null=True)
textfield = SmthCharField(max_length=255, null=True)
slugfield = models.SlugField(max_length=255, null=True)
emailfield = models.EmailField(max_length=255, null=True)
filepathfield = models.FilePathField(max_length=255, null=True)
urlfield = models.URLField(max_length=255, null=True)
class IncorrectModelWithoutSuperclass:
charfield = DjangoModel.CharField(max_length=255, null=True)
textfield = SmthCharField(max_length=255, null=True)
slugfield = models.SlugField(max_length=255, null=True)
emailfield = models.EmailField(max_length=255, null=True)
filepathfield = models.FilePathField(max_length=255, null=True)
urlfield = models.URLField(max_length=255, null=True)
class CorrectModel(models.Model):
charfield = models.CharField(max_length=255, null=False, blank=True)
textfield = models.TextField(max_length=255, null=False, blank=True)
slugfield = models.SlugField(max_length=255, null=False, blank=True)
emailfield = models.EmailField(max_length=255, null=False, blank=True)
filepathfield = models.FilePathField(max_length=255, null=False, blank=True)
urlfield = models.URLField(max_length=255, null=False, blank=True)
charfieldu = models.CharField(max_length=255, null=True, blank=True, unique=True)
textfieldu = models.TextField(max_length=255, null=True, blank=True, unique=True)
slugfieldu = models.SlugField(max_length=255, null=True, blank=True, unique=True)
emailfieldu = models.EmailField(max_length=255, null=True, blank=True, unique=True)
filepathfieldu = models.FilePathField(
max_length=255, null=True, blank=True, unique=True
)
urlfieldu = models.URLField(max_length=255, null=True, blank=True, unique=True)

View File

@@ -1,21 +0,0 @@
from django.shortcuts import render
def test_view1(request):
return render(request, "index.html", locals())
def test_view2(request):
return render(request, "index.html", context=locals())
def test_view3(request):
return render(request, "index.html")
def test_view4(request):
return render(request, "index.html", {})
def test_view5(request):
return render(request, "index.html", context={})

View File

@@ -1,11 +0,0 @@
from django import forms
class TestModelForm1(forms.ModelForm):
class Meta:
exclude = ["bar"]
class TestModelForm2(forms.ModelForm):
class Meta:
fields = ["foo"]

View File

@@ -1,16 +0,0 @@
from django import forms
class TestModelForm1(forms.ModelForm):
class Meta:
fields = "__all__"
class TestModelForm2(forms.ModelForm):
class Meta:
fields = b"__all__"
class TestModelForm3(forms.ModelForm):
class Meta:
fields = ["foo"]

View File

@@ -1,167 +0,0 @@
from django.db import models
from django.db.models import Model
# Models without __str__
class TestModel1(models.Model):
new_field = models.CharField(max_length=10)
class Meta:
verbose_name = "test model"
verbose_name_plural = "test models"
@property
def my_brand_new_property(self):
return 1
def my_beautiful_method(self):
return 2
class TestModel2(Model):
new_field = models.CharField(max_length=10)
class Meta:
verbose_name = "test model"
verbose_name_plural = "test models"
@property
def my_brand_new_property(self):
return 1
def my_beautiful_method(self):
return 2
class TestModel3(Model):
new_field = models.CharField(max_length=10)
class Meta:
abstract = False
@property
def my_brand_new_property(self):
return 1
def my_beautiful_method(self):
return 2
# Models with __str__
class TestModel4(Model):
new_field = models.CharField(max_length=10)
class Meta:
verbose_name = "test model"
verbose_name_plural = "test models"
def __str__(self):
return self.new_field
@property
def my_brand_new_property(self):
return 1
def my_beautiful_method(self):
return 2
class TestModel5(models.Model):
new_field = models.CharField(max_length=10)
class Meta:
verbose_name = "test model"
verbose_name_plural = "test models"
def __str__(self):
return self.new_field
@property
def my_brand_new_property(self):
return 1
def my_beautiful_method(self):
return 2
# Abstract models without str
class AbstractTestModel1(models.Model):
new_field = models.CharField(max_length=10)
class Meta:
abstract = True
@property
def my_brand_new_property(self):
return 1
def my_beautiful_method(self):
return 2
class AbstractTestModel2(Model):
new_field = models.CharField(max_length=10)
class Meta:
abstract = True
@property
def my_brand_new_property(self):
return 1
def my_beautiful_method(self):
return 2
# Abstract models with __str__
class AbstractTestModel3(Model):
new_field = models.CharField(max_length=10)
class Meta:
abstract = True
def __str__(self):
return self.new_field
@property
def my_brand_new_property(self):
return 1
def my_beautiful_method(self):
return 2
class AbstractTestModel4(models.Model):
new_field = models.CharField(max_length=10)
class Meta:
abstract = True
def __str__(self):
return self.new_field
@property
def my_brand_new_property(self):
return 1
def my_beautiful_method(self):
return 2
class AbstractTestModel5(models.Model):
new_field = models.CharField(max_length=10)
class Meta:
abstract = False
def __str__(self):
return self.new_field
@property
def my_brand_new_property(self):
return 1
def my_beautiful_method(self):
return 2

View File

@@ -1,37 +0,0 @@
from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel
test_decorator = lambda func: lambda *args, **kwargs: func(*args, **kwargs)
@receiver(pre_save, sender=MyModel)
@test_decorator
def correct_pre_save_handler():
pass
@test_decorator
@receiver(pre_save, sender=MyModel)
def incorrect_pre_save_handler():
pass
@receiver(pre_save, sender=MyModel)
@receiver(pre_save, sender=MyModel)
@test_decorator
def correct_multiple():
pass
@receiver(pre_save, sender=MyModel)
@receiver(pre_save, sender=MyModel)
def correct_multiple():
pass
@receiver(pre_save, sender=MyModel)
@test_decorator
@receiver(pre_save, sender=MyModel)
def incorrect_multiple():
pass

View File

@@ -1,4 +0,0 @@
#!/usr/bin/python
if __name__ == '__main__':
print('I should be executable.')

View File

@@ -1,2 +0,0 @@
if __name__ == '__main__':
print('I should be executable.')

View File

@@ -1,4 +0,0 @@
#!/usr/bin/python
if __name__ == '__main__':
print('I should be executable.')

View File

@@ -1,2 +0,0 @@
if __name__ == '__main__':
print('I should be executable.')

View File

@@ -1,2 +0,0 @@
if __name__ == '__main__':
print('I should be executable.')

View File

@@ -1,4 +0,0 @@
#!/usr/bin/python
if __name__ == '__main__':
print('I should be executable.')

View File

@@ -1,3 +0,0 @@
import logging
logging.info("Hello {}".format("World!"))

View File

@@ -1,3 +0,0 @@
import logging
logging.info("Hello %s" % "World!")

View File

@@ -1,3 +0,0 @@
import logging
logging.info("Hello" + " " + "World!")

View File

@@ -1,4 +0,0 @@
import logging
name = "world"
logging.info(f"Hello {name}")

View File

@@ -1,3 +0,0 @@
import logging
logging.warn("Hello World!")

View File

@@ -1,8 +0,0 @@
import logging
logging.info(
"Hello world!",
extra={
"name": "foobar",
},
)

View File

@@ -1,8 +0,0 @@
import logging
logging.info(
"Hello world!",
extra=dict(
name="foobar",
),
)

View File

@@ -1,21 +0,0 @@
import logging
import sys
# G201
try:
pass
except:
logging.error("Hello World", exc_info=True)
try:
pass
except:
logging.error("Hello World", exc_info=sys.exc_info())
# OK
try:
pass
except:
logging.error("Hello World", exc_info=False)
logging.error("Hello World", exc_info=sys.exc_info())

View File

@@ -1,21 +0,0 @@
import logging
import sys
# G202
try:
pass
except:
logging.exception("Hello World", exc_info=True)
try:
pass
except:
logging.exception("Hello World", exc_info=sys.exc_info())
# OK
try:
pass
except:
logging.exception("Hello World", exc_info=False)
logging.exception("Hello World", exc_info=True)

View File

@@ -1,7 +0,0 @@
import argparse
from pathlib import Path
parser = argparse.ArgumentParser()
parser.add_argument("target_dir", type=Path)
args = parser.parse_args()
parser.error(f"Target directory {args.target_dir} does not exist")

View File

@@ -1,8 +0,0 @@
import logging
logging.info(
"Hello {world}!",
extra=dict(
world="World",
),
)

View File

@@ -1,8 +0,0 @@
import logging
logging.info(
"Hello {world}!",
extra=dict(
world="{}".format("World"),
),
)

View File

@@ -1,3 +0,0 @@
import logging
logging.info("Hello World!")

View File

@@ -1,3 +0,0 @@
import warnings
warnings.warn("Hello World!")

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env python3
print("Hello, INP001!")

View File

@@ -1,10 +0,0 @@
# no error
all((x.id for x in bar))
all(x.id for x in bar)
all(x.id for x in bar)
any(x.id for x in bar)
any({x.id for x in bar})
# PIE 802
any([x.id for x in bar])
all([x.id for x in bar])

View File

@@ -1,17 +0,0 @@
# error
obj.startswith("foo") or obj.startswith("bar")
# error
obj.endswith("foo") or obj.endswith("bar")
# error
obj.startswith(foo) or obj.startswith(bar)
# error
obj.startswith(foo) or obj.startswith("foo")
# ok
obj.startswith(("foo", "bar"))
# ok
obj.endswith(("foo", "bar"))
# ok
obj.startswith("foo") or obj.endswith("bar")
# ok
obj.startswith("foo") or abc.startswith("bar")

View File

@@ -1,13 +0,0 @@
from typing import ParamSpec, TypeVar, TypeVarTuple
T = TypeVar("T") # OK
TTuple = TypeVarTuple("TTuple") # OK
P = ParamSpec("P") # OK
_T = TypeVar("_T") # OK
_TTuple = TypeVarTuple("_TTuple") # OK
_P = ParamSpec("_P") # OK

View File

@@ -1,13 +0,0 @@
from typing import ParamSpec, TypeVar, TypeVarTuple
T = TypeVar("T") # Error: TypeVars in stubs must start with _
TTuple = TypeVarTuple("TTuple") # Error: TypeVarTuples must also start with _
P = ParamSpec("P") # Error: ParamSpecs must start with _
_T = TypeVar("_T") # OK
_TTuple = TypeVarTuple("_TTuple") # OK
_P = ParamSpec("_P") # OK

View File

@@ -1,14 +0,0 @@
import sys
from sys import version_info as python_version
if sys.version_info < (3, 9): ... # OK
if sys.version_info >= (3, 9): ... # OK
if sys.version_info == (3, 9): ... # OK
if sys.version_info <= (3, 10): ... # OK
if sys.version_info > (3, 10): ... # OK
if python_version > (3, 10): ... # OK

View File

@@ -1,18 +0,0 @@
import sys
from sys import version_info as python_version
if sys.version_info < (3, 9): ... # OK
if sys.version_info >= (3, 9): ... # OK
if sys.version_info == (3, 9): ... # OK
if sys.version_info == (3, 9): ... # Error: PYI006 Use only `<` and `>=` for version info comparisons
if sys.version_info <= (3, 10): ... # Error: PYI006 Use only `<` and `>=` for version info comparisons
if sys.version_info <= (3, 10): ... # Error: PYI006 Use only `<` and `>=` for version info comparisons
if sys.version_info > (3, 10): ... # Error: PYI006 Use only `<` and `>=` for version info comparisons
if python_version > (3, 10): ... # Error: PYI006 Use only `<` and `>=` for version info comparisons

View File

@@ -1,11 +0,0 @@
import sys
if sys.platform == "platform_name_1": ... # OK
if sys.platform != "platform_name_2": ... # OK
if sys.platform in ["linux"]: ... # OK
if sys.platform > 3: ... # OK
if sys.platform == 10.12: ... # OK

View File

@@ -1,11 +0,0 @@
import sys
if sys.platform == "platform_name_1": ... # OK
if sys.platform != "platform_name_2": ... # OK
if sys.platform in ["linux"]: ... # Error: PYI007 Unrecognized sys.platform check
if sys.platform > 3: ... # Error: PYI007 Unrecognized sys.platform check
if sys.platform == 10.12: ... # Error: PYI007 Unrecognized sys.platform check

View File

@@ -1,11 +0,0 @@
import sys
if sys.platform == "linus": ... # OK
if sys.platform != "linux": ... # OK
if sys.platform == "win32": ... # OK
if sys.platform != "darwin": ... # OK
if sys.platform == "cygwin": ... # OK

View File

@@ -1,11 +0,0 @@
import sys
if sys.platform == "linus": ... # Error: PYI008 Unrecognized platform `linus`
if sys.platform != "linux": ... # OK
if sys.platform == "win32": ... # OK
if sys.platform != "darwin": ... # OK
if sys.platform == "cygwin": ... # OK

View File

@@ -1,14 +0,0 @@
def bar():
... # OK
def foo():
pass # OK, since we're not in a stub file
class Bar:
... # OK
class Foo:
pass # OK, since we're not in a stub file

View File

@@ -1,8 +0,0 @@
def bar(): ... # OK
def foo():
pass # ERROR PYI009, since we're in a stub file
class Bar: ... # OK
class Foo:
pass # ERROR PYI009, since we're in a stub file

View File

@@ -1,18 +0,0 @@
def bar():
... # OK
def foo():
"""foo""" # OK
def buzz():
print("buzz") # OK, not in stub file
def foo2():
123 # OK, not in a stub file
def bizz():
x = 123 # OK, not in a stub file

View File

@@ -1,12 +0,0 @@
def bar(): ... # OK
def foo():
"""foo""" # OK, strings are handled by another rule
def buzz():
print("buzz") # ERROR PYI010
def foo2():
123 # ERROR PYI010
def bizz():
x = 123 # ERROR PYI010

View File

@@ -1,79 +0,0 @@
def f12(
x,
y: str = os.pathsep, # OK
) -> None:
...
def f11(*, x: str = "x") -> None: # OK
...
def f13(
x: list[str] = [
"foo",
"bar",
"baz",
] # OK
) -> None:
...
def f14(
x: tuple[str, ...] = (
"foo",
"bar",
"baz",
) # OK
) -> None:
...
def f15(
x: set[str] = {
"foo",
"bar",
"baz",
} # OK
) -> None:
...
def f16(x: frozenset[bytes] = frozenset({b"foo", b"bar", b"baz"})) -> None: # OK
...
def f17(
x: str = "foo" + "bar", # OK
) -> None:
...
def f18(
x: str = b"foo" + b"bar", # OK
) -> None:
...
def f19(
x: object = "foo" + 4, # OK
) -> None:
...
def f20(
x: int = 5 + 5, # OK
) -> None:
...
def f21(
x: complex = 3j - 3j, # OK
) -> None:
...
def f22(
x: complex = -42.5j + 4.3j, # OK
) -> None:
...

View File

@@ -1,63 +0,0 @@
def f12(
x,
y: str = os.pathsep, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f11(*, x: str = "x") -> None: ... # OK
def f13(
x: list[
str
] = [ # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
]
) -> None: ...
def f14(
x: tuple[
str, ...
] = ( # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
)
) -> None: ...
def f15(
x: set[
str
] = { # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
}
) -> None: ...
def f16(
x: frozenset[
bytes
] = frozenset( # Error PYI011 Only simple default values allowed for typed arguments
{b"foo", b"bar", b"baz"}
)
) -> None: ...
def f17(
x: str = "foo" # Error PYI011 Only simple default values allowed for typed arguments
+ "bar",
) -> None: ...
def f18(
x: str = b"foo" # Error PYI011 Only simple default values allowed for typed arguments
+ b"bar",
) -> None: ...
def f19(
x: object = "foo" # Error PYI011 Only simple default values allowed for typed arguments
+ 4,
) -> None: ...
def f20(
x: int = 5
+ 5, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f21(
x: complex = 3j
- 3j, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f22(
x: complex = -42.5j # Error PYI011 Only simple default values allowed for typed arguments
+ 4.3j,
) -> None: ...

View File

@@ -1,79 +0,0 @@
def f12(
x,
y=os.pathsep, # OK
) -> None:
...
def f11(*, x="x") -> None:
... # OK
def f13(
x=[ # OK
"foo",
"bar",
"baz",
]
) -> None:
...
def f14(
x=( # OK
"foo",
"bar",
"baz",
)
) -> None:
...
def f15(
x={ # OK
"foo",
"bar",
"baz",
}
) -> None:
...
def f16(x=frozenset({b"foo", b"bar", b"baz"})) -> None:
... # OK
def f17(
x="foo" + "bar", # OK
) -> None:
...
def f18(
x=b"foo" + b"bar", # OK
) -> None:
...
def f19(
x="foo" + 4, # OK
) -> None:
...
def f20(
x=5 + 5, # OK
) -> None:
...
def f21(
x=3j - 3j, # OK
) -> None:
...
def f22(
x=-42.5j + 4.3j, # OK
) -> None:
...

View File

@@ -1,45 +0,0 @@
def f12(
x,
y=os.pathsep, # Error PYI014
) -> None: ...
def f11(*, x="x") -> None: ... # OK
def f13(
x=[ # Error PYI014
"foo",
"bar",
"baz",
]
) -> None: ...
def f14(
x=( # Error PYI014
"foo",
"bar",
"baz",
)
) -> None: ...
def f15(
x={ # Error PYI014
"foo",
"bar",
"baz",
}
) -> None: ...
def f16(x=frozenset({b"foo", b"bar", b"baz"})) -> None: ... # Error PYI014
def f17(
x="foo" + "bar", # Error PYI014
) -> None: ...
def f18(
x=b"foo" + b"bar", # Error PYI014
) -> None: ...
def f19(
x="foo" + 4, # Error PYI014
) -> None: ...
def f20(
x=5 + 5, # Error PYI014
) -> None: ...
def f21(
x=3j - 3j, # Error PYI014
) -> None: ...
def f22(
x=-42.5j + 4.3j, # Error PYI014
) -> None: ...

View File

@@ -1,14 +0,0 @@
"""foo""" # OK, not in stub
def foo():
"""foo""" # OK, doc strings are allowed in non-stubs
class Bar:
"""bar""" # OK, doc strings are allowed in non-stubs
def bar():
x = 1
"""foo""" # OK, not a doc string

View File

@@ -1,11 +0,0 @@
"""foo""" # ERROR PYI021
def foo():
"""foo""" # ERROR PYI021
class Bar:
"""bar""" # ERROR PYI021
def bar():
x = 1
"""foo""" # OK, not a doc string

View File

@@ -1,73 +0,0 @@
import pytest
@pytest.fixture()
def ok_no_scope():
...
@pytest.fixture(scope="module")
def ok_other_scope():
...
@pytest.fixture(scope="function")
def error():
...
@pytest.fixture(scope="function", name="my_fixture")
def error_multiple_args():
...
@pytest.fixture(name="my_fixture", scope="function")
def error_multiple_args():
...
@pytest.fixture(name="my_fixture", scope="function", **kwargs)
def error_second_arg():
...
# pytest.fixture does not take positional arguments, however this
# tests the general case as we use a helper function that should
# work for all cases.
@pytest.fixture("my_fixture", scope="function")
def error_arg():
...
@pytest.fixture(
scope="function",
name="my_fixture",
)
def error_multiple_args():
...
@pytest.fixture(
name="my_fixture",
scope="function",
)
def error_multiple_args():
...
@pytest.fixture(
"hello",
name,
*args
,
# another comment ,)
scope=\
"function" # some comment ),
,
name2=name, name3="my_fixture", **kwargs
)
def error_multiple_args():
...

View File

@@ -1,41 +0,0 @@
import pytest
def test_ok():
assert something
assert something or something_else
assert something or something_else and something_third
assert not (something and something_else)
assert something, "something message"
assert something or something_else and something_third, "another message"
def test_error():
assert something and something_else
assert something and something_else and something_third
assert something and not something_else
assert something and (something_else or something_third)
assert not something and something_else
assert not (something or something_else)
assert not (something or something_else or something_third)
assert something and something_else == """error
message
"""
# recursive case
assert not (a or not (b or c))
assert not (a or not (b and c))
# detected, but no autofix for messages
assert something and something_else, "error message"
assert not (something or something_else and something_third), "with message"
# detected, but no autofix for mixed conditions (e.g. `a or b and c`)
assert not (something or something_else and something_third)
# detected, but no autofix for parenthesized conditions
assert (
something
and something_else
== """error
message
"""
)

View File

@@ -1,4 +0,0 @@
this_should_be_linted = "double quote string"
this_should_be_linted = u"double quote string"
this_should_be_linted = f"double quote string"
this_should_be_linted = f"double {'quote'} string"

View File

@@ -1,27 +0,0 @@
x = (
"This"
"is"
"not"
)
x = (
"This" \
"is" \
"not"
)
x = (
"This"
"is 'actually'"
"fine"
)
x = (
"This" \
"is 'actually'" \
"fine"
)
if True:
"This can use 'double' quotes"
"But this needs to be changed"

View File

@@ -1,4 +0,0 @@
this_should_be_linted = 'single quote string'
this_should_be_linted = u'double quote string'
this_should_be_linted = f'double quote string'
this_should_be_linted = f'double {"quote"} string'

View File

@@ -1,27 +0,0 @@
x = (
'This'
'is'
'not'
)
x = (
'This' \
'is' \
'not'
)
x = (
'This'
'is "actually"'
'fine'
)
x = (
'This' \
'is "actually"' \
'fine'
)
if True:
'This can use "single" quotes'
'But this needs to be changed'

View File

@@ -1,34 +0,0 @@
try:
y = 6 + "7"
except TypeError:
# RSE102
raise ValueError()
try:
x = 1 / 0
except ZeroDivisionError:
raise
# RSE102
raise TypeError()
# RSE102
raise TypeError ()
# RSE102
raise TypeError \
()
# RSE102
raise TypeError(
)
# RSE102
raise TypeError(
# Hello, world!
)
raise AssertionError
raise AttributeError("test message")

View File

@@ -1,295 +0,0 @@
import builtins
import os
import posix
from posix import abort
import sys as std_sys
import typing
import typing_extensions
import _thread
import _winapi
import pytest
from pytest import xfail as py_xfail
###
# Errors
###
# if/elif/else
def x(y):
if not y:
return 1
# error
def x(y):
if not y:
print() # error
else:
return 2
def x(y):
if not y:
return 1
print() # error
# for
def x(y):
for i in range(10):
if i > 10:
return i
# error
def x(y):
for i in range(10):
if i > 10:
return i
else:
print() # error
# A nonexistent function
def func_unknown(x):
if x > 0:
return False
no_such_function() # error
# A function that does return the control
def func_no_noreturn(x):
if x > 0:
return False
print("", end="") # error
###
# Non-errors
###
# raise as last return
def x(y):
if not y:
return 1
raise Exception
# last line in while loop
def x(y):
while i > 0:
if y > 0:
return 1
y += 1
# exclude empty functions
def x(y):
return None
# return inner with statement
def x(y):
with y:
return 1
async def function():
async with thing as foo:
return foo.bar
# assert as last return
def x(y):
if not y:
return 1
assert False, "Sanity check"
# return value within loop
def bar1(x, y, z):
for i in x:
if i > y:
break
return z
def bar3(x, y, z):
for i in x:
if i > y:
if z:
break
else:
return z
return None
def bar1(x, y, z):
for i in x:
if i < y:
continue
return z
def bar3(x, y, z):
for i in x:
if i < y:
if z:
continue
else:
return z
return None
def prompts(self, foo):
if not foo:
return []
for x in foo:
yield x
yield x + 1
# Functions that never return
def noreturn_exit(x):
if x > 0:
return 1
exit()
def noreturn_quit(x):
if x > 0:
return 1
quit()
def noreturn_builtins_exit(x):
if x > 0:
return 1
builtins.exit()
def noreturn_builtins_quit(x):
if x > 0:
return 1
builtins.quit()
def noreturn_os__exit(x):
if x > 0:
return 1
os._exit(0)
def noreturn_os_abort(x):
if x > 0:
return 1
os.abort()
def noreturn_posix__exit():
if x > 0:
return 1
posix._exit()
def noreturn_posix_abort():
if x > 0:
return 1
posix.abort()
def noreturn_posix_abort_2():
if x > 0:
return 1
abort()
def noreturn_sys_exit():
if x > 0:
return 1
std_sys.exit(0)
def noreturn_typing_assert_never():
if x > 0:
return 1
typing.assert_never(0)
def noreturn_typing_extensions_assert_never():
if x > 0:
return 1
typing_extensions.assert_never(0)
def noreturn__thread_exit():
if x > 0:
return 1
_thread.exit(0)
def noreturn__winapi_exitprocess():
if x > 0:
return 1
_winapi.ExitProcess(0)
def noreturn_pytest_exit():
if x > 0:
return 1
pytest.exit("oof")
def noreturn_pytest_fail():
if x > 0:
return 1
pytest.fail("oof")
def noreturn_pytest_skip():
if x > 0:
return 1
pytest.skip("oof")
def noreturn_pytest_xfail():
if x > 0:
return 1
pytest.xfail("oof")
def noreturn_pytest_xfail_2():
if x > 0:
return 1
py_xfail("oof")
def nested(values):
if not values:
return False
for value in values:
print(value)
def while_true():
while True:
if y > 0:
return 1
y += 1
# match
def x(y):
match y:
case 0:
return 1
case 1:
print() # error
def foo(baz: str) -> str:
return baz

View File

@@ -1,72 +0,0 @@
class BazMeta(type):
_private_count = 1
def __new__(mcs, name, bases, attrs):
if mcs._private_count <= 5:
mcs.some_method()
return super().__new__(mcs, name, bases, attrs)
def some_method():
pass
class Bar:
_private = True
@classmethod
def is_private(cls):
return cls._private
class Foo(metaclass=BazMeta):
def __init__(self):
self.public_thing = "foo"
self._private_thing = "bar"
self.__really_private_thing = "baz"
self.bar = Bar()
def __str__(self):
return "foo"
def get_bar():
if self.bar._private: # SLF001
return None
if self.bar()._private: # SLF001
return None
if Bar._private_thing: # SLF001
return None
if Foo._private_thing:
return None
Foo = Bar()
if Foo._private_thing: # SLF001
return None
return self.bar
def public_func(self):
super().public_func()
def _private_func(self):
super()._private_func()
def __really_private_func(self, arg):
super().__really_private_func(arg)
foo = Foo()
print(foo._private_thing) # SLF001
print(foo.__really_private_thing) # SLF001
print(foo._private_func()) # SLF001
print(foo.__really_private_func(1)) # SLF001
print(foo.bar._private) # SLF001
print(foo()._private_thing) # SLF001
print(foo()._private_thing__) # SLF001
print(foo.public_thing)
print(foo.public_func())
print(foo.__dict__)
print(foo.__str__())
print(foo().__class__)
print(foo._asdict())

View File

@@ -1,96 +0,0 @@
# Errors
if a:
b
elif c:
b
if x == 1:
for _ in range(20):
print("hello")
elif x == 2:
for _ in range(20):
print("hello")
if x == 1:
if True:
for _ in range(20):
print("hello")
elif x == 2:
if True:
for _ in range(20):
print("hello")
if x == 1:
if True:
for _ in range(20):
print("hello")
elif False:
for _ in range(20):
print("hello")
elif x == 2:
if True:
for _ in range(20):
print("hello")
elif False:
for _ in range(20):
print("hello")
if (
x == 1
and y == 2
and z == 3
and a == 4
and b == 5
and c == 6
and d == 7
and e == 8
and f == 9
and g == 10
and h == 11
and i == 12
and j == 13
and k == 14
):
pass
elif 1 == 2:
pass
if result.eofs == "O":
pass
elif result.eofs == "S":
skipped = 1
elif result.eofs == "F":
errors = 1
elif result.eofs == "E":
errors = 1
# OK
def complicated_calc(*arg, **kwargs):
return 42
def foo(p):
if p == 2:
return complicated_calc(microsecond=0)
elif p == 3:
return complicated_calc(microsecond=0, second=0)
return None
a = False
b = True
c = True
if a:
z = 1
elif b:
z = 2
elif c:
z = 1
# False negative (or arguably a different rule)
if result.eofs == "F":
errors = 1
else:
errors = 1

View File

@@ -1,86 +0,0 @@
# Errors
a = "hello"
# SIM116
if a == "foo":
return "bar"
elif a == "bar":
return "baz"
elif a == "boo":
return "ooh"
else:
return 42
# SIM116
if a == 1:
return (1, 2, 3)
elif a == 2:
return (4, 5, 6)
elif a == 3:
return (7, 8, 9)
else:
return (10, 11, 12)
# SIM116
if a == 1:
return (1, 2, 3)
elif a == 2:
return (4, 5, 6)
elif a == 3:
return (7, 8, 9)
# SIM116
if a == "hello 'sir'":
return (1, 2, 3)
elif a == 'goodbye "mam"':
return (4, 5, 6)
elif a == """Fairwell 'mister'""":
return (7, 8, 9)
else:
return (10, 11, 12)
# SIM116
if a == b"one":
return 1
elif a == b"two":
return 2
elif a == b"three":
return 3
# SIM116
if a == "hello 'sir'":
return ("hello'", 'hi"', 3)
elif a == 'goodbye "mam"':
return (4, 5, 6)
elif a == """Fairwell 'mister'""":
return (7, 8, 9)
else:
return (10, 11, 12)
# OK
if a == "foo":
return "bar"
elif a == "bar":
return baz()
elif a == "boo":
return "ooh"
else:
return 42
# OK
if a == b"one":
return 1
elif b == b"two":
return 2
elif a == b"three":
return 3
# SIM116
if func_name == "create":
return "A"
elif func_name == "modify":
return "M"
elif func_name == "remove":
return "D"
elif func_name == "move":
return "MV"

View File

@@ -1,29 +0,0 @@
# Errors
"yoda" == compare # SIM300
"yoda" == compare # SIM300
42 == age # SIM300
("a", "b") == compare # SIM300
"yoda" <= compare # SIM300
"yoda" < compare # SIM300
42 > age # SIM300
YODA == age # SIM300
YODA > age # SIM300
YODA >= age # SIM300
JediOrder.YODA == age # SIM300
0 < (number - 100) # SIM300
SomeClass().settings.SOME_CONSTANT_VALUE > (60 * 60) # SIM300
# OK
compare == "yoda"
age == 42
compare == ("a", "b")
x == y
"yoda" == compare == 1
"yoda" == compare == someothervar
"yoda" == "yoda"
age == YODA
age < YODA
age <= YODA
YODA == YODA
age == JediOrder.YODA
(number - 100) > 0

View File

@@ -1,28 +0,0 @@
# OK
import other
import other.example
from other import example
# TID252
from . import sibling
from .sibling import example
from .. import parent
from ..parent import example
from ... import grandparent
from ...grandparent import example
from .parent import hello
from .\
parent import \
hello_world
from \
..parent\
import \
world_hello
from ..... import ultragrantparent
from ...... import ultragrantparent
from ....... import ultragrantparent
from ......... import ultragrantparent
from ........................... import ultragrantparent
from .....parent import ultragrantparent
from .........parent import ultragrantparent
from ...........................parent import ultragrantparent

View File

@@ -1,8 +0,0 @@
from typing import TYPE_CHECKING, Any, ClassVar
import attrs
from ..protocol import commands, definitions, responses
from ..server import example
from .. import server
from . import logger, models

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