Compare commits

..

3 Commits

Author SHA1 Message Date
Charlie Marsh
6e97c9438a Merge branch 'main' into github-2923 2023-03-02 15:55:55 -05:00
Charlie Marsh
ea86edf12e Rename, etc. 2023-03-02 15:50:10 -05:00
Martin Kagamino Lehoux
2558384817 Replace tuples with type union in isinstance or issubclass calls (#2923) 2023-03-02 21:07:00 +01:00
1777 changed files with 70287 additions and 101190 deletions

View File

@@ -1,6 +1,5 @@
[alias]
dev = "run --package ruff_dev --bin ruff_dev"
benchmark = "bench -p ruff_benchmark --"
[target.'cfg(all())']
rustflags = [
@@ -26,11 +25,4 @@ rustflags = [
"-Wclippy::print_stdout",
"-Wclippy::print_stderr",
"-Wclippy::dbg_macro",
"-Wclippy::empty_drop",
"-Wclippy::empty_structs_with_brackets",
"-Wclippy::exit",
"-Wclippy::get_unwrap",
"-Wclippy::rc_buffer",
"-Wclippy::rc_mutex",
"-Wclippy::rest_pat_in_fully_bound_structs",
]

View File

@@ -1,11 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "12:00"
timezone: "America/New_York"
commit-message:
prefix: "ci(deps)"

View File

@@ -1,135 +0,0 @@
name: Benchmark
on:
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
jobs:
run-benchmark:
if: github.event_name == 'pull_request'
name: "Run | ${{ matrix.os }}"
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: "PR - Checkout Branch"
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: "PR - Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v2
- name: "PR - Build benchmarks"
uses: actions-rs/cargo@v1
with:
command: bench
args: -p ruff_benchmark --no-run
- name: "PR - Run benchmarks"
run: cargo benchmark --save-baseline=pr
- name: "Main - Checkout Branch"
uses: actions/checkout@v3
with:
clean: false
ref: main
- name: "Main - Install Rust toolchain"
run: rustup show
- name: "Main - Build benchmarks"
uses: actions-rs/cargo@v1
with:
command: bench
args: -p ruff_benchmark --no-run
- name: "Main - Run benchmarks"
run: cargo benchmark --save-baseline=main
- name: "Upload benchmark results"
uses: actions/upload-artifact@v3
with:
name: benchmark-results-${{ matrix.os }}
path: ./target/criterion
# Cleanup
- name: Remove Criterion Artifact
uses: JesseTG/rm@v1.0.3
with:
path: ./target/criterion
benchmark-compare:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
name: Compare
needs:
- run-benchmark
steps:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo-binstall"
uses: taiki-e/install-action@cargo-binstall
- name: "Install critcmp"
run: cargo binstall critcmp -y
- name: "Linux | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-ubuntu-latest
path: ./target/criterion
- name: "Linux | Compare benchmark results"
shell: bash
run: |
echo "### Benchmark" >> summary.md
echo "#### Linux" >> summary.md
echo "\`\`\`" >> summary.md
critcmp main pr >> summary.md
echo "\`\`\`" >> summary.md
echo "" >> summary.md
- name: "Linux | Cleanup benchmark results"
run: rm -rf ./target/criterion
- name: "Windows | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-windows-latest
path: ./target/criterion
- name: "Windows | Compare benchmark results"
shell: bash
run: |
echo "#### Windows" >> summary.md
echo "\`\`\`" >> summary.md
critcmp main pr >> summary.md
echo "\`\`\`" >> summary.md
echo "" >> summary.md
echo ${{ github.event.pull_request.number }} > pr-number
cat summary.md > $GITHUB_STEP_SUMMARY
- uses: actions/upload-artifact@v3
name: Upload PR Number
with:
name: pr-number
path: pr-number
- uses: actions/upload-artifact@v3
name: Upload Summary
with:
name: summary
path: summary.md

View File

@@ -17,6 +17,20 @@ env:
RUSTUP_MAX_RETRIES: 10
jobs:
cargo-build:
name: "cargo build"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- 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
cargo-fmt:
name: "cargo fmt"
runs-on: ubuntu-latest
@@ -34,7 +48,7 @@ jobs:
- name: "Install Rust toolchain"
run: |
rustup component add clippy
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@v1
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo-clippy-wasm:
@@ -46,8 +60,8 @@ jobs:
run: |
rustup component add clippy
rustup target add wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
- run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features -- -D warnings
- uses: Swatinem/rust-cache@v1
- run: cargo clippy -p ruff --target wasm32-unknown-unknown --all-features -- -D warnings
cargo-test:
strategy:
@@ -59,9 +73,9 @@ jobs:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@v1
- run: cargo install cargo-insta
- run: pip install black[d]==23.1.0
- run: pip install black[d]==22.12.0
- name: "Run tests (Ubuntu)"
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
@@ -79,30 +93,6 @@ jobs:
env:
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
RUSTDOCFLAGS: "-D warnings"
- uses: actions/upload-artifact@v3
if: ${{ matrix.os == 'ubuntu-latest' }}
with:
name: ruff
path: target/debug/ruff
cargo-test-wasm:
runs-on: ubuntu-latest
name: "cargo test (wasm)"
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@v3
with:
node-version: 18
cache: "npm"
cache-dependency-path: playground/package-lock.json
- uses: jetli/wasm-pack-action@v0.4.0
- uses: Swatinem/rust-cache@v2
- name: "Run wasm-pack"
run: |
cd crates/ruff_wasm
wasm-pack test --node
scripts:
name: "test scripts"
@@ -110,16 +100,29 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup component add rustfmt
- uses: Swatinem/rust-cache@v2
- run: ./scripts/add_rule.py --name DoTheThing --prefix PL --code C0999 --linter pylint
run: rustup show
- uses: Swatinem/rust-cache@v1
- run: ./scripts/add_rule.py --name DoTheThing --code PLC999 --linter pylint
- run: cargo check
- run: cargo fmt --all --check
- run: |
./scripts/add_plugin.py test --url https://pypi.org/project/-test/0.1.0/ --prefix TST
./scripts/add_rule.py --name FirstRule --prefix TST --code 001 --linter test
./scripts/add_rule.py --name FirstRule --code TST001 --linter test
- run: cargo check
- run: cargo fmt --all --check
maturin-build:
name: "maturin build"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- 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"
@@ -129,104 +132,3 @@ jobs:
- uses: crate-ci/typos@master
with:
files: .
ecosystem:
name: "ecosystem"
runs-on: ubuntu-latest
needs: cargo-test
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.11"
- uses: actions/download-artifact@v3
name: Download Ruff binary
id: ruff-target
with:
name: ruff
path: target/debug
- uses: dawidd6/action-download-artifact@v2
name: Download base results
with:
name: ruff
branch: ${{ github.event.pull_request.base.ref }}
check_artifacts: true
- name: Run ecosystem check
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
scripts/check_ecosystem.py ruff ${{ steps.ruff-target.outputs.download-path }}/ruff | tee ecosystem-result
cat ecosystem-result > $GITHUB_STEP_SUMMARY
echo ${{ github.event.number }} > pr-number
- uses: actions/upload-artifact@v3
name: Upload PR Number
with:
name: pr-number
path: pr-number
- uses: actions/upload-artifact@v3
name: Upload Results
with:
name: ecosystem-result
path: ecosystem-result
cargo-udeps:
name: "cargo udeps"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Install nightly Rust toolchain"
# Only pinned to make caching work, update freely
run: rustup toolchain install nightly-2023-03-30
- uses: Swatinem/rust-cache@v2
- name: "Install cargo-udeps"
uses: taiki-e/install-action@cargo-udeps
- name: "Run cargo-udeps"
run: |
unused_dependencies=$(cargo +nightly-2023-03-30 udeps > unused.txt && cat unused.txt | cut -d $'\n' -f 2-)
if [ -z "$unused_dependencies" ]; then
echo "No unused dependencies found" > $GITHUB_STEP_SUMMARY
exit 0
else
echo "Found unused dependencies" > $GITHUB_STEP_SUMMARY
echo '```console' >> $GITHUB_STEP_SUMMARY
echo "$unused_dependencies" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
exit 1
fi
pre-commit:
name: "pre-commit"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v2
- name: "Install pre-commit"
run: pip install pre-commit
- name: "Cache pre-commit"
uses: actions/cache@v3
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
- name: "Run pre-commit"
run: |
echo '```console' > $GITHUB_STEP_SUMMARY
# Enable color output for pre-commit and remove it for the summary
SKIP=cargo-fmt,clippy,dev-generate-all pre-commit run --all-files --show-diff-on-failure --color=always | \
tee >(sed -E 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[mGK]//g' >> $GITHUB_STEP_SUMMARY) >&1
exit_code=${PIPESTATUS[0]}
echo '```' >> $GITHUB_STEP_SUMMARY
exit $exit_code

View File

@@ -15,7 +15,7 @@ jobs:
- uses: actions/setup-python@v4
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@v1
- name: "Install dependencies"
run: |
pip install -r docs/requirements.txt

View File

@@ -133,7 +133,7 @@ jobs:
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
- uses: uraimo/run-on-arch-action@v2.5.0
if: matrix.target != 'ppc64'
name: Install built wheel
with:
@@ -206,7 +206,7 @@ jobs:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2
args: --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
- uses: uraimo/run-on-arch-action@v2
- uses: uraimo/run-on-arch-action@master
name: Install built wheel
with:
arch: ${{ matrix.platform.arch }}

View File

@@ -28,7 +28,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_wasm
run: wasm-pack build --target web --out-dir ../../playground/src/pkg crates/ruff
- name: "Install Node dependencies"
run: npm ci
working-directory: playground

View File

@@ -1,84 +0,0 @@
name: PR Check Comment
on:
workflow_run:
workflows: [CI, Benchmark]
types: [completed]
workflow_dispatch:
inputs:
workflow_run_id:
description: The ecosystem workflow that triggers the workflow run
required: true
permissions:
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: dawidd6/action-download-artifact@v2
name: Download PR Number
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
- name: Extract PR Number
id: pr-number
run: |
if [[ -f pr-number ]]
then
echo "pr-number=$(<pr-number)" >> $GITHUB_OUTPUT
fi
- uses: dawidd6/action-download-artifact@v2
name: "Download Ecosystem Result"
id: download-ecosystem-result
if: steps.pr-number.outputs.pr-number
with:
name: ecosystem-result
workflow: ci.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/ecosystem
if_no_artifact_found: ignore
- uses: dawidd6/action-download-artifact@v2
name: "Download Benchmark Result"
id: download-benchmark-result
if: steps.pr-number.outputs.pr-number
with:
name: summary
workflow: benchmark.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/benchmark
if_no_artifact_found: ignore
- name: Generate Comment
id: generate-comment
if: steps.download-ecosystem-result.outputs.found_artifact == 'true' || steps.download-benchmark-result.outputs.found_artifact == 'true'
run: |
echo 'comment<<EOF' >> $GITHUB_OUTPUT
echo '## PR Check Results' >> $GITHUB_OUTPUT
if [[ -f pr/ecosystem/ecosystem-result ]]
then
echo "### Ecosystem" >> $GITHUB_OUTPUT
cat pr/ecosystem/ecosystem-result >> $GITHUB_OUTPUT
echo "" >> $GITHUB_OUTPUT
fi
if [[ -f pr/benchmark/summary.md ]]
then
cat pr/benchmark/summary.md >> $GITHUB_OUTPUT
fi
echo 'EOF' >> $GITHUB_OUTPUT
- name: Create or update comment
if: steps.generate-comment.outputs.comment
uses: thollander/actions-comment-pull-request@v2
with:
pr_number: ${{ steps.pr-number.outputs.pr-number }}
message: ${{ steps.generate-comment.outputs.comment }}
comment_tag: PR Check Results

View File

@@ -208,7 +208,7 @@ jobs:
target: ${{ matrix.platform.target }}
manylinux: auto
args: --release --out dist
- uses: uraimo/run-on-arch-action@v2
- uses: uraimo/run-on-arch-action@v2.5.0
if: matrix.platform.arch != 'ppc64'
name: Install built wheel
with:
@@ -309,7 +309,7 @@ jobs:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2
args: --release --out dist
- uses: uraimo/run-on-arch-action@v2
- uses: uraimo/run-on-arch-action@master
name: Install built wheel
with:
arch: ${{ matrix.platform.arch }}

1
.gitignore vendored
View File

@@ -3,7 +3,6 @@
crates/ruff/resources/test/cpython
mkdocs.yml
.overrides
github_search.jsonl
###
# Rust.gitignore

View File

@@ -1,7 +1,7 @@
fail_fast: true
repos:
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.12.1
rev: v0.10.1
hooks:
- id: validate-pyproject
@@ -28,17 +28,17 @@ repos:
- id: cargo-fmt
name: cargo fmt
entry: cargo fmt --
language: system
language: rust
types: [rust]
- id: clippy
name: clippy
entry: cargo clippy --workspace --all-targets --all-features -- -D warnings
language: system
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: system
language: rust
types_or: [python, pyi]
require_serial: true
exclude: |
@@ -49,7 +49,7 @@ repos:
- id: dev-generate-all
name: dev-generate-all
entry: cargo dev generate-all
language: system
language: rust
pass_filenames: false
exclude: target

View File

@@ -1,40 +1,5 @@
# Breaking Changes
## 0.0.260
### Fixes are now represented as a list of edits ([#3709](https://github.com/charliermarsh/ruff/pull/3709))
Previously, Ruff represented each fix as a single edit, which prohibited Ruff from automatically
fixing violations that required multiple edits across a file. As such, Ruff now represents each
fix as a list of edits.
This primarily affects the JSON API. Ruff's JSON representation used to represent the `fix` field as
a single edit, like so:
```json
{
"message": "Remove unused import: `sys`",
"content": "",
"location": {"row": 1, "column": 0},
"end_location": {"row": 2, "column": 0}
}
```
The updated representation instead includes a list of edits:
```json
{
"message": "Remove unused import: `sys`",
"edits": [
{
"content": "",
"location": {"row": 1, "column": 0},
"end_location": {"row": 2, "column": 0},
}
]
}
```
## 0.0.246
### `multiple-statements-on-one-line-def` (`E704`) was removed ([#2773](https://github.com/charliermarsh/ruff/pull/2773))
@@ -180,4 +145,4 @@ default.
`pyproject.toml` files are now resolved hierarchically, such that for each Python file, we find
the first `pyproject.toml` file in its path, and use that to determine its lint settings.
See the [documentation](https://beta.ruff.rs/docs/configuration/#python-file-discovery) for more.
See the [README](https://github.com/charliermarsh/ruff#pyprojecttoml-discovery) for more.

View File

@@ -104,7 +104,7 @@ 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 `#[violation]` to see examples.
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`
@@ -115,8 +115,9 @@ At a high level, the steps involved in adding a new lint rule are as follows:
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
contain a struct defined via `#[violation]`, along with a function that creates the violation
based on any required inputs.
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.)
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
@@ -214,20 +215,6 @@ 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).
## Ecosystem CI
GitHub Actions will run your changes against a number of real-world projects from GitHub and
report on any diagnostic differences. You can also run those checks locally via:
```shell
python scripts/check_ecosystem.py path/to/your/ruff path/to/older/ruff
```
You can also run the Ecosystem CI check in a Docker container across a larger set of projects by
downloading the [`known-github-tomls.json`](https://github.com/akx/ruff-usage-aggregate/blob/master/data/known-github-tomls.jsonl)
as `github_search.jsonl` and following the instructions in [scripts/Dockerfile.ecosystem](scripts/Dockerfile.ecosystem).
Note that this check will take a while to run.
## Benchmarks
First, clone [CPython](https://github.com/python/cpython). It's a large and diverse Python codebase,

1189
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,54 +3,31 @@ members = ["crates/*"]
[workspace.package]
edition = "2021"
rust-version = "1.69"
homepage = "https://beta.ruff.rs/docs/"
documentation = "https://beta.ruff.rs/docs/"
repository = "https://github.com/charliermarsh/ruff"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
rust-version = "1.67.0"
[workspace.dependencies]
anyhow = { version = "1.0.69" }
bitflags = { version = "2.1.0" }
chrono = { version = "0.4.23", default-features = false, features = ["clock"] }
clap = { version = "4.1.8", features = ["derive"] }
colored = { version = "2.0.0" }
filetime = { version = "0.2.20" }
glob = { version = "0.3.1" }
globset = { version = "0.4.10" }
ignore = { version = "0.4.20" }
insta = { version = "1.28.0" }
is-macro = { version = "0.2.2" }
anyhow = { version = "1.0.66" }
clap = { version = "4.0.1", features = ["derive"] }
itertools = { version = "0.10.5" }
is-macro = { version = "0.2.2" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e5beb532fdd1e209ad2dbb470438" }
log = { version = "0.4.17" }
nohash-hasher = { version = "0.2.0" }
once_cell = { version = "1.17.1" }
path-absolutize = { version = "3.0.14" }
proc-macro2 = { version = "1.0.51" }
quote = { version = "1.0.23" }
regex = { version = "1.7.1" }
once_cell = { version = "1.16.0" }
regex = { version = "1.6.0" }
rustc-hash = { version = "1.1.0" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "c15f670f2c30cfae6b41a1874893590148c74bc4" }
rustpython-parser = { features = [
"lalrpop",
"serde",
], git = "https://github.com/RustPython/RustPython.git", rev = "c15f670f2c30cfae6b41a1874893590148c74bc4" }
schemars = { version = "0.8.12" }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.93", features = ["preserve_order"] }
shellexpand = { version = "3.0.0" }
similar = { version = "2.2.1" }
smallvec = { version = "1.10.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" }
schemars = { version = "0.8.11" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
syn = { version = "2.0.15" }
test-case = { version = "3.0.0" }
textwrap = { version = "0.16.0" }
toml = { version = "0.7.2" }
toml = { version = "0.6.0" }
[profile.release]
lto = "fat"
panic = "abort"
lto = "thin"
codegen-units = 1
opt-level = 3
[profile.dev.package.insta]
opt-level = 3
@@ -62,9 +39,3 @@ opt-level = 3
# https://github.com/bytecodealliance/wasm-tools/blob/b5c3d98e40590512a3b12470ef358d5c7b983b15/crates/wasmparser/src/limits.rs#L29
[profile.dev.package.rustpython-parser]
opt-level = 1
# Use the `--profile release-debug` flag to show symbols in release mode.
# e.g. `cargo build --profile release-debug`
[profile.release-debug]
inherits = "release"
debug = 1

35
LICENSE
View File

@@ -195,15 +195,6 @@ are:
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
- flake8-gettext, licensed as follows:
"""
BSD Zero Clause License
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""
- flake8-implicit-str-concat, licensed as follows:
"""
The MIT License (MIT)
@@ -402,6 +393,7 @@ are:
THE SOFTWARE.
"""
- autoflake, licensed as follows:
"""
Copyright (C) 2012-2018 Steven Myint
@@ -425,31 +417,6 @@ are:
SOFTWARE.
"""
- autotyping, licensed as follows:
"""
MIT License
Copyright (c) 2023 Jelle Zijlstra
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- Flake8, licensed as follows:
"""
== Flake8 License (MIT) ==

162
README.md
View File

@@ -47,16 +47,16 @@ all while executing tens or hundreds of times faster than any individual tool.
Ruff is extremely actively developed and used in major open-source projects like:
- [Apache Airflow](https://github.com/apache/airflow)
- [pandas](https://github.com/pandas-dev/pandas)
- [FastAPI](https://github.com/tiangolo/fastapi)
- [Hugging Face](https://github.com/huggingface/transformers)
- [Pandas](https://github.com/pandas-dev/pandas)
- [Transformers (Hugging Face)](https://github.com/huggingface/transformers)
- [Apache Airflow](https://github.com/apache/airflow)
- [SciPy](https://github.com/scipy/scipy)
...and many more.
Ruff is backed by [Astral](https://astral.sh). Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff),
or the original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster) or
the most recent [project update](https://notes.crmarsh.com/ruff-the-first-200-releases).
## Testimonials
@@ -137,7 +137,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.263'
rev: 'v0.0.253'
hooks:
- id: ruff
```
@@ -145,20 +145,6 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
Ruff can also be used as a [VS Code extension](https://github.com/charliermarsh/ruff-vscode) or
alongside any other editor through the [Ruff LSP](https://github.com/charliermarsh/ruff-lsp).
Ruff can also be used as a [GitHub Action](https://github.com/features/actions) via
[`ruff-action`](https://github.com/chartboost/ruff-action):
```yaml
name: Ruff
on: [ push, pull_request ]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: chartboost/ruff-action@v1
```
### Configuration
Ruff can be configured through a `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file (see:
@@ -174,7 +160,7 @@ select = ["E", "F"]
ignore = []
# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
fixable = ["A", "B", "C", "D", "E", "F", "..."]
unfixable = []
# Exclude a variety of commonly ignored directories.
@@ -241,55 +227,6 @@ stylistic rules made obsolete by the use of an autoformatter, like
If you're just getting started with Ruff, **the default rule set is a great place to start**: it
catches a wide variety of common errors (like unused imports) with zero configuration.
Beyond the defaults, Ruff re-implements some of the most popular Flake8 plugins and related code
quality tools, including:
- [autoflake](https://pypi.org/project/autoflake/)
- [eradicate](https://pypi.org/project/eradicate/)
- [flake8-2020](https://pypi.org/project/flake8-2020/)
- [flake8-annotations](https://pypi.org/project/flake8-annotations/)
- [flake8-bandit](https://pypi.org/project/flake8-bandit/) ([#1646](https://github.com/charliermarsh/ruff/issues/1646))
- [flake8-blind-except](https://pypi.org/project/flake8-blind-except/)
- [flake8-boolean-trap](https://pypi.org/project/flake8-boolean-trap/)
- [flake8-bugbear](https://pypi.org/project/flake8-bugbear/)
- [flake8-builtins](https://pypi.org/project/flake8-builtins/)
- [flake8-commas](https://pypi.org/project/flake8-commas/)
- [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
- [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
- [flake8-debugger](https://pypi.org/project/flake8-debugger/)
- [flake8-django](https://pypi.org/project/flake8-django/)
- [flake8-docstrings](https://pypi.org/project/flake8-docstrings/)
- [flake8-eradicate](https://pypi.org/project/flake8-eradicate/)
- [flake8-errmsg](https://pypi.org/project/flake8-errmsg/)
- [flake8-executable](https://pypi.org/project/flake8-executable/)
- [flake8-gettext](https://pypi.org/project/flake8-gettext/)
- [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/)
- [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions)
- [flake8-logging-format](https://pypi.org/project/flake8-logging-format/)
- [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420)
- [flake8-pie](https://pypi.org/project/flake8-pie/)
- [flake8-print](https://pypi.org/project/flake8-print/)
- [flake8-pyi](https://pypi.org/project/flake8-pyi/)
- [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/)
- [flake8-quotes](https://pypi.org/project/flake8-quotes/)
- [flake8-raise](https://pypi.org/project/flake8-raise/)
- [flake8-return](https://pypi.org/project/flake8-return/)
- [flake8-self](https://pypi.org/project/flake8-self/)
- [flake8-simplify](https://pypi.org/project/flake8-simplify/)
- [flake8-super](https://pypi.org/project/flake8-super/)
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
- [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
- [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
- [isort](https://pypi.org/project/isort/)
- [mccabe](https://pypi.org/project/mccabe/)
- [pandas-vet](https://pypi.org/project/pandas-vet/)
- [pep8-naming](https://pypi.org/project/pep8-naming/)
- [pydocstyle](https://pypi.org/project/pydocstyle/)
- [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks) ([#980](https://github.com/charliermarsh/ruff/issues/980))
- [pyupgrade](https://pypi.org/project/pyupgrade/)
- [tryceratops](https://pypi.org/project/tryceratops/)
- [yesqa](https://pypi.org/project/yesqa/)
<!-- End section: Rules -->
For a complete enumeration of the supported rules, see [_Rules_](https://beta.ruff.rs/docs/rules/).
@@ -332,68 +269,43 @@ Ruff is released under the MIT license.
## Who's Using Ruff?
Ruff is used by a number of major open-source projects and companies, including:
Ruff is used in a number of major open-source projects, including:
- Amazon ([AWS SAM](https://github.com/aws/serverless-application-model))
- [Apache Airflow](https://github.com/apache/airflow)
- AstraZeneca ([Magnus](https://github.com/AstraZeneca/magnus-core))
- Benchling ([Refac](https://github.com/benchling/refac))
- [Babel](https://github.com/python-babel/babel)
- [Bokeh](https://github.com/bokeh/bokeh)
- [Cryptography (PyCA)](https://github.com/pyca/cryptography)
- [Dagger](https://github.com/dagger/dagger)
- [Dagster](https://github.com/dagster-io/dagster)
- [DVC](https://github.com/iterative/dvc)
- [pandas](https://github.com/pandas-dev/pandas)
- [FastAPI](https://github.com/tiangolo/fastapi)
- [Gradio](https://github.com/gradio-app/gradio)
- [Great Expectations](https://github.com/great-expectations/great_expectations)
- Hugging Face ([Transformers](https://github.com/huggingface/transformers), [Datasets](https://github.com/huggingface/datasets), [Diffusers](https://github.com/huggingface/diffusers))
- [Hatch](https://github.com/pypa/hatch)
- [Home Assistant](https://github.com/home-assistant/core)
- [Ibis](https://github.com/ibis-project/ibis)
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
- [LangChain](https://github.com/hwchase17/langchain)
- [LlamaIndex](https://github.com/jerryjliu/llama_index)
- Matrix ([Synapse](https://github.com/matrix-org/synapse))
- Meltano ([Meltano CLI](https://github.com/meltano/meltano), [Singer SDK](https://github.com/meltano/sdk))
- Modern Treasury ([Python SDK](https://github.com/Modern-Treasury/modern-treasury-python-sdk))
- Mozilla ([Firefox](https://github.com/mozilla/gecko-dev))
- [MegaLinter](https://github.com/oxsecurity/megalinter)
- Microsoft ([Semantic Kernel](https://github.com/microsoft/semantic-kernel), [ONNX Runtime](https://github.com/microsoft/onnxruntime))
- Netflix ([Dispatch](https://github.com/Netflix/dispatch))
- [Neon](https://github.com/neondatabase/neon)
- [ONNX](https://github.com/onnx/onnx)
- [OpenBB](https://github.com/OpenBB-finance/OpenBBTerminal)
- [PDM](https://github.com/pdm-project/pdm)
- [PaddlePaddle](https://github.com/PaddlePaddle/Paddle)
- [Pandas](https://github.com/pandas-dev/pandas)
- [Poetry](https://github.com/python-poetry/poetry)
- [Polars](https://github.com/pola-rs/polars)
- [PostHog](https://github.com/PostHog/posthog)
- Prefect ([Python SDK](https://github.com/PrefectHQ/prefect), [Marvin](https://github.com/PrefectHQ/marvin))
- [Pydantic](https://github.com/pydantic/pydantic)
- [PyInstaller](https://github.com/pyinstaller/pyinstaller)
- [Pylint](https://github.com/PyCQA/pylint)
- [Pynecone](https://github.com/pynecone-io/pynecone)
- [Robyn](https://github.com/sansyrox/robyn)
- Scale AI ([Launch SDK](https://github.com/scaleapi/launch-python-client))
- Snowflake ([SnowCLI](https://github.com/Snowflake-Labs/snowcli))
- [Saleor](https://github.com/saleor/saleor)
- [Transformers (Hugging Face)](https://github.com/huggingface/transformers)
- [Diffusers (Hugging Face)](https://github.com/huggingface/diffusers)
- [Apache Airflow](https://github.com/apache/airflow)
- [SciPy](https://github.com/scipy/scipy)
- [Sphinx](https://github.com/sphinx-doc/sphinx)
- [Stable Baselines3](https://github.com/DLR-RM/stable-baselines3)
- [Starlite](https://github.com/starlite-api/starlite)
- [The Algorithms](https://github.com/TheAlgorithms/Python)
- [Vega-Altair](https://github.com/altair-viz/altair)
- WordPress ([Openverse](https://github.com/WordPress/openverse))
- [ZenML](https://github.com/zenml-io/zenml)
- [Zulip](https://github.com/zulip/zulip)
- [build (PyPA)](https://github.com/pypa/build)
- [Bokeh](https://github.com/bokeh/bokeh)
- [Pydantic](https://github.com/pydantic/pydantic)
- [Dagster](https://github.com/dagster-io/dagster)
- [Dagger](https://github.com/dagger/dagger)
- [Sphinx](https://github.com/sphinx-doc/sphinx)
- [Hatch](https://github.com/pypa/hatch)
- [PDM](https://github.com/pdm-project/pdm)
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
- [Great Expectations](https://github.com/great-expectations/great_expectations)
- [ONNX](https://github.com/onnx/onnx)
- [Polars](https://github.com/pola-rs/polars)
- [Ibis](https://github.com/ibis-project/ibis)
- [Synapse (Matrix)](https://github.com/matrix-org/synapse)
- [SnowCLI (Snowflake)](https://github.com/Snowflake-Labs/snowcli)
- [Dispatch (Netflix)](https://github.com/Netflix/dispatch)
- [Saleor](https://github.com/saleor/saleor)
- [Pynecone](https://github.com/pynecone-io/pynecone)
- [OpenBB](https://github.com/OpenBB-finance/OpenBBTerminal)
- [Home Assistant](https://github.com/home-assistant/core)
- [Pylint](https://github.com/PyCQA/pylint)
- [Cryptography (PyCA)](https://github.com/pyca/cryptography)
- [cibuildwheel (PyPA)](https://github.com/pypa/cibuildwheel)
- [delta-rs](https://github.com/delta-io/delta-rs)
- [build (PyPA)](https://github.com/pypa/build)
- [Babel](https://github.com/python-babel/babel)
- [featuretools](https://github.com/alteryx/featuretools)
- [meson-python](https://github.com/mesonbuild/meson-python)
- [nox](https://github.com/wntrblm/nox)
- [ZenML](https://github.com/zenml-io/zenml)
- [delta-rs](https://github.com/delta-io/delta-rs)
## License

View File

@@ -5,4 +5,3 @@ extend-exclude = ["snapshots", "black"]
trivias = "trivias"
hel = "hel"
whos = "whos"
spawnve = "spawnve"

View File

@@ -1,18 +1,17 @@
[package]
name = "flake8-to-ruff"
version = "0.0.263"
version = "0.0.253"
edition = { workspace = true }
rust-version = { workspace = true }
[dependencies]
ruff = { path = "../ruff", default-features = false }
anyhow = { workspace = true }
clap = { workspace = true }
colored = { 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 }

View File

@@ -85,9 +85,8 @@ flake8-to-ruff path/to/.flake8 --plugin flake8-builtins --plugin flake8-quotes
ignore unsupported options in the `.flake8` file (or equivalent). (Similarly, Ruff has a few
configuration options that don't exist in Flake8.)
1. Ruff will omit any rule codes that are unimplemented or unsupported by Ruff, including rule
codes from unsupported plugins. (See the
[documentation](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) for the complete
list of supported plugins.)
codes from unsupported plugins. (See the [Ruff README](https://github.com/charliermarsh/ruff#user-content-how-does-ruff-compare-to-flake8)
for the complete list of supported plugins.)
## License

View File

@@ -46,15 +46,8 @@ fn main() -> Result<()> {
.map(|tool| ExternalConfig {
black: tool.black.as_ref(),
isort: tool.isort.as_ref(),
..Default::default()
})
.unwrap_or_default();
let external_config = ExternalConfig {
project: pyproject
.as_ref()
.and_then(|pyproject| pyproject.project.as_ref()),
..external_config
};
// Create Ruff's pyproject.toml section.
let pyproject = flake8_to_ruff::convert(&config, &external_config, args.plugin)?;

View File

@@ -1,84 +1,83 @@
[package]
name = "ruff"
version = "0.0.263"
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
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_cache = { path = "../ruff_cache" }
ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] }
ruff_macros = { path = "../ruff_macros" }
ruff_python_ast = { path = "../ruff_python_ast" }
ruff_python_semantic = { path = "../ruff_python_semantic" }
ruff_python_stdlib = { path = "../ruff_python_stdlib" }
ruff_python = { path = "../ruff_python" }
ruff_rustpython = { path = "../ruff_rustpython" }
ruff_text_size = { path = "../ruff_text_size" }
annotate-snippets = { version = "0.9.1", features = ["color"] }
anyhow = { workspace = true }
bitflags = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true, features = ["derive", "string"], optional = true }
colored = { workspace = true }
dirs = { version = "5.0.0" }
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 = { workspace = true }
globset = { workspace = true }
ignore = { workspace = true }
imperative = { version = "1.0.4" }
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 = { workspace = true }
log = { version = "0.4.17" }
natord = { version = "1.0.9" }
nohash-hasher = { workspace = true }
nohash-hasher = { version = "0.2.0" }
num-bigint = { version = "0.4.3" }
num-traits = { version = "0.2.15" }
num-traits = "0.2.15"
once_cell = { workspace = true }
path-absolutize = { workspace = true, features = [
"once_cell_cache",
"use_unix_paths_on_wasm",
] }
pathdiff = { version = "0.2.1" }
pep440_rs = { version = "0.3.1", features = ["serde"] }
quick-junit = { version = "0.3.2" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
regex = { workspace = true }
result-like = { version = "0.4.6" }
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 }
serde_json = { workspace = true }
similar = { workspace = true, features = ["inline"] }
shellexpand = { workspace = true }
smallvec = { workspace = true }
shellexpand = { version = "3.0.0" }
smallvec = { version = "1.10.0" }
strum = { workspace = true }
strum_macros = { workspace = true }
textwrap = { workspace = true }
thiserror = { version = "1.0.38" }
textwrap = { version = "0.16.0" }
thiserror = { version = "1.0" }
titlecase = { version = "2.2.1" }
toml = { workspace = true }
typed-arena = { version = "2.0.2" }
unicode-width = { version = "0.1.10" }
# 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 = { workspace = true }
pretty_assertions = "1.3.0"
test-case = { workspace = true }
# Disable colored output in tests
colored = { workspace = true, features = ["no-color"] }
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 = []
jupyter_notebook = []

View File

@@ -2,7 +2,3 @@ avoid-*
do-not-*
uses-*
*-used
rewrite-*
prefer-*
consider-*
use-*

View File

@@ -9,7 +9,6 @@ def foo(x, y, z):
print(x, y, z)
# This is a real comment.
# # This is a (nested) comment.
#return True
return False

View File

@@ -1,42 +0,0 @@
class Foo:
def __str__(self):
...
def __repr__(self):
...
def __len__(self):
...
def __length_hint__(self):
...
def __init__(self):
...
def __del__(self):
...
def __bool__(self):
...
def __bytes__(self):
...
def __format__(self, format_spec):
...
def __contains__(self, item):
...
def __complex__(self):
...
def __int__(self):
...
def __float__(self):
...
def __index__(self):
...

View File

@@ -1,13 +1,11 @@
assert True # S101
# Error
assert True
def fn():
x = 1
assert x == 1 # S101
assert x == 2 # S101
# Error
assert x == 1
from typing import TYPE_CHECKING
if TYPE_CHECKING:
assert True # OK
# Error
assert x == 2

View File

@@ -1,3 +0,0 @@
import pickle
pickle.loads()

View File

@@ -1,3 +0,0 @@
from telnetlib import Telnet
Telnet("localhost", 23)

View File

@@ -1,20 +0,0 @@
from subprocess import Popen, call, check_call, check_output, run
# Check different Popen wrappers are checked.
Popen("true", shell=True)
call("true", shell=True)
check_call("true", shell=True)
check_output("true", shell=True)
run("true", shell=True)
# Check values that truthy values are treated as true.
Popen("true", shell=1)
Popen("true", shell=[1])
Popen("true", shell={1: 1})
Popen("true", shell=(1,))
# Check command argument looks unsafe.
var_string = "true"
Popen(var_string, shell=True)
Popen([var_string], shell=True)
Popen([var_string, ""], shell=True)

View File

@@ -1,20 +0,0 @@
from subprocess import Popen, call, check_call, check_output, run
# Different Popen wrappers are checked.
Popen("true", shell=False)
call("true", shell=False)
check_call("true", shell=False)
check_output("true", shell=False)
run("true", shell=False)
# Values that falsey values are treated as false.
Popen("true", shell=0)
Popen("true", shell=[])
Popen("true", shell={})
Popen("true", shell=None)
# Unknown values are treated as falsey.
Popen("true", shell=True if True else False)
# No value is also caught.
Popen("true")

View File

@@ -1,5 +0,0 @@
def foo(shell):
pass
foo(shell=True)

View File

@@ -1,25 +0,0 @@
import os
import commands
import popen2
# Check all shell functions.
os.system("true")
os.popen("true")
os.popen2("true")
os.popen3("true")
os.popen4("true")
popen2.popen2("true")
popen2.popen3("true")
popen2.popen4("true")
popen2.Popen3("true")
popen2.Popen4("true")
commands.getoutput("true")
commands.getstatusoutput("true")
# Check command argument looks unsafe.
var_string = "true"
os.system(var_string)
os.system([var_string])
os.system([var_string, ""])

View File

@@ -1,20 +0,0 @@
import os
# Check all shell functions.
os.execl("true")
os.execle("true")
os.execlp("true")
os.execlpe("true")
os.execv("true")
os.execve("true")
os.execvp("true")
os.execvpe("true")
os.spawnl("true")
os.spawnle("true")
os.spawnlp("true")
os.spawnlpe("true")
os.spawnv("true")
os.spawnve("true")
os.spawnvp("true")
os.spawnvpe("true")
os.startfile("true")

View File

@@ -1,44 +0,0 @@
import os
# Check all functions.
subprocess.Popen("true")
subprocess.call("true")
subprocess.check_call("true")
subprocess.check_output("true")
subprocess.run("true")
os.system("true")
os.popen("true")
os.popen2("true")
os.popen3("true")
os.popen4("true")
popen2.popen2("true")
popen2.popen3("true")
popen2.popen4("true")
popen2.Popen3("true")
popen2.Popen4("true")
commands.getoutput("true")
commands.getstatusoutput("true")
os.execl("true")
os.execle("true")
os.execlp("true")
os.execlpe("true")
os.execv("true")
os.execve("true")
os.execvp("true")
os.execvpe("true")
os.spawnl("true")
os.spawnle("true")
os.spawnlp("true")
os.spawnlpe("true")
os.spawnv("true")
os.spawnve("true")
os.spawnvp("true")
os.spawnvpe("true")
os.startfile("true")
# Check it does not fail for full paths.
os.system("/bin/ls")
os.system("./bin/ls")
os.system(["/bin/ls"])
os.system(["/bin/ls", "/tmp"])
os.system(r"C:\\bin\ls")

View File

@@ -1,9 +1,7 @@
import collections
import datetime as dt
from decimal import Decimal
import logging
import operator
from pathlib import Path
import random
import re
import time
@@ -167,26 +165,6 @@ def float_str_not_inf_or_nan_is_wrong(value=float("3.14")):
pass
# Allow decimals
def decimal_okay(value=Decimal("0.1")):
pass
# Allow dates
def date_okay(value=dt.date(2023, 3, 27)):
pass
# Allow datetimes
def datetime_okay(value=dt.datetime(2023, 3, 27, 13, 51, 59)):
pass
# Allow timedeltas
def timedelta_okay(value=dt.timedelta(hours=1)):
pass
# Allow paths
def path_okay(value=Path(".")):
pass
# B006 and B008
# We should handle arbitrary nesting of these B008.
def nested_combo(a=[float(3), dt.datetime.now()]):

View File

@@ -1,10 +1,9 @@
"""
Should emit:
B017 - on lines 23 and 41
B017 - on lines 20
"""
import asyncio
import unittest
import pytest
CONSTANT = True
@@ -35,14 +34,3 @@ class Foobar(unittest.TestCase):
def raises_with_absolute_reference(self):
with self.assertRaises(asyncio.CancelledError):
Foo()
def test_pytest_raises():
with pytest.raises(Exception):
raise ValueError("Hello")
with pytest.raises(Exception, "hello"):
raise ValueError("This is fine")
with pytest.raises(Exception, match="hello"):
raise ValueError("This is also fine")

View File

@@ -57,9 +57,3 @@ def foo3():
def foo4():
...
def foo5():
foo.bar # Attribute (raise)
object().__class__ # Attribute (raise)
"foo" + "bar" # BinOp (raise)

View File

@@ -1,11 +0,0 @@
import warnings
"""
Should emit:
B028 - on lines 8 and 9
"""
warnings.warn(DeprecationWarning("test"))
warnings.warn(DeprecationWarning("test"), source=None)
warnings.warn(DeprecationWarning("test"), source=None, stacklevel=2)
warnings.warn(DeprecationWarning("test"), stacklevel=1)

View File

@@ -1,78 +0,0 @@
"""
Should emit:
B030:
- line 12, column 8
- line 17, column 9
- line 22, column 21
- line 27, column 37
"""
try:
pass
except 1: # error
pass
try:
pass
except (1, ValueError): # error
pass
try:
pass
except (ValueError, (RuntimeError, (KeyError, TypeError))): # error
pass
try:
pass
except (ValueError, *(RuntimeError, (KeyError, TypeError))): # error
pass
try:
pass
except (*a, *(RuntimeError, (KeyError, TypeError))): # error
pass
try:
pass
except (ValueError, *(RuntimeError, TypeError)): # ok
pass
try:
pass
except (ValueError, *[RuntimeError, *(TypeError,)]): # ok
pass
try:
pass
except (*a, *b): # ok
pass
try:
pass
except (*a, *(RuntimeError, TypeError)): # ok
pass
try:
pass
except (*a, *(b, c)): # ok
pass
try:
pass
except (*a, *(*b, *c)): # ok
pass
def what_to_catch():
return ...
try:
pass
except what_to_catch(): # ok
pass

View File

@@ -1,187 +0,0 @@
import itertools
from itertools import groupby
shoppers = ["Jane", "Joe", "Sarah"]
items = [
("lettuce", "greens"),
("tomatoes", "greens"),
("cucumber", "greens"),
("chicken breast", "meats & fish"),
("salmon", "meats & fish"),
("ice cream", "frozen items"),
]
carts = {shopper: [] for shopper in shoppers}
def collect_shop_items(shopper, items):
# Imagine this an expensive database query or calculation that is
# advantageous to batch.
carts[shopper] += items
# Invoking the `groupby` function directly
for _section, section_items in groupby(items, key=lambda p: p[1]):
for shopper in shoppers:
shopper = shopper.title()
collect_shop_items(shopper, section_items) # B031
# We're outside the nested loop and used the group again.
collect_shop_items(shopper, section_items) # B031
for _section, section_items in groupby(items, key=lambda p: p[1]):
collect_shop_items("Jane", section_items)
collect_shop_items("Joe", section_items) # B031
# Make sure to detect in other loop constructs as well - `while` loop
for _section, section_items in groupby(items, key=lambda p: p[1]):
countdown = 3
while countdown > 0:
collect_shop_items(shopper, section_items) # B031
countdown -= 1
# Make sure to detect in other loop constructs as well - `list` comprehension
collection = []
for _section, section_items in groupby(items, key=lambda p: p[1]):
collection.append([list(section_items) for _ in range(3)]) # B031
unique_items = set()
another_set = set()
for _section, section_items in groupby(items, key=lambda p: p[1]):
# For nested loops, it should not flag the usage of the name
for item in section_items:
unique_items.add(item)
# But it should be detected when used again
for item in section_items: # B031
another_set.add(item)
for _section, section_items in groupby(items, key=lambda p: p[1]):
# Variable has been overridden, skip checking
section_items = list(unique_items)
collect_shop_items("Jane", section_items)
collect_shop_items("Jane", section_items)
for _section, section_items in groupby(items, key=lambda p: p[1]):
# Variable has been overridden, skip checking
# Not a realistic situation, just for testing purpose
(section_items := list(unique_items))
collect_shop_items("Jane", section_items)
collect_shop_items("Jane", section_items)
for _section, section_items in groupby(items, key=lambda p: p[1]):
# This is ok
collect_shop_items("Jane", section_items)
# Invocation via the `itertools` module
for _section, section_items in itertools.groupby(items, key=lambda p: p[1]):
for shopper in shoppers:
collect_shop_items(shopper, section_items) # B031
for _section, section_items in itertools.groupby(items, key=lambda p: p[1]):
_ = [collect_shop_items(shopper, section_items) for shopper in shoppers] # B031
for _section, section_items in itertools.groupby(items, key=lambda p: p[1]):
# The variable is overridden, skip checking.
_ = [_ for section_items in range(3)]
_ = [collect_shop_items(shopper, section_items) for shopper in shoppers]
for _section, section_items in itertools.groupby(items, key=lambda p: p[1]):
_ = [item for item in section_items]
for _section, section_items in itertools.groupby(items, key=lambda p: p[1]):
# The iterator is being used for the second time.
_ = [(item1, item2) for item1 in section_items for item2 in section_items] # B031
for _section, section_items in itertools.groupby(items, key=lambda p: p[1]):
if _section == "greens":
collect_shop_items(shopper, section_items)
else:
collect_shop_items(shopper, section_items)
collect_shop_items(shopper, section_items) # B031
for _section, section_items in itertools.groupby(items, key=lambda p: p[1]):
# Mutually exclusive branches shouldn't trigger the warning
if _section == "greens":
collect_shop_items(shopper, section_items)
if _section == "greens":
collect_shop_items(shopper, section_items) # B031
elif _section == "frozen items":
collect_shop_items(shopper, section_items) # B031
else:
collect_shop_items(shopper, section_items) # B031
collect_shop_items(shopper, section_items) # B031
elif _section == "frozen items":
# Mix `match` and `if` statements
match shopper:
case "Jane":
collect_shop_items(shopper, section_items)
if _section == "fourth":
collect_shop_items(shopper, section_items) # B031
case _:
collect_shop_items(shopper, section_items)
else:
collect_shop_items(shopper, section_items)
# Now, it should detect
collect_shop_items(shopper, section_items) # B031
for _section, section_items in itertools.groupby(items, key=lambda p: p[1]):
# Mutually exclusive branches shouldn't trigger the warning
match _section:
case "greens":
collect_shop_items(shopper, section_items)
match shopper:
case "Jane":
collect_shop_items(shopper, section_items) # B031
case _:
collect_shop_items(shopper, section_items) # B031
case "frozen items":
collect_shop_items(shopper, section_items)
collect_shop_items(shopper, section_items) # B031
case _:
collect_shop_items(shopper, section_items)
# Now, it should detect
collect_shop_items(shopper, section_items) # B031
for group in groupby(items, key=lambda p: p[1]):
# This is bad, but not detected currently
collect_shop_items("Jane", group[1])
collect_shop_items("Joe", group[1])
# https://github.com/charliermarsh/ruff/issues/4050
for _section, section_items in itertools.groupby(items, key=lambda p: p[1]):
if _section == "greens":
for item in section_items:
collect_shop_items(shopper, item)
elif _section == "frozen items":
_ = [item for item in section_items]
else:
collect_shop_items(shopper, section_items)
# Make sure we ignore - but don't fail on more complicated invocations
for _key, (_value1, _value2) in groupby(
[("a", (1, 2)), ("b", (3, 4)), ("a", (5, 6))], key=lambda p: p[1]
):
collect_shop_items("Jane", group[1])
collect_shop_items("Joe", group[1])
# Make sure we ignore - but don't fail on more complicated invocations
for (_key1, _key2), (_value1, _value2) in groupby(
[(("a", "a"), (1, 2)), (("b", "b"), (3, 4)), (("a", "a"), (5, 6))],
key=lambda p: p[1],
):
collect_shop_items("Jane", group[1])
collect_shop_items("Joe", group[1])
# Let's redefine the `groupby` function to make sure we pick up the correct one.
# NOTE: This should always be at the end of the file.
def groupby(data, key=None):
pass
for name, group in groupby(items):
collect_shop_items("Jane", items)
# This shouldn't be flagged as the `groupby` function is different
collect_shop_items("Joe", items)

View File

@@ -7,14 +7,11 @@ set(set(x))
set(list(x))
set(tuple(x))
set(sorted(x))
set(sorted(x, key=lambda y: y))
set(reversed(x))
sorted(list(x))
sorted(tuple(x))
sorted(sorted(x))
sorted(sorted(x, key=lambda y: y))
sorted(reversed(x))
sorted(list(x), key=lambda y: y)
tuple(
list(
[x, 3, "hell"\

View File

@@ -1,19 +1,6 @@
x = [1, 2, 3]
y = [("a", 1), ("b", 2), ("c", 3)]
z = [(1,), (2,), (3,)]
d = {"a": 1, "b": 2, "c": 3}
[i for i in x]
{i for i in x}
{k: v for k, v in y}
{k: v for k, v in d.items()}
[i for i, in z]
[i for i, j in y]
[i for i in x if i > 1]
[i for i in x for j in x]
{v: k for k, v in y}
{k.foo: k for k in y}
{k["foo"]: k for k in y}
{k: v if v else None for k, v in y}

View File

@@ -1,10 +0,0 @@
dict({})
dict({'a': 1})
dict({'x': 1 for x in range(10)})
dict(
{'x': 1 for x in range(10)}
)
dict({}, a=1)
dict({x: 1 for x in range(1)}, a=1)

View File

@@ -1,19 +0,0 @@
any([x.id for x in bar])
all([x.id for x in bar])
any( # first comment
[x.id for x in bar], # second comment
) # third comment
all( # first comment
[x.id for x in bar], # second comment
) # third comment
any({x.id for x in bar})
# OK
all(x.id for x in bar)
all(x.id for x in bar)
any(x.id for x in bar)
all((x.id for x in bar))
async def f() -> bool:
return all([await use_greeting(greeting) for greeting in await greetings()])

View File

@@ -1,113 +0,0 @@
from django.db import models
from django.db.models import Model
class StrBeforeRandomField(models.Model):
"""Model with `__str__` before a random property."""
class Meta:
verbose_name = "test"
verbose_name_plural = "tests"
def __str__(self):
return ""
random_property = "foo"
class StrBeforeFieldModel(models.Model):
"""Model with `__str__` before fields."""
class Meta:
verbose_name = "test"
verbose_name_plural = "tests"
def __str__(self):
return "foobar"
first_name = models.CharField(max_length=32)
class ManagerBeforeField(models.Model):
"""Model with manager before fields."""
objects = "manager"
class Meta:
verbose_name = "test"
verbose_name_plural = "tests"
def __str__(self):
return "foobar"
first_name = models.CharField(max_length=32)
class CustomMethodBeforeStr(models.Model):
"""Model with a custom method before `__str__`."""
class Meta:
verbose_name = "test"
verbose_name_plural = "tests"
def my_method(self):
pass
def __str__(self):
return "foobar"
class GetAbsoluteUrlBeforeSave(Model):
"""Model with `get_absolute_url` method before `save` method.
Subclass this directly using the `Model` class.
"""
def get_absolute_url(self):
pass
def save(self):
pass
class ConstantsAreNotFields(models.Model):
"""Model with an assignment to a constant after `__str__`."""
first_name = models.CharField(max_length=32)
class Meta:
verbose_name = "test"
verbose_name_plural = "tests"
def __str__(self):
pass
MY_CONSTANT = id(1)
class PerfectlyFine(models.Model):
"""Model which has everything in perfect order."""
first_name = models.CharField(max_length=32)
last_name = models.CharField(max_length=32)
objects = "manager"
class Meta:
verbose_name = "test"
verbose_name_plural = "tests"
def __str__(self):
return "Perfectly fine!"
def save(self, **kwargs):
super(PerfectlyFine, self).save(**kwargs)
def get_absolute_url(self):
return "http://%s" % self
def my_method(self):
pass
@property
def random_property(self):
return "%s" % self

View File

@@ -1 +0,0 @@
_(f"{'value'}")

View File

@@ -1 +0,0 @@
_("{}".format("line"))

View File

@@ -1 +0,0 @@
_("%s" % "line")

View File

@@ -1,16 +0,0 @@
import typing as t # banned
import typing as ty # banned
import numpy as nmp # banned
import numpy as npy # banned
import tensorflow.keras.backend as K # banned
import torch.nn.functional as F # banned
from tensorflow.keras import backend as K # banned
from torch.nn import functional as F # banned
from typing import Any # ok
import numpy as np # ok
import tensorflow as tf # ok
import torch.nn as nn # ok
from tensorflow.keras import backend # ok

View File

@@ -1,10 +0,0 @@
from logging.config import BaseConfigurator # banned
from typing import Any, Dict # banned
from typing import * # banned
from pandas import DataFrame # banned
from pandas import * # banned
import logging.config # ok
import typing # ok
import pandas # ok

View File

@@ -1,9 +1,3 @@
import logging
import logging as foo
logging.info("Hello {}".format("World!"))
logging.log(logging.INFO, "Hello {}".format("World!"))
foo.info("Hello {}".format("World!"))
logging.log(logging.INFO, msg="Hello {}".format("World!"))
logging.log(level=logging.INFO, msg="Hello {}".format("World!"))
logging.log(msg="Hello {}".format("World!"), level=logging.INFO)

View File

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

View File

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

View File

@@ -2,4 +2,3 @@ import logging
name = "world"
logging.info(f"Hello {name}")
logging.log(logging.INFO, f"Hello {name}")

View File

@@ -1,5 +1,3 @@
import logging
from distutils import log
logging.warn("Hello World!")
log.warn("Hello world!") # This shouldn't be considered as a logger candidate

View File

@@ -0,0 +1,10 @@
# 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

@@ -6,8 +6,6 @@ obj.endswith("foo") or obj.endswith("bar")
obj.startswith(foo) or obj.startswith(bar)
# error
obj.startswith(foo) or obj.startswith("foo")
# error
obj.endswith(foo) or obj.startswith(foo) or obj.startswith("foo")
# ok
obj.startswith(("foo", "bar"))

View File

@@ -11,7 +11,3 @@ _T = TypeVar("_T") # OK
_TTuple = TypeVarTuple("_TTuple") # OK
_P = ParamSpec("_P") # OK
def f():
T = TypeVar("T") # OK

View File

@@ -11,6 +11,3 @@ _T = TypeVar("_T") # OK
_TTuple = TypeVarTuple("_TTuple") # OK
_P = ParamSpec("_P") # OK
def f():
T = TypeVar("T") # OK

View File

@@ -1,161 +1,79 @@
import math
import os
import sys
from math import inf
import numpy as np
def f12(
x,
y: str = os.pathsep, # OK
) -> None: ...
def f11(*, x: str = "x") -> None: ... # OK
) -> None:
...
def f11(*, x: str = "x") -> None: # OK
...
def f13(
x: list[str] = [ # OK
x: list[str] = [
"foo",
"bar",
"baz",
]
) -> None: ...
] # OK
) -> None:
...
def f14(
x: tuple[str, ...] = ( # OK
x: tuple[str, ...] = (
"foo",
"bar",
"baz",
)
) -> None: ...
) # OK
) -> None:
...
def f15(
x: set[str] = { # OK
x: set[str] = {
"foo",
"bar",
"baz",
}
) -> None: ...
def f151(x: dict[int, int] = {1: 2}) -> None: ... # Ok
def f152(
x: dict[
int, int
] = { # OK
1: 2,
**{3: 4},
}
) -> None: ...
def f153(
x: list[
int
] = [ # OK
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
]
) -> None: ...
def f154(
x: tuple[
str, tuple[str, ...]
] = ( # OK
"foo",
("bar", "baz"),
)
) -> None: ...
def f141(
x: list[
int
] = [ # OK
*range(10)
],
) -> None: ...
def f142(
x: list[
int
] = list( # OK
range(10)
),
) -> None: ...
def f16(
x: frozenset[
bytes
] = frozenset( # OK
{b"foo", b"bar", b"baz"}
)
) -> None: ...
} # OK
) -> None:
...
def f16(x: frozenset[bytes] = frozenset({b"foo", b"bar", b"baz"})) -> None: # OK
...
def f17(
x: str = "foo" # OK
+ "bar",
) -> None: ...
x: str = "foo" + "bar", # OK
) -> None:
...
def f18(
x: str = b"foo" # OK
+ b"bar",
) -> None: ...
x: str = b"foo" + b"bar", # OK
) -> None:
...
def f19(
x: object = "foo" # OK
+ 4,
) -> None: ...
x: object = "foo" + 4, # OK
) -> None:
...
def f20(
x: int = 5
+ 5, # OK
) -> None: ...
x: int = 5 + 5, # OK
) -> None:
...
def f21(
x: complex = 3j
- 3j, # OK
) -> None: ...
x: complex = 3j - 3j, # OK
) -> None:
...
def f22(
x: complex = -42.5j # OK
+ 4.3j,
) -> None: ...
def f23(
x: bool = True, # OK
) -> None: ...
def f24(
x: float = 3.14, # OK
) -> None: ...
def f25(
x: float = -3.14, # OK
) -> None: ...
def f26(
x: complex = -3.14j, # OK
) -> None: ...
def f27(
x: complex = -3 - 3.14j, # OK
) -> None: ...
def f28(
x: float = math.tau, # OK
) -> None: ...
def f29(
x: float = math.inf, # OK
) -> None: ...
def f30(
x: float = -math.inf, # OK
) -> None: ...
def f31(
x: float = inf, # OK
) -> None: ...
def f32(
x: float = np.inf, # OK
) -> None: ...
def f33(
x: float = math.nan, # OK
) -> None: ...
def f34(
x: float = -math.nan, # OK
) -> None: ...
def f35(
x: complex = math.inf # OK
+ 1j,
) -> None: ...
def f36(
*,
x: str = sys.version, # OK
) -> None: ...
def f37(
*,
x: str = "" # OK
+ "",
) -> None: ...
x: complex = -42.5j + 4.3j, # OK
) -> None:
...

View File

@@ -1,84 +1,35 @@
import math
import os
import sys
from math import inf
import numpy as np
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] = [ # OK
x: list[
str
] = [ # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
]
) -> None: ...
def f14(
x: tuple[str, ...] = ( # OK
x: tuple[
str, ...
] = ( # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
)
) -> None: ...
def f15(
x: set[str] = { # OK
x: set[
str
] = { # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
}
) -> None: ...
def f151(x: dict[int, int] = {1: 2}) -> None: ... # Ok
def f152(
x: dict[
int, int
] = { # Error PYI011 Only simple default values allowed for typed arguments
1: 2,
**{3: 4},
}
) -> None: ...
def f153(
x: list[
int
] = [ # Error PYI011 Only simple default values allowed for typed arguments
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
]
) -> None: ...
def f154(
x: tuple[
str, tuple[str, ...]
] = ( # Error PYI011 Only simple default values allowed for typed arguments
"foo",
("bar", "baz"),
)
) -> None: ...
def f141(
x: list[
int
] = [ # Error PYI011 Only simple default values allowed for typed arguments
*range(10)
],
) -> None: ...
def f142(
x: list[
int
] = list( # Error PYI011 Only simple default values allowed for typed arguments
range(10)
),
) -> None: ...
def f16(
x: frozenset[
bytes
@@ -110,52 +61,3 @@ def f22(
x: complex = -42.5j # Error PYI011 Only simple default values allowed for typed arguments
+ 4.3j,
) -> None: ...
def f23(
x: bool = True, # OK
) -> None: ...
def f24(
x: float = 3.14, # OK
) -> None: ...
def f25(
x: float = -3.14, # OK
) -> None: ...
def f26(
x: complex = -3.14j, # OK
) -> None: ...
def f27(
x: complex = -3 - 3.14j, # OK
) -> None: ...
def f28(
x: float = math.tau, # OK
) -> None: ...
def f29(
x: float = math.inf, # OK
) -> None: ...
def f30(
x: float = -math.inf, # OK
) -> None: ...
def f31(
x: float = inf, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f32(
x: float = np.inf, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f33(
x: float = math.nan, # OK
) -> None: ...
def f34(
x: float = -math.nan, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f35(
x: complex = math.inf # Error PYI011 Only simple default values allowed for typed arguments
+ 1j,
) -> None: ...
def f36(
*,
x: str = sys.version, # OK
) -> None: ...
def f37(
*,
x: str = "" # Error PYI011 Only simple default values allowed for typed arguments
+ "",
) -> None: ...

View File

@@ -1,75 +0,0 @@
# Violations of PYI012
class OneAttributeClass:
value: int
pass # PYI012 Class body must not contain `pass`
class OneAttributeClassRev:
pass # PYI012 Class body must not contain `pass`
value: int
class DocstringClass:
"""
My body only contains pass.
"""
pass # PYI012 Class body must not contain `pass`
class NonEmptyChild(Exception):
value: int
pass # PYI012 Class body must not contain `pass`
class NonEmptyChild2(Exception):
pass # PYI012 Class body must not contain `pass`
value: int
class NonEmptyWithInit:
value: int
pass # PYI012 Class body must not contain `pass`
def __init__():
pass
# Not violations (of PYI012)
class EmptyClass:
pass # Y009 Empty body should contain `...`, not `pass`
class EmptyOneLine:
pass # Y009 Empty body should contain `...`, not `pass`
class Dog:
eyes: int = 2
class EmptyEllipsis:
...
class NonEmptyEllipsis:
value: int
... # Y013 Non-empty class body must not contain `...`
class WithInit:
value: int = 0
def __init__():
pass
def function():
pass
pass

View File

@@ -1,59 +0,0 @@
# Violations of PYI012
class OneAttributeClass:
value: int
pass # PYI012 Class body must not contain `pass`
class OneAttributeClassRev:
pass # PYI012 Class body must not contain `pass`
value: int
class DocstringClass:
"""
My body only contains pass.
"""
pass # PYI012 Class body must not contain `pass`
class NonEmptyChild(Exception):
value: int
pass # PYI012 Class body must not contain `pass`
class NonEmptyChild2(Exception):
pass # PYI012 Class body must not contain `pass`
value: int
class NonEmptyWithInit:
value: int
pass # PYI012 Class body must not contain `pass`
def __init__():
pass
# Not violations (of PYI012)
class EmptyClass:
pass # Y009 Empty body should contain `...`, not `pass`
class EmptyOneLine:
pass # Y009 Empty body should contain `...`, not `pass`
class Dog:
eyes: int = 2
class EmptyEllipsis: ...
class NonEmptyEllipsis:
value: int
... # Y013 Non-empty class body must not contain `...`
class WithInit:
value: int = 0
def __init__():
pass
def function():
pass
pass

View File

@@ -39,58 +39,6 @@ def f15(
...
def f151(x={1: 2}) -> None: # Ok
...
def f152(
x={ # OK
1: 2,
**{3: 4},
}
) -> None:
...
def f153(
x=[ # OK
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
]
) -> None:
...
def f154(
x=( # OK
"foo",
("bar", "baz"),
)
) -> None:
...
def f141(
x=[*range(10)], # OK
) -> None:
...
def f142(
x=list(range(10)), # OK
) -> None:
...
def f16(x=frozenset({b"foo", b"bar", b"baz"})) -> None:
... # OK

View File

@@ -4,60 +4,26 @@ def f12(
) -> None: ...
def f11(*, x="x") -> None: ... # OK
def f13(
x=[ # OK
x=[ # Error PYI014
"foo",
"bar",
"baz",
]
) -> None: ...
def f14(
x=( # OK
x=( # Error PYI014
"foo",
"bar",
"baz",
)
) -> None: ...
def f15(
x={ # OK
x={ # Error PYI014
"foo",
"bar",
"baz",
}
) -> None: ...
def f151(x={1: 2}) -> None: ...
def f152(
x={ # Error PYI014
1: 2,
**{3: 4},
}
) -> None: ...
def f153(
x=[ # Error PYI014
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
]
) -> None: ...
def f154(
x=( # Error PYI014
"foo",
("bar", "baz"),
)
) -> None: ...
def f141(
x=[*range(10)], # Error PYI014
) -> None: ...
def f142(
x=list(range(10)), # Error PYI014
) -> None: ...
def f16(x=frozenset({b"foo", b"bar", b"baz"})) -> None: ... # Error PYI014
def f17(
x="foo" + "bar", # Error PYI014
@@ -77,6 +43,3 @@ def f21(
def f22(
x=-42.5j + 4.3j, # Error PYI014
) -> None: ...
def f23(
x=True, # OK
) -> None: ...

View File

@@ -1,93 +0,0 @@
import builtins
import typing
from typing import TypeAlias, Final
field1: int
field2: int = ...
field3 = ... # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
field4: int = 0
field41: int = 0xFFFFFFFF
field42: int = 1234567890
field43: int = -0xFFFFFFFF
field44: int = -1234567890
field5 = 0 # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") # Y052 Need type annotation for "field5"
field6 = 0 # Y052 Need type annotation for "field6"
field7 = b"" # Y052 Need type annotation for "field7"
field71 = "foo" # Y052 Need type annotation for "field71"
field72: str = "foo"
field8 = False # Y052 Need type annotation for "field8"
field81 = -1 # Y052 Need type annotation for "field81"
field82: float = -98.43
field83 = -42j # Y052 Need type annotation for "field83"
field84 = 5 + 42j # Y052 Need type annotation for "field84"
field85 = -5 - 42j # Y052 Need type annotation for "field85"
field9 = None # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "field9: TypeAlias = None"
Field95: TypeAlias = None
Field96: TypeAlias = int | None
Field97: TypeAlias = None | typing.SupportsInt | builtins.str | float | bool
field19 = [1, 2, 3] # Y052 Need type annotation for "field19"
field191: list[int] = [1, 2, 3]
field20 = (1, 2, 3) # Y052 Need type annotation for "field20"
field201: tuple[int, ...] = (1, 2, 3)
field21 = {1, 2, 3} # Y052 Need type annotation for "field21"
field211: set[int] = {1, 2, 3}
field212 = {"foo": "bar"} # Y052 Need type annotation for "field212"
field213: dict[str, str] = {"foo": "bar"}
field22: Final = {"foo": 5}
field221: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] # Y015 Only simple default values are allowed for assignments
field223: list[int] = [*range(10)] # Y015 Only simple default values are allowed for assignments
field224: list[int] = list(range(10)) # Y015 Only simple default values are allowed for assignments
field225: list[object] = [{}, 1, 2] # Y015 Only simple default values are allowed for assignments
field226: tuple[str | tuple[str, ...], ...] = ("foo", ("foo", "bar")) # Y015 Only simple default values are allowed for assignments
field227: dict[str, object] = {"foo": {"foo": "bar"}} # Y015 Only simple default values are allowed for assignments
field228: dict[str, list[object]] = {"foo": []} # Y015 Only simple default values are allowed for assignments
# When parsed, this case results in `None` being placed in the `.keys` list for the `ast.Dict` node
field229: dict[int, int] = {1: 2, **{3: 4}} # Y015 Only simple default values are allowed for assignments
field23 = "foo" + "bar" # Y015 Only simple default values are allowed for assignments
field24 = b"foo" + b"bar" # Y015 Only simple default values are allowed for assignments
field25 = 5 * 5 # Y015 Only simple default values are allowed for assignments
# We shouldn't emit Y015 within functions
def f():
field26: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
# We shouldn't emit Y015 for __slots__ or __match_args__
class Class1:
__slots__ = (
'_one',
'_two',
'_three',
'_four',
'_five',
'_six',
'_seven',
'_eight',
'_nine',
'_ten',
'_eleven',
)
__match_args__ = (
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
'ten',
'eleven',
)
# We shouldn't emit Y015 for __all__
__all__ = ["Class1"]
# Ignore the following for PYI015
field26 = typing.Sequence[int]
field27 = list[str]
field28 = builtins.str
field29 = str
field30 = str | bytes | None

View File

@@ -1,100 +0,0 @@
import builtins
import typing
from typing import TypeAlias, Final, NewType, TypeVar, TypeVarTuple, ParamSpec
# We shouldn't emit Y015 for simple default values
field1: int
field2: int = ...
field3 = ... # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
field4: int = 0
field41: int = 0xFFFFFFFF
field42: int = 1234567890
field43: int = -0xFFFFFFFF
field44: int = -1234567890
field5 = 0 # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") # Y052 Need type annotation for "field5"
field6 = 0 # Y052 Need type annotation for "field6"
field7 = b"" # Y052 Need type annotation for "field7"
field71 = "foo" # Y052 Need type annotation for "field71"
field72: str = "foo"
field8 = False # Y052 Need type annotation for "field8"
field81 = -1 # Y052 Need type annotation for "field81"
field82: float = -98.43
field83 = -42j # Y052 Need type annotation for "field83"
field84 = 5 + 42j # Y052 Need type annotation for "field84"
field85 = -5 - 42j # Y052 Need type annotation for "field85"
field9 = None # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "field9: TypeAlias = None"
Field95: TypeAlias = None
Field96: TypeAlias = int | None
Field97: TypeAlias = None | typing.SupportsInt | builtins.str | float | bool
Field98 = NewType('MyInt', int)
Field99 = TypeVar('Field99')
Field100 = TypeVarTuple('Field100')
Field101 = ParamSpec('Field101')
field19 = [1, 2, 3] # Y052 Need type annotation for "field19"
field191: list[int] = [1, 2, 3]
field20 = (1, 2, 3) # Y052 Need type annotation for "field20"
field201: tuple[int, ...] = (1, 2, 3)
field21 = {1, 2, 3} # Y052 Need type annotation for "field21"
field211: set[int] = {1, 2, 3}
field212 = {"foo": "bar"} # Y052 Need type annotation for "field212"
field213: dict[str, str] = {"foo": "bar"}
field22: Final = {"foo": 5}
# We *should* emit Y015 for more complex default values
field221: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] # Y015 Only simple default values are allowed for assignments
field223: list[int] = [*range(10)] # Y015 Only simple default values are allowed for assignments
field224: list[int] = list(range(10)) # Y015 Only simple default values are allowed for assignments
field225: list[object] = [{}, 1, 2] # Y015 Only simple default values are allowed for assignments
field226: tuple[str | tuple[str, ...], ...] = ("foo", ("foo", "bar")) # Y015 Only simple default values are allowed for assignments
field227: dict[str, object] = {"foo": {"foo": "bar"}} # Y015 Only simple default values are allowed for assignments
field228: dict[str, list[object]] = {"foo": []} # Y015 Only simple default values are allowed for assignments
# When parsed, this case results in `None` being placed in the `.keys` list for the `ast.Dict` node
field229: dict[int, int] = {1: 2, **{3: 4}} # Y015 Only simple default values are allowed for assignments
field23 = "foo" + "bar" # Y015 Only simple default values are allowed for assignments
field24 = b"foo" + b"bar" # Y015 Only simple default values are allowed for assignments
field25 = 5 * 5 # Y015 Only simple default values are allowed for assignments
# We shouldn't emit Y015 within functions
def f():
field26: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
# We shouldn't emit Y015 for __slots__ or __match_args__
class Class1:
__slots__ = (
'_one',
'_two',
'_three',
'_four',
'_five',
'_six',
'_seven',
'_eight',
'_nine',
'_ten',
'_eleven',
)
__match_args__ = (
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
'ten',
'eleven',
)
# We shouldn't emit Y015 for __all__
__all__ = ["Class1"]
# Ignore the following for PYI015
field26 = typing.Sequence[int]
field27 = list[str]
field28 = builtins.str
field29 = str
field30 = str | bytes | None

View File

@@ -1,35 +0,0 @@
# Shouldn't affect non-union field types.
field1: str
# Should emit for duplicate field types.
field2: str | str # PYI016: Duplicate union member `str`
# Should emit for union types in arguments.
def func1(arg1: int | int): # PYI016: Duplicate union member `int`
print(arg1)
# Should emit for unions in return types.
def func2() -> str | str: # PYI016: Duplicate union member `str`
return "my string"
# Should emit in longer unions, even if not directly adjacent.
field3: str | str | int # PYI016: Duplicate union member `str`
field4: int | int | str # PYI016: Duplicate union member `int`
field5: str | int | str # PYI016: Duplicate union member `str`
field6: int | bool | str | int # PYI016: Duplicate union member `int`
# Shouldn't emit for non-type unions.
field7 = str | str
# Should emit for strangely-bracketed unions.
field8: int | (str | int) # PYI016: Duplicate union member `int`
# Should handle user brackets when fixing.
field9: int | (int | str) # PYI016: Duplicate union member `int`
field10: (str | int) | str # PYI016: Duplicate union member `str`
# Should emit for nested unions.
field11: dict[int | int, str]

View File

@@ -1,32 +0,0 @@
# Shouldn't affect non-union field types.
field1: str
# Should emit for duplicate field types.
field2: str | str # PYI016: Duplicate union member `str`
# Should emit for union types in arguments.
def func1(arg1: int | int): # PYI016: Duplicate union member `int`
print(arg1)
# Should emit for unions in return types.
def func2() -> str | str: # PYI016: Duplicate union member `str`
return "my string"
# Should emit in longer unions, even if not directly adjacent.
field3: str | str | int # PYI016: Duplicate union member `str`
field4: int | int | str # PYI016: Duplicate union member `int`
field5: str | int | str # PYI016: Duplicate union member `str`
field6: int | bool | str | int # PYI016: Duplicate union member `int`
# Shouldn't emit for non-type unions.
field7 = str | str
# Should emit for strangely-bracketed unions.
field8: int | (str | int) # PYI016: Duplicate union member `int`
# Should handle user brackets when fixing.
field9: int | (int | str) # PYI016: Duplicate union member `int`
field10: (str | int) | str # PYI016: Duplicate union member `str`
# Should emit for nested unions.
field11: dict[int | int, str]

View File

@@ -1,33 +0,0 @@
# From https://github.com/PyCQA/flake8-pyi/blob/4212bec43dbc4020a59b90e2957c9488575e57ba/tests/type_comments.pyi
from collections.abc import Sequence
from typing import TypeAlias
A: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
B: TypeAlias = None # type: str # And here's an extra comment about why it's that type # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
C: TypeAlias = None #type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
D: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
E: TypeAlias = None# type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
F: TypeAlias = None#type:int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
def func(
arg1, # type: dict[str, int] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
arg2 # type: Sequence[bytes] # And here's some more info about this arg # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
): ...
class Foo:
Attr: TypeAlias = None # type: set[str] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
G: TypeAlias = None # type: ignore
H: TypeAlias = None # type: ignore[attr-defined]
I: TypeAlias = None #type: ignore
J: TypeAlias = None # type: ignore
K: TypeAlias = None# type: ignore
L: TypeAlias = None#type:ignore
# Whole line commented out # type: int
M: TypeAlias = None # type: can't parse me!
class Bar:
N: TypeAlias = None # type: can't parse me either!
# This whole line is commented out and indented # type: str

View File

@@ -1,33 +0,0 @@
# From https://github.com/PyCQA/flake8-pyi/blob/4212bec43dbc4020a59b90e2957c9488575e57ba/tests/type_comments.pyi
from collections.abc import Sequence
from typing import TypeAlias
A: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
B: TypeAlias = None # type: str # And here's an extra comment about why it's that type # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
C: TypeAlias = None #type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
D: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
E: TypeAlias = None# type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
F: TypeAlias = None#type:int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
def func(
arg1, # type: dict[str, int] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
arg2 # type: Sequence[bytes] # And here's some more info about this arg # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
): ...
class Foo:
Attr: TypeAlias = None # type: set[str] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
G: TypeAlias = None # type: ignore
H: TypeAlias = None # type: ignore[attr-defined]
I: TypeAlias = None #type: ignore
J: TypeAlias = None # type: ignore
K: TypeAlias = None# type: ignore
L: TypeAlias = None#type:ignore
# Whole line commented out # type: int
M: TypeAlias = None # type: can't parse me!
class Bar:
N: TypeAlias = None # type: can't parse me either!
# This whole line is commented out and indented # type: str

View File

@@ -49,18 +49,3 @@ def test_list_expressions(param1, param2):
@pytest.mark.parametrize([some_expr, "param2"], [1, 2, 3])
def test_list_mixed_expr_literal(param1, param2):
...
@pytest.mark.parametrize(("param1, " "param2, " "param3"), [(1, 2, 3), (4, 5, 6)])
def test_implicit_str_concat_with_parens(param1, param2, param3):
...
@pytest.mark.parametrize("param1, " "param2, " "param3", [(1, 2, 3), (4, 5, 6)])
def test_implicit_str_concat_no_parens(param1, param2, param3):
...
@pytest.mark.parametrize((("param1, " "param2, " "param3")), [(1, 2, 3), (4, 5, 6)])
def test_implicit_str_concat_with_multi_parens(param1, param2, param3):
...

View File

@@ -2,13 +2,3 @@ def x(y):
if not y:
return
return None # error
class BaseCache:
def get(self, key: str) -> str | None:
print(f"{key} not found")
return None
def get(self, key: str) -> None:
print(f"{key} not found")
return None

View File

@@ -293,30 +293,3 @@ def x(y):
def foo(baz: str) -> str:
return baz
def end_of_statement():
def example():
if True:
return ""
def example():
if True:
return ""
def example():
if True:
return "" # type: ignore
def example():
if True:
return "" ;
def example():
if True:
return "" \
; # type: ignore

View File

@@ -3,7 +3,23 @@
###
def x():
a = 1
return a # RET504
return a # error
def x():
a = 1
print(a)
a = 2
return a # error
def x():
a = 1
if True:
return a # error
a = 2
print(a)
return a
# Can be refactored false positives
@@ -149,6 +165,44 @@ def my_func():
return foo
# Refactored incorrect false positives
# See test cases above marked: "Can be refactored false positives"
# https://github.com/afonasev/flake8-return/issues/47#issuecomment-1122571066
def get_bar_if_exists(obj):
if hasattr(obj, "bar"):
return str(obj.bar)
return ""
# https://github.com/afonasev/flake8-return/issues/47#issue-641117366
def x():
formatted = _USER_AGENT_FORMATTER.format(format_string, **values)
# clean up after any blank components
return formatted.replace("()", "").replace(" ", " ").strip()
# https://github.com/afonasev/flake8-return/issues/47#issue-641117366
def user_agent_username(username=None):
if not username:
return ""
username = username.replace(" ", "_") # Avoid spaces or %20.
try:
username.encode("ascii") # just test,
# but not actually use it
except UnicodeEncodeError:
username = quote(username.encode("utf-8"))
else:
# % is legal in the default $wgLegalTitleChars
# This is so that ops know the real pywikibot will not
# allow a useragent in the username to allow through a
# hand-coded percent-encoded value.
if "%" in username:
username = quote(username)
return username
# https://github.com/afonasev/flake8-return/issues/116#issue-1367575481
def no_exception_loop():
success = False
@@ -206,69 +260,3 @@ def nonlocal_assignment():
nonlocal X
X = 1
return X
def decorator() -> Flask:
app = Flask(__name__)
@app.route("/hello")
def hello() -> str:
"""Hello endpoint."""
return "Hello, World!"
return app
def default():
y = 1
def f(x=y) -> X:
return x
return y
# Multiple assignment
def get_queryset(option_1, option_2):
queryset: Any = None
queryset = queryset.filter(a=1)
if option_1:
queryset = queryset.annotate(b=Value(2))
if option_2:
queryset = queryset.filter(c=3)
return queryset
def get_queryset():
queryset = Model.filter(a=1)
queryset = queryset.filter(c=3)
return queryset
def get_queryset():
queryset = Model.filter(a=1)
return queryset # RET504
# Function arguments
def str_to_bool(val):
if isinstance(val, bool):
return val
val = val.strip().lower()
if val in ("1", "true", "yes"):
return True
return False
def str_to_bool(val):
if isinstance(val, bool):
return val
val = 1
return val # RET504
def str_to_bool(val):
if isinstance(val, bool):
return some_obj
return val

View File

@@ -16,26 +16,15 @@ if isinstance(a, int) or isinstance(b, bool) or isinstance(a, float): # SIM101
if (isinstance(a, int) or isinstance(a, float)) and isinstance(b, bool): # SIM101
pass
if isinstance(a.b, int) or isinstance(a.b, float): # SIM101
pass
if isinstance(a(), int) or isinstance(a(), float): # SIM101
pass
if isinstance(a, int) and isinstance(b, bool) or isinstance(a, float):
pass
if isinstance(a, bool) or isinstance(b, str):
pass
if isinstance(a, int) or isinstance(a.b, float):
pass
def f():
# OK
def isinstance(a, b):
return False
if isinstance(a, int) or isinstance(a, float):
pass

View File

@@ -46,10 +46,10 @@ if a:
if b:
c
while x > 0:
while True:
# SIM102
if y > 0:
if z > 0:
if True:
if True:
"""this
is valid"""
@@ -64,8 +64,8 @@ is valid"""
# SIM102
if x > 0:
if y > 0:
if True:
if True:
"""this
is valid"""
@@ -78,7 +78,7 @@ is valid"""
("so is"
"this for some reason")
while x > 0:
while True:
# SIM102
if node.module:
if node.module == "multiprocessing" or node.module.startswith(
@@ -129,15 +129,3 @@ if a:
print("baz")
else:
print("bar")
# OK
if False:
if a:
pass
# OK
if True:
if a:
pass

View File

@@ -59,15 +59,3 @@ def bar():
return foo()
except ValueError:
pass
def with_ellipsis():
try:
foo()
except ValueError:
...
def with_ellipsis_and_return():
try:
return foo()
except ValueError:
...

View File

@@ -9,17 +9,6 @@ os.environ.get('foo', 'bar')
os.getenv('foo')
env = os.environ.get('foo')
env = os.environ['foo']
if env := os.environ.get('foo'):
pass
if env := os.environ['foo']:
pass
# Good
os.environ['FOO']
@@ -28,13 +17,3 @@ os.environ.get('FOO')
os.environ.get('FOO', 'bar')
os.getenv('FOO')
env = os.getenv('FOO')
if env := os.getenv('FOO'):
pass
env = os.environ['FOO']
if env := os.environ['FOO']:
pass

View File

@@ -20,5 +20,3 @@ for key in list(obj.keys()):
{k: k for k in obj.keys()} # SIM118
(k for k in obj.keys()) # SIM118
key in (obj or {}).keys() # SIM118

View File

@@ -1,10 +1,10 @@
if a or True: # SIM222
if a or True: # SIM223
pass
if (a or b) or True: # SIM222
if (a or b) or True: # SIM223
pass
if a or (b or True): # SIM222
if a or (b or True): # SIM223
pass
if a and True: # OK
@@ -16,139 +16,3 @@ if True: # OK
def validate(self, value):
return json.loads(value) or True # OK
if a or f() or b or g() or True: # OK
pass
if a or f() or True or g() or b: # SIM222
pass
if True or f() or a or g() or b: # SIM222
pass
if a or True or f() or b or g(): # SIM222
pass
if a and f() and b and g() and False: # OK
pass
if a and f() and False and g() and b: # OK
pass
if False and f() and a and g() and b: # OK
pass
if a and False and f() and b and g(): # OK
pass
a or "" or True # SIM222
a or "foo" or True or "bar" # SIM222
a or 0 or True # SIM222
a or 1 or True or 2 # SIM222
a or 0.0 or True # SIM222
a or 0.1 or True or 0.2 # SIM222
a or [] or True # SIM222
a or list([]) or True # SIM222
a or [1] or True or [2] # SIM222
a or list([1]) or True or list([2]) # SIM222
a or {} or True # SIM222
a or dict() or True # SIM222
a or {1: 1} or True or {2: 2} # SIM222
a or dict({1: 1}) or True or dict({2: 2}) # SIM222
a or set() or True # SIM222
a or set(set()) or True # SIM222
a or {1} or True or {2} # SIM222
a or set({1}) or True or set({2}) # SIM222
a or () or True # SIM222
a or tuple(()) or True # SIM222
a or (1,) or True or (2,) # SIM222
a or tuple((1,)) or True or tuple((2,)) # SIM222
a or frozenset() or True # SIM222
a or frozenset(frozenset()) or True # SIM222
a or frozenset({1}) or True or frozenset({2}) # SIM222
a or frozenset(frozenset({1})) or True or frozenset(frozenset({2})) # SIM222
# Inside test `a` is simplified.
bool(a or [1] or True or [2]) # SIM222
assert a or [1] or True or [2] # SIM222
if (a or [1] or True or [2]) and (a or [1] or True or [2]): # SIM222
pass
0 if a or [1] or True or [2] else 1 # SIM222
while a or [1] or True or [2]: # SIM222
pass
[
0
for a in range(10)
for b in range(10)
if a or [1] or True or [2] # SIM222
if b or [1] or True or [2] # SIM222
]
{
0
for a in range(10)
for b in range(10)
if a or [1] or True or [2] # SIM222
if b or [1] or True or [2] # SIM222
}
{
0: 0
for a in range(10)
for b in range(10)
if a or [1] or True or [2] # SIM222
if b or [1] or True or [2] # SIM222
}
(
0
for a in range(10)
for b in range(10)
if a or [1] or True or [2] # SIM222
if b or [1] or True or [2] # SIM222
)
# Outside test `a` is not simplified.
a or [1] or True or [2] # SIM222
if (a or [1] or True or [2]) == (a or [1]): # SIM222
pass
if f(a or [1] or True or [2]): # SIM222
pass

View File

@@ -12,138 +12,3 @@ if a or False:
if False:
pass
if a and f() and b and g() and False: # OK
pass
if a and f() and False and g() and b: # SIM223
pass
if False and f() and a and g() and b: # SIM223
pass
if a and False and f() and b and g(): # SIM223
pass
if a or f() or b or g() or True: # OK
pass
if a or f() or True or g() or b: # OK
pass
if True or f() or a or g() or b: # OK
pass
if a or True or f() or b or g(): # OK
pass
a and "" and False # SIM223
a and "foo" and False and "bar" # SIM223
a and 0 and False # SIM223
a and 1 and False and 2 # SIM223
a and 0.0 and False # SIM223
a and 0.1 and False and 0.2 # SIM223
a and [] and False # SIM223
a and list([]) and False # SIM223
a and [1] and False and [2] # SIM223
a and list([1]) and False and list([2]) # SIM223
a and {} and False # SIM223
a and dict() and False # SIM223
a and {1: 1} and False and {2: 2} # SIM223
a and dict({1: 1}) and False and dict({2: 2}) # SIM223
a and set() and False # SIM223
a and set(set()) and False # SIM223
a and {1} and False and {2} # SIM223
a and set({1}) and False and set({2}) # SIM223
a and () and False # SIM222
a and tuple(()) and False # SIM222
a and (1,) and False and (2,) # SIM222
a and tuple((1,)) and False and tuple((2,)) # SIM222
a and frozenset() and False # SIM222
a and frozenset(frozenset()) and False # SIM222
a and frozenset({1}) and False and frozenset({2}) # SIM222
a and frozenset(frozenset({1})) and False and frozenset(frozenset({2})) # SIM222
# Inside test `a` is simplified.
bool(a and [] and False and []) # SIM223
assert a and [] and False and [] # SIM223
if (a and [] and False and []) or (a and [] and False and []): # SIM223
pass
0 if a and [] and False and [] else 1 # SIM222
while a and [] and False and []: # SIM223
pass
[
0
for a in range(10)
for b in range(10)
if a and [] and False and [] # SIM223
if b and [] and False and [] # SIM223
]
{
0
for a in range(10)
for b in range(10)
if a and [] and False and [] # SIM223
if b and [] and False and [] # SIM223
}
{
0: 0
for a in range(10)
for b in range(10)
if a and [] and False and [] # SIM223
if b and [] and False and [] # SIM223
}
(
0
for a in range(10)
for b in range(10)
if a and [] and False and [] # SIM223
if b and [] and False and [] # SIM223
)
# Outside test `a` is not simplified.
a and [] and False and [] # SIM223
if (a and [] and False and []) == (a and []): # SIM223
pass
if f(a and [] and False and []): # SIM223
pass

View File

@@ -6,8 +6,6 @@
"yoda" <= compare # SIM300
"yoda" < compare # SIM300
42 > age # SIM300
-42 > age # SIM300
+42 > age # SIM300
YODA == age # SIM300
YODA > age # SIM300
YODA >= age # SIM300

View File

@@ -1,27 +0,0 @@
# SIM910
{}.get(key, None)
# SIM910
{}.get("key", None)
# OK
{}.get(key)
# OK
{}.get("key")
# OK
{}.get(key, False)
# OK
{}.get("key", False)
# SIM910
if a := {}.get(key, None):
pass
# SIM910
a = {}.get(key, None)
# SIM910
({}).get(key, None)

View File

@@ -31,6 +31,3 @@ typing.TypedDict.anything()
# import aliases are resolved
import typing as totally_not_typing
totally_not_typing.TypedDict
# relative imports are respected
from .typing import TypedDict

View File

@@ -0,0 +1,11 @@
# module members cannot be imported with that syntax
import typing.TypedDict
# we don't track reassignments
import typing, other
typing = other
typing.TypedDict()
# yet another false positive
def foo(typing):
typing.TypedDict()

View File

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

View File

@@ -1,3 +0,0 @@
from typing import Tuple
x: Tuple

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