Compare commits

..

1 Commits

Author SHA1 Message Date
Charlie Marsh
6783d8a53b Don't expand groups for trailing end-of-line comments 2023-08-09 20:17:31 -04:00
1224 changed files with 57023 additions and 74479 deletions

136
.github/workflows/benchmark.yaml vendored Normal file
View File

@@ -0,0 +1,136 @@
name: Benchmark
on:
pull_request:
paths:
- "Cargo.toml"
- "Cargo.lock"
- "rust-toolchain"
- "crates/**"
- "!crates/ruff_dev"
- "!crates/ruff_shrinking"
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"
run: cargo bench -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"
run: cargo bench -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 critcmp"
uses: taiki-e/install-action@v2
with:
tool: critcmp
- 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

@@ -30,7 +30,7 @@ jobs:
with:
fetch-depth: 0
- uses: tj-actions/changed-files@v38
- uses: tj-actions/changed-files@v37
id: changed
with:
files_yaml: |
@@ -106,9 +106,10 @@ jobs:
shell: bash
# We can't reject unreferenced snapshots on windows because flake8_executable can't run on windows
run: cargo insta test --all --all-features
# Check linter fix compatibility with Black
- name: "Check Black compatibility"
run: cargo test --package ruff_cli --test black_compatibility_test -- --ignored
- run: cargo test --package ruff_cli --test black_compatibility_test -- --ignored
# TODO: Skipped as it's currently broken. The resource were moved from the
# ruff_cli to ruff crate, but this test was not updated.
if: false
# Check for broken links in the documentation.
- run: cargo doc --all --no-deps
env:
@@ -327,7 +328,7 @@ jobs:
name: "Formatter ecosystem and progress checks"
runs-on: ubuntu-latest
needs: determine_changes
if: needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main'
if: needs.determine_changes.outputs.formatter == 'true'
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
@@ -337,31 +338,6 @@ jobs:
- name: "Formatter progress"
run: scripts/formatter_ecosystem_checks.sh
- name: "Github step summary"
run: cat target/progress_projects_stats.txt > $GITHUB_STEP_SUMMARY
run: grep "similarity index" target/progress_projects_log.txt | sort > $GITHUB_STEP_SUMMARY
- name: "Remove checkouts from cache"
run: rm -r target/progress_projects
benchmarks:
runs-on: ubuntu-latest
steps:
- name: "Checkout Branch"
uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@v2
with:
tool: cargo-codspeed
- uses: Swatinem/rust-cache@v2
- name: "Build benchmarks"
run: cargo codspeed build --features codspeed -p ruff_benchmark
- name: "Run benchmarks"
uses: CodSpeedHQ/action@v1
with:
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}

View File

@@ -40,7 +40,7 @@ jobs:
run: mkdocs build --strict -f mkdocs.generated.yml
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@v3.1.0
uses: cloudflare/wrangler-action@3.0.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}

View File

@@ -40,7 +40,7 @@ jobs:
working-directory: playground
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@v3.1.0
uses: cloudflare/wrangler-action@3.0.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}

View File

@@ -2,7 +2,7 @@ name: PR Check Comment
on:
workflow_run:
workflows: [CI]
workflows: [CI, Benchmark]
types: [completed]
workflow_dispatch:
inputs:
@@ -43,34 +43,42 @@ jobs:
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'
if: steps.download-ecosystem-result.outputs.found_artifact == 'true' || steps.download-benchmark-result.outputs.found_artifact == 'true'
run: |
echo '## PR Check Results' >> comment.txt
echo "### Ecosystem" >> comment.txt
cat pr/ecosystem/ecosystem-result >> comment.txt
echo "" >> comment.txt
echo 'comment<<EOF' >> $GITHUB_OUTPUT
cat comment.txt >> $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: Find Comment
uses: peter-evans/find-comment@v2
if: steps.generate-comment.outcome == 'success'
id: find-comment
with:
issue-number: ${{ steps.pr-number.outputs.pr-number }}
comment-author: "github-actions[bot]"
body-includes: PR Check Results
- name: Create or update comment
if: steps.find-comment.outcome == 'success'
uses: peter-evans/create-or-update-comment@v3
if: steps.generate-comment.outputs.comment
uses: thollander/actions-comment-pull-request@v2
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ steps.pr-number.outputs.pr-number }}
body-path: comment.txt
edit-mode: replace
pr_number: ${{ steps.pr-number.outputs.pr-number }}
message: ${{ steps.generate-comment.outputs.comment }}
comment_tag: PR Check Results

View File

@@ -571,7 +571,7 @@ An alternative is to convert the perf data to `flamegraph.svg` using
[flamegraph](https://github.com/flamegraph-rs/flamegraph) (`cargo install flamegraph`):
```shell
flamegraph --perfdata perf.data --no-inline
flamegraph --perfdata perf.data
```
#### Mac

130
Cargo.lock generated
View File

@@ -14,18 +14,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"cfg-if",
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.20"
@@ -414,28 +402,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "codspeed"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aeec2fbed4969dc38b5ca201115dd5c2614b8ef78e0a7221dd5f0977fb1552b"
dependencies = [
"colored",
"libc",
"serde_json",
]
[[package]]
name = "codspeed-criterion-compat"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b13f0a08d40ce7c95bdf288f725b975e62fcadfa8ba152340943bab6de43af7"
dependencies = [
"codspeed",
"colored",
"criterion",
]
[[package]]
name = "colorchoice"
version = "1.0.0"
@@ -834,20 +800,15 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.286"
version = "0.0.284"
dependencies = [
"anyhow",
"clap",
"colored",
"configparser",
"itertools",
"log",
"once_cell",
"pep440_rs",
"pretty_assertions",
"regex",
"ruff",
"ruff_workspace",
"rustc-hash",
"serde",
"serde_json",
@@ -903,10 +864,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
[[package]]
@@ -1032,16 +991,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "imara-diff"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
dependencies = [
"ahash",
"hashbrown 0.12.3",
]
[[package]]
name = "imperative"
version = "1.0.4"
@@ -2093,7 +2042,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.286"
version = "0.0.284"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2101,9 +2050,11 @@ dependencies = [
"chrono",
"clap",
"colored",
"dirs 5.0.1",
"fern",
"glob",
"globset",
"ignore",
"imperative",
"insta",
"is-macro",
@@ -2143,6 +2094,7 @@ dependencies = [
"serde",
"serde_json",
"serde_with",
"shellexpand",
"similar",
"smallvec",
"strum",
@@ -2154,7 +2106,6 @@ dependencies = [
"typed-arena",
"unicode-width",
"unicode_names2",
"uuid",
"wsl",
]
@@ -2162,14 +2113,12 @@ dependencies = [
name = "ruff_benchmark"
version = "0.0.0"
dependencies = [
"codspeed-criterion-compat",
"criterion",
"mimalloc",
"once_cell",
"ruff",
"ruff_python_ast",
"ruff_python_formatter",
"ruff_python_index",
"ruff_python_parser",
"serde",
"serde_json",
@@ -2192,7 +2141,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.286"
version = "0.0.284"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2210,7 +2159,6 @@ dependencies = [
"glob",
"ignore",
"insta",
"is-macro",
"itertools",
"itoa",
"log",
@@ -2222,7 +2170,6 @@ dependencies = [
"ruff",
"ruff_cache",
"ruff_diagnostics",
"ruff_formatter",
"ruff_macros",
"ruff_python_ast",
"ruff_python_formatter",
@@ -2230,7 +2177,6 @@ dependencies = [
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"ruff_workspace",
"rustc-hash",
"serde",
"serde_json",
@@ -2238,9 +2184,7 @@ dependencies = [
"similar",
"strum",
"tempfile",
"thiserror",
"tikv-jemallocator",
"tracing",
"ureq",
"walkdir",
"wild",
@@ -2253,7 +2197,6 @@ dependencies = [
"anyhow",
"clap",
"ignore",
"imara-diff",
"indicatif",
"indoc",
"itertools",
@@ -2273,7 +2216,6 @@ dependencies = [
"ruff_python_parser",
"ruff_python_stdlib",
"ruff_python_trivia",
"ruff_workspace",
"schemars",
"serde",
"serde_json",
@@ -2374,7 +2316,6 @@ dependencies = [
"insta",
"is-macro",
"itertools",
"memchr",
"once_cell",
"ruff_formatter",
"ruff_python_ast",
@@ -2389,7 +2330,6 @@ dependencies = [
"similar",
"smallvec",
"thiserror",
"unicode-width",
]
[[package]]
@@ -2413,6 +2353,7 @@ dependencies = [
"is-macro",
"itertools",
"lexical-parse-float",
"num-bigint",
"num-traits",
"rand",
"unic-ucd-category",
@@ -2434,7 +2375,6 @@ dependencies = [
"ruff_text_size",
"rustc-hash",
"static_assertions",
"test-case",
"tiny-keccak",
"unic-emoji-char",
"unic-ucd-ident",
@@ -2460,7 +2400,6 @@ dependencies = [
"num-traits",
"ruff_index",
"ruff_python_ast",
"ruff_python_parser",
"ruff_python_stdlib",
"ruff_source_file",
"ruff_text_size",
@@ -2533,48 +2472,18 @@ dependencies = [
"log",
"ruff",
"ruff_diagnostics",
"ruff_formatter",
"ruff_python_ast",
"ruff_python_codegen",
"ruff_python_formatter",
"ruff_python_index",
"ruff_python_parser",
"ruff_source_file",
"ruff_text_size",
"ruff_workspace",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
]
[[package]]
name = "ruff_workspace"
version = "0.0.0"
dependencies = [
"anyhow",
"colored",
"dirs 5.0.1",
"glob",
"globset",
"ignore",
"itertools",
"log",
"path-absolutize",
"pep440_rs",
"regex",
"ruff",
"ruff_cache",
"ruff_macros",
"rustc-hash",
"schemars",
"serde",
"shellexpand",
"strum",
"tempfile",
"toml",
]
[[package]]
name = "rust-stemmers"
version = "1.2.0"
@@ -2757,9 +2666,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.105"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c"
dependencies = [
"itoa",
"ryu",
@@ -3410,26 +3319,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "1.4.1"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
dependencies = [
"getrandom",
"rand",
"uuid-macro-internal",
"wasm-bindgen",
]
[[package]]
name = "uuid-macro-internal"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7e1ba1f333bd65ce3c9f27de592fcbc256dafe3af2717f56d7c87761fbaccf4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.23",
]
checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be"
[[package]]
name = "valuable"

View File

@@ -49,8 +49,6 @@ toml = { version = "0.7.2" }
tracing = "0.1.37"
tracing-indicatif = "0.3.4"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
unicode-width = "0.1.10"
uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
wsl = { version = "0.1.0" }
# v1.0.1

View File

@@ -30,7 +30,7 @@ An extremely fast Python linter, written in Rust.
- 🤝 Python 3.11 compatibility
- 📦 Built-in caching, to avoid re-analyzing unchanged files
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
- 📏 Over [600 built-in rules](https://beta.ruff.rs/docs/rules/)
- 📏 Over [500 built-in rules](https://beta.ruff.rs/docs/rules/)
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the
built-in Flake8 rule set
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
@@ -140,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.286
rev: v0.0.284
hooks:
- id: ruff
```
@@ -233,7 +233,7 @@ linting command.
<!-- Begin section: Rules -->
**Ruff supports over 600 lint rules**, many of which are inspired by popular tools like Flake8,
**Ruff supports over 500 lint rules**, many of which are inspired by popular tools like Flake8,
isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
Rust as a first-party feature.

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.286"
version = "0.0.284"
description = """
Convert Flake8 configuration files to Ruff configuration files.
"""
@@ -14,16 +14,12 @@ license = { workspace = true }
[dependencies]
ruff = { path = "../ruff", default-features = false }
ruff_workspace = { path = "../ruff_workspace" }
anyhow = { workspace = true }
clap = { workspace = true }
colored = { workspace = true }
configparser = { version = "3.0.2" }
itertools = { workspace = true }
log = { workspace = true }
once_cell = { workspace = true }
pep440_rs = { version = "0.3.1", features = ["serde"] }
regex = { workspace = true }
rustc-hash = { workspace = true }
serde = { workspace = true }
@@ -31,6 +27,3 @@ serde_json = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
toml = { workspace = true }
[dev-dependencies]
pretty_assertions = "1.3.0"

View File

@@ -1,10 +0,0 @@
use super::black::Black;
use super::isort::Isort;
use super::pep621::Project;
#[derive(Default)]
pub(crate) struct ExternalConfig<'a> {
pub(crate) black: Option<&'a Black>,
pub(crate) isort: Option<&'a Isort>,
pub(crate) project: Option<&'a Project>,
}

View File

@@ -1,24 +1,12 @@
//! Utility to generate Ruff's `pyproject.toml` section from a Flake8 INI file.
mod black;
mod converter;
mod external_config;
mod isort;
mod parser;
mod pep621;
mod plugin;
mod pyproject;
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use configparser::ini::Ini;
use crate::converter::convert;
use crate::external_config::ExternalConfig;
use crate::plugin::Plugin;
use crate::pyproject::parse;
use ruff::flake8_to_ruff::{self, ExternalConfig};
use ruff::logging::{set_up_logging, LogLevel};
#[derive(Parser)]
@@ -37,7 +25,7 @@ struct Args {
pyproject: Option<PathBuf>,
/// List of plugins to enable.
#[arg(long, value_delimiter = ',')]
plugin: Option<Vec<Plugin>>,
plugin: Option<Vec<flake8_to_ruff::Plugin>>,
}
fn main() -> Result<()> {
@@ -51,7 +39,7 @@ fn main() -> Result<()> {
let config = ini.load(args.file).map_err(|msg| anyhow::anyhow!(msg))?;
// Read the pyproject.toml file.
let pyproject = args.pyproject.map(parse).transpose()?;
let pyproject = args.pyproject.map(flake8_to_ruff::parse).transpose()?;
let external_config = pyproject
.as_ref()
.and_then(|pyproject| pyproject.tool.as_ref())
@@ -69,7 +57,7 @@ fn main() -> Result<()> {
};
// Create Ruff's pyproject.toml section.
let pyproject = convert(&config, &external_config, args.plugin);
let pyproject = flake8_to_ruff::convert(&config, &external_config, args.plugin)?;
#[allow(clippy::print_stdout)]
{

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.286"
version = "0.0.284"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -36,9 +36,11 @@ bitflags = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true, features = ["derive", "string"], optional = true }
colored = { workspace = true }
dirs = { version = "5.0.0" }
fern = { version = "0.6.1" }
glob = { workspace = true }
globset = { workspace = true }
ignore = { workspace = true }
imperative = { version = "1.0.4" }
is-macro = { workspace = true }
itertools = { workspace = true }
@@ -60,21 +62,23 @@ quick-junit = { version = "0.3.2" }
regex = { workspace = true }
result-like = { version = "0.4.6" }
rustc-hash = { workspace = true }
schemars = { workspace = true, optional = true }
semver = { version = "1.0.16" }
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { version = "3.0.0" }
similar = { workspace = true }
shellexpand = { workspace = true }
smallvec = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
thiserror = { version = "1.0.43" }
toml = { workspace = true }
typed-arena = { version = "2.0.2" }
unicode-width = { workspace = true }
unicode-width = { version = "0.1.10" }
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }
uuid = { workspace = true, features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
wsl = { version = "0.1.0" }
[dev-dependencies]

View File

@@ -19,12 +19,3 @@ def foo(x, y, z):
class A():
pass
# b = c
dictionary = {
# "key1": 123, # noqa: ERA001
# "key2": 456,
# "key3": 789, # test
}
#import os # noqa

View File

@@ -152,9 +152,3 @@ def f(a: Union[str, bytes, Any]) -> None: ...
def f(a: Optional[Any]) -> None: ...
def f(a: Annotated[Any, ...]) -> None: ...
def f(a: "Union[str, bytes, Any]") -> None: ...
class Foo:
@decorator()
def __init__(self: "Foo", foo: int):
...

View File

@@ -69,7 +69,6 @@ g_action.set_enabled(True)
settings.set_enable_developer_extras(True)
foo.is_(True)
bar.is_not(False)
next(iter([]), False)
class Registry:
def __init__(self) -> None:

View File

@@ -68,20 +68,6 @@ def this_is_also_wrong(value={}):
...
class Foo:
@staticmethod
def this_is_also_wrong_and_more_indented(value={}):
pass
def multiline_arg_wrong(value={
}):
...
def single_line_func_wrong(value = {}): ...
def and_this(value=set()):
...
@@ -230,10 +216,6 @@ def timedelta_okay(value=dt.timedelta(hours=1)):
def path_okay(value=Path(".")):
pass
# B008 allow arbitrary call with immutable annotation
def immutable_annotation_call(value: Sequence[int] = foo()):
pass
# B006 and B008
# We should handle arbitrary nesting of these B008.
def nested_combo(a=[float(3), dt.datetime.now()]):
@@ -279,32 +261,3 @@ def mutable_annotations(
d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
):
pass
def single_line_func_wrong(value: dict[str, str] = {}):
"""Docstring"""
def single_line_func_wrong(value: dict[str, str] = {}):
"""Docstring"""
...
def single_line_func_wrong(value: dict[str, str] = {}):
"""Docstring"""; ...
def single_line_func_wrong(value: dict[str, str] = {}):
"""Docstring"""; \
...
def single_line_func_wrong(value: dict[str, str] = {
# This is a comment
}):
"""Docstring"""
def single_line_func_wrong(value: dict[str, str] = {}) \
: \
"""Docstring"""

View File

@@ -1,18 +0,0 @@
import custom
from custom import ImmutableTypeB
def okay(foo: ImmutableTypeB = []):
...
def okay(foo: custom.ImmutableTypeA = []):
...
def okay(foo: custom.ImmutableTypeB = []):
...
def error_due_to_missing_import(foo: ImmutableTypeA = []):
...

View File

@@ -1,7 +1,6 @@
from typing import List
import fastapi
import custom
from fastapi import Query
@@ -17,9 +16,5 @@ def okay(data: List[str] = Query(None)):
...
def okay(data: custom.ImmutableTypeA = foo()):
...
def error_due_to_missing_import(data: List[str] = Depends(None)):
...

View File

@@ -22,10 +22,6 @@ tuple(
"o"]
)
)
set(set())
set(list())
set(tuple())
sorted(reversed())
# Nested sorts with differing keyword arguments. Not flagged.
sorted(sorted(x, key=lambda y: y))

View File

@@ -15,6 +15,11 @@ filter(func, map(lambda v: v, nums))
_ = f"{set(map(lambda x: x % 2 == 0, nums))}"
_ = f"{dict(map(lambda v: (v, v**2), nums))}"
# Error, but unfixable.
# For simple expressions, this could be: `(x if x else 1 for x in nums)`.
# For more complex expressions, this would differ: `(x + 2 if x else 3 for x in nums)`.
map(lambda x=1: x, nums)
# False negatives.
map(lambda x=2, y=1: x + y, nums, nums)
set(map(lambda x, y: x, nums, nums))
@@ -32,11 +37,3 @@ map(lambda x: lambda: x, range(4))
# Error: the `x` is overridden by the inner lambda.
map(lambda x: lambda x: x, range(4))
# Ok because of the default parameters, and variadic arguments.
map(lambda x=1: x, nums)
map(lambda *args: len(args), range(4))
map(lambda **kwargs: len(kwargs), range(4))
# Ok because multiple arguments are allowed.
dict(map(lambda k, v: (k, v), keys, values))

View File

@@ -1,31 +1,22 @@
def not_checked():
import math
import math # not checked
import altair # unconventional
import matplotlib.pyplot # unconventional
import numpy # unconventional
import pandas # unconventional
import seaborn # unconventional
import tkinter # unconventional
def unconventional():
import altair
import matplotlib.pyplot
import numpy
import pandas
import seaborn
import tkinter
import networkx
import altair as altr # unconventional
import matplotlib.pyplot as plot # unconventional
import numpy as nmp # unconventional
import pandas as pdas # unconventional
import seaborn as sbrn # unconventional
import tkinter as tkr # unconventional
def unconventional_aliases():
import altair as altr
import matplotlib.pyplot as plot
import numpy as nmp
import pandas as pdas
import seaborn as sbrn
import tkinter as tkr
import networkx as nxy
def conventional_aliases():
import altair as alt
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
import tkinter as tk
import networkx as nx
import altair as alt # conventional
import matplotlib.pyplot as plt # conventional
import numpy as np # conventional
import pandas as pd # conventional
import seaborn as sns # conventional
import tkinter as tk # conventional

View File

@@ -1,9 +0,0 @@
"""Test cases for difficult renames."""
def rename_global():
try:
global pandas
import pandas
except ImportError:
return False

View File

@@ -1,13 +0,0 @@
# PIE808
range(0, 10)
# OK
range(x, 10)
range(-15, 10)
range(10)
range(0)
range(0, 10, x)
range(0, 10, 1)
range(0, 10, step=1)
range(start=0, stop=10)
range(0, stop=10)

View File

@@ -64,8 +64,3 @@ 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):
...
@pytest.mark.parametrize(("param1,param2"), [(1, 2), (3, 4)])
def test_csv_with_parens(param1, param2):
...

View File

@@ -80,15 +80,3 @@ class Test(unittest.TestCase):
def test_assert_not_regexp_matches(self):
self.assertNotRegex("abc", r"abc") # Error
def test_fail_if(self):
self.failIf("abc") # Error
def test_fail_unless(self):
self.failUnless("abc") # Error
def test_fail_unless_equal(self):
self.failUnlessEqual(1, 2) # Error
def test_fail_if_equal(self):
self.failIfEqual(1, 2) # Error

View File

@@ -1,4 +1,3 @@
from pickle import PicklingError, UnpicklingError
import socket
import pytest
@@ -21,12 +20,6 @@ def test_error_no_argument_given():
with pytest.raises(socket.error):
raise ValueError("Can't divide 1 by 0")
with pytest.raises(PicklingError):
raise PicklingError("Can't pickle")
with pytest.raises(UnpicklingError):
raise UnpicklingError("Can't unpickle")
def test_error_match_is_empty():
with pytest.raises(ValueError, match=None):

View File

@@ -1,53 +0,0 @@
import pytest
@pytest.mark.parametrize("x", [1, 1, 2])
def test_error_literal(x):
...
a = 1
b = 2
c = 3
@pytest.mark.parametrize("x", [a, a, b, b, b, c])
def test_error_expr_simple(x):
...
@pytest.mark.parametrize(
"x",
[
(a, b),
# comment
(a, b),
(b, c),
],
)
def test_error_expr_complex(x):
...
@pytest.mark.parametrize("x", [a, b, (a), c, ((a))])
def test_error_parentheses(x):
...
@pytest.mark.parametrize(
"x",
[
a,
b,
(a),
c,
((a)),
],
)
def test_error_parentheses_trailing_comma(x):
...
@pytest.mark.parametrize("x", [1, 2])
def test_ok(x):
...

View File

@@ -43,12 +43,3 @@ message
assert something # OK
assert something and something_else # Error
assert something and something_else and something_third # Error
def test_multiline():
assert something and something_else; x = 1
x = 1; assert something and something_else
x = 1; \
assert something and something_else

View File

@@ -1,48 +0,0 @@
import unittest
class Test(unittest.TestCase):
def test_errors(self):
with self.assertRaises(ValueError):
raise ValueError
with self.assertRaises(expected_exception=ValueError):
raise ValueError
with self.failUnlessRaises(ValueError):
raise ValueError
with self.assertRaisesRegex(ValueError, "test"):
raise ValueError("test")
with self.assertRaisesRegex(ValueError, expected_regex="test"):
raise ValueError("test")
with self.assertRaisesRegex(
expected_exception=ValueError, expected_regex="test"
):
raise ValueError("test")
with self.assertRaisesRegex(
expected_regex="test", expected_exception=ValueError
):
raise ValueError("test")
with self.assertRaisesRegexp(ValueError, "test"):
raise ValueError("test")
def test_unfixable_errors(self):
with self.assertRaises(ValueError, msg="msg"):
raise ValueError
with self.assertRaises(
# comment
ValueError
):
raise ValueError
with (
self
# comment
.assertRaises(ValueError)
):
raise ValueError

View File

@@ -1,12 +0,0 @@
import unittest
import pytest
class Test(unittest.TestCase):
def test_pytest_raises(self):
with pytest.raises(ValueError):
raise ValueError
def test_errors(self):
with self.assertRaises(ValueError):
raise ValueError

View File

@@ -19,20 +19,11 @@ raise TypeError ()
raise TypeError \
()
# RSE102
raise TypeError \
();
# RSE102
raise TypeError(
)
# RSE102
raise (TypeError) (
)
# RSE102
raise TypeError(
# Hello, world!
@@ -61,21 +52,3 @@ class Class:
# OK
raise Class.error()
import ctypes
# OK
raise ctypes.WinError(1)
# RSE102
raise IndexError()from ZeroDivisionError
raise IndexError()\
from ZeroDivisionError
raise IndexError() from ZeroDivisionError
raise IndexError();

View File

@@ -320,9 +320,3 @@ def end_of_statement():
if True:
return "" \
; # type: ignore
def end_of_file():
if False:
return 1
x = 2 \

View File

@@ -73,7 +73,3 @@ print(foo.__dict__)
print(foo.__str__())
print(foo().__class__)
print(foo._asdict())
import os
os._exit()

View File

@@ -31,8 +31,6 @@ for key in list(obj.keys()):
key in (obj or {}).keys() # SIM118
(key) in (obj or {}).keys() # SIM118
from typing import KeysView

View File

@@ -1,31 +0,0 @@
## Banned modules ##
import torch
from torch import *
from tensorflow import a, b, c
import torch as torch_wearing_a_trenchcoat
# this should count as module level
x = 1; import tensorflow
# banning a module also bans any submodules
import torch.foo.bar
from tensorflow.foo import bar
from torch.foo.bar import *
# unlike TID251, inline imports are *not* banned
def my_cool_function():
import tensorflow.foo.bar
def another_cool_function():
from torch.foo import bar
def import_alias():
from torch.foo import bar
if TYPE_CHECKING:
import torch

View File

@@ -1,12 +0,0 @@
from __future__ import annotations
from datetime import date
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Birthday(DeclarativeBase):
__tablename__ = "birthday"
id: Mapped[int] = mapped_column(primary_key=True)
day: Mapped[date]

View File

@@ -27,8 +27,6 @@ def f(cls, x):
###
lambda x: print("Hello, world!")
lambda: print("Hello, world!")
class C:
###
@@ -204,14 +202,3 @@ class C:
###
def f(x: None) -> None:
_ = cast(Any, _identity)(x=x)
###
# Unused arguments with `locals`.
###
def f(bar: str):
print(locals())
class C:
def __init__(self, x) -> None:
print(locals())

View File

@@ -1,3 +0,0 @@
import os
import pandas
import foo.baz

View File

@@ -1,2 +0,0 @@
[tool.ruff]
line-length = 88

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env python3
# A copyright notice could go here
# A linter directive could go here
x = 1

View File

@@ -1,37 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import math\n",
"import os\n",
"\n",
"math.pi"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python (ruff)",
"language": "python",
"name": "ruff"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -1,37 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import math\n",
"import os\n",
"\n",
"math.pi"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python (ruff)",
"language": "python",
"name": "ruff"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -21,29 +21,3 @@ while i < 10:
print("error")
i += 1
# OK - no other way to write this
for i in range(10):
try:
print(f"{i}")
break
except:
print("error")
# OK - no other way to write this
for i in range(10):
try:
print(f"{i}")
continue
except:
print("error")
# OK - no other way to write this
for i in range(10):
try:
print(f"{i}")
if i > 0:
break
except:
print("error")

View File

@@ -28,6 +28,3 @@ mdtypes_template = {
'tag_full': [('mdtype', 'u4'), ('byte_count', 'u4')],
'tag_smalldata':[('byte_count_mdtype', 'u4'), ('data', 'S4')],
}
#: Okay
a = (1,

View File

@@ -30,10 +30,3 @@ def foo() -> None:
if __name__ == "__main__":
import g
import h; import i
if __name__ == "__main__":
import j; \
import k

View File

@@ -1,16 +1,11 @@
# aaaa
# aaaaa
# a
# a
# aa
# aaa
# aaaa
# a
# aa
# aaa
a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
if True: # noqa: E501
[12]
[12 ]
[1,2]
[1, 2]
b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""

View File

@@ -25,12 +25,6 @@ if (True) == TrueElement or x == TrueElement:
if res == True != False:
pass
if(True) == TrueElement or x == TrueElement:
pass
if (yield i) == True:
print("even")
#: Okay
if x not in y:
pass

View File

@@ -61,30 +61,3 @@ if x == types.X:
#: E721
assert type(res) is int
class Foo:
def asdf(self, value: str | None):
#: E721
if type(value) is str:
...
class Foo:
def type(self):
pass
def asdf(self, value: str | None):
#: E721
if type(value) is str:
...
class Foo:
def asdf(self, value: str | None):
def type():
pass
# Okay
if type(value) is str:
...

View File

@@ -133,8 +133,3 @@ def scope():
from collections.abc import Callable
f: Callable[[str, int, list[str]], list[str]] = lambda a, b, /, c: [*c, a * b]
class TemperatureScales(Enum):
CELSIUS = (lambda deg_c: deg_c)
FAHRENHEIT = (lambda deg_c: deg_c * 9 / 5 + 32)

View File

@@ -1,13 +0,0 @@
def func():
"""\
"""
def func():
"""\\
"""
def func():
"""\ \
"""

View File

@@ -99,16 +99,3 @@ import foo.bar as bop
import foo.bar.baz
print(bop.baz.read_csv("test.csv"))
# Test: isolated deletions.
if TYPE_CHECKING:
import a1
import a2
match *0, 1, *2:
case 0,:
import b1
import b2

View File

@@ -2,5 +2,6 @@
"{bar}{}".format(1, bar=2, spam=3) # F522
"{bar:{spam}}".format(bar=2, spam=3) # No issues
"{bar:{spam}}".format(bar=2, spam=3, eggs=4, ham=5) # F522
# Not fixable
(''
.format(x=2)) # F522
.format(x=2))

View File

@@ -28,6 +28,6 @@
"{1}{3}".format(1, 2, 3, 4) # F523, # F524
"{1} {8}".format(0, 1) # F523, # F524
# Multiline
# Not fixable
(''
.format(2))

View File

@@ -145,9 +145,3 @@ def f() -> None:
obj = Foo()
obj.do_thing()
def f():
try:
pass
except Exception as _:
pass

View File

@@ -154,14 +154,3 @@ def f() -> None:
print("hello")
except A as e :
print("oh no!")
def f():
x = 1
y = 2
def f():
x = 1
y = 2

View File

@@ -1,46 +0,0 @@
class Apples:
def _init_(self): # [bad-dunder-name]
pass
def __hello__(self): # [bad-dunder-name]
print("hello")
def __init_(self): # [bad-dunder-name]
# author likely unintentionally misspelled the correct init dunder.
pass
def _init_(self): # [bad-dunder-name]
# author likely unintentionally misspelled the correct init dunder.
pass
def ___neg__(self): # [bad-dunder-name]
# author likely accidentally added an additional `_`
pass
def __inv__(self): # [bad-dunder-name]
# author likely meant to call the invert dunder method
pass
def hello(self):
print("hello")
def __init__(self):
pass
def init(self):
# valid name even though someone could accidentally mean __init__
pass
def _protected_method(self):
print("Protected")
def __private_method(self):
print("Private")
@property
def __doc__(self):
return "Docstring"
def __foo_bar__(): # this is not checked by the [bad-dunder-name] rule
...

View File

@@ -14,12 +14,7 @@
"{:s} {:y}".format("hello", "world") # [bad-format-character]
"{:*^30s}".format("centered") # OK
"{:{s}}".format("hello", s="s") # OK (nested placeholder value not checked)
"{:{s:y}}".format("hello", s="s") # [bad-format-character] (nested placeholder format spec checked)
"{0:.{prec}g}".format(1.23, prec=15) # OK (cannot validate after nested placeholder)
"{0:.{foo}{bar}{foobar}y}".format(...) # OK (cannot validate after nested placeholders)
"{0:.{foo}x{bar}y{foobar}g}".format(...) # OK (all nested placeholders are consumed without considering in between chars)
"{:*^30s}".format("centered")
## f-strings

View File

@@ -13,7 +13,6 @@ print("foo %(foo)d bar %(bar)d" % {"foo": "1", "bar": "2"})
"%(key)d" % {"key": []}
print("%d" % ("%s" % ("nested",),))
"%d" % ((1, 2, 3),)
"%d" % (1 if x > 0 else [])
# False negatives
WORD = "abc"
@@ -56,4 +55,3 @@ r'\%03o' % (ord(c),)
"%d" % (len(foo),)
'(%r, %r, %r, %r)' % (hostname, address, username, '$PASSWORD')
'%r' % ({'server_school_roles': server_school_roles, 'is_school_multiserver_domain': is_school_multiserver_domain}, )
"%d" % (1 if x > 0 else 2)

View File

@@ -1,11 +1,10 @@
class Person: # [eq-without-hash]
class Person:
def __init__(self):
self.name = "monty"
def __eq__(self, other):
return isinstance(other, Person) and other.name == self.name
# OK
class Language:
def __init__(self):
self.name = "python"
@@ -15,9 +14,3 @@ class Language:
def __hash__(self):
return hash(self.name)
class MyClass:
def __eq__(self, other):
return True
__hash__ = None

View File

@@ -10,4 +10,3 @@ os.getenv("AA", "GOOD" + "BAD")
os.getenv("AA", "GOOD" + 1)
os.getenv("AA", "GOOD %s" % "BAD")
os.getenv("B", Z)

View File

@@ -10,8 +10,6 @@ os.getenv(key="foo", default="bar")
os.getenv(key=f"foo", default="bar")
os.getenv(key="foo" + "bar", default=1)
os.getenv(key=1 + "bar", default=1) # [invalid-envvar-value]
os.getenv("PATH_TEST" if using_clear_path else "PATH_ORIG")
os.getenv(1 if using_clear_path else "PATH_ORIG")
AA = "aa"
os.getenv(AA)

View File

@@ -1,62 +0,0 @@
import abc
class Person:
def developer_greeting(self, name): # [no-self-use]
print(f"Greetings {name}!")
def greeting_1(self): # [no-self-use]
print("Hello!")
def greeting_2(self): # [no-self-use]
print("Hi!")
# OK
def developer_greeting():
print("Greetings developer!")
# OK
class Person:
name = "Paris"
def __init__(self):
pass
def __cmp__(self, other):
print(24)
def __repr__(self):
return "Person"
def func(self):
...
def greeting_1(self):
print(f"Hello from {self.name} !")
@staticmethod
def greeting_2():
print("Hi!")
class Base(abc.ABC):
"""abstract class"""
@abstractmethod
def abstract_method(self):
"""abstract method could not be a function"""
raise NotImplementedError
class Sub(Base):
@override
def abstract_method(self):
print("concret method")
class Prop:
@property
def count(self):
return 24

View File

@@ -9,22 +9,13 @@ foo != "a" and foo != "b" and foo != "c"
foo == a or foo == "b" or foo == 3 # Mixed types.
# False negatives (the current implementation doesn't support Yoda conditions).
"a" == foo or "b" == foo or "c" == foo
"a" != foo and "b" != foo and "c" != foo
"a" == foo or foo == "b" or "c" == foo
foo == bar or baz == foo or qux == foo
foo == "a" or "b" == foo or foo == "c"
foo != "a" and "b" != foo and foo != "c"
foo == "a" or foo == "b" or "c" == bar or "d" == bar # Multiple targets
foo.bar == "a" or foo.bar == "b" # Attributes.
# OK
foo == "a" and foo == "b" and foo == "c" # `and` mixed with `==`.
@@ -41,13 +32,3 @@ foo not in {"a", "b", "c"} # Uses membership test already.
foo == "a" # Single comparison.
foo != "a" # Single comparison.
foo == "a" == "b" or foo == "c" # Multiple comparisons.
foo == bar == "b" or foo == "c" # Multiple comparisons.
foo == foo or foo == bar # Self-comparison.
foo[0] == "a" or foo[0] == "b" # Subscripts.
foo() == "a" or foo() == "b" # Calls.

View File

@@ -1,13 +0,0 @@
import subprocess
# Errors.
subprocess.run("ls")
subprocess.run("ls", shell=True)
# Non-errors.
subprocess.run("ls", check=True)
subprocess.run("ls", check=False)
subprocess.run("ls", shell=True, check=True)
subprocess.run("ls", shell=True, check=False)
foo.run("ls") # Not a subprocess.run call.
subprocess.bar("ls") # Not a subprocess.run call.

View File

@@ -1,3 +0,0 @@
from sys import *
exit(0)

View File

@@ -59,35 +59,3 @@ def f() -> None:
x = Union["str", "int"]
x: Union[str, int]
x: Union["str", "int"]
def f(x: Union[int : float]) -> None:
...
def f(x: Union[str, int : float]) -> None:
...
def f(x: Union[x := int]) -> None:
...
def f(x: Union[str, x := int]) -> None:
...
def f(x: Union[lambda: int]) -> None:
...
def f(x: Union[str, lambda: int]) -> None:
...
def f(x: Optional[int : float]) -> None:
...
def f(x: Optional[str, int : float]) -> None:
...

View File

@@ -1,7 +0,0 @@
"""
# coding=utf8""" # empty comment
"""
Invalid coding declaration since it is nested inside a docstring
The following empty comment tests for false positives as our implementation visits comments
"""

View File

@@ -1,7 +0,0 @@
# coding=utf8
print("Hello world")
"""
Regression test for https://github.com/astral-sh/ruff/issues/6756
The leading space must be removed to prevent invalid syntax.
"""

View File

@@ -1,7 +0,0 @@
# coding=utf8
print("Hello world")
"""
Regression test for https://github.com/astral-sh/ruff/issues/6756
The leading tab must be removed to prevent invalid syntax.
"""

View File

@@ -1,6 +0,0 @@
print("foo") # coding=utf8
print("Hello world")
"""
Invalid coding declaration due to a statement before the comment
"""

View File

@@ -1,7 +0,0 @@
x = 1 \
# coding=utf8
x = 2
"""
Invalid coding declaration due to continuation on preceding line
"""

View File

@@ -22,4 +22,3 @@ MyType = typing.NamedTuple("MyType", a=int, b=tuple[str, ...])
# unfixable
MyType = typing.NamedTuple("MyType", [("a", int)], [("b", str)])
MyType = typing.NamedTuple("MyType", [("a", int)], b=str)
MyType = typing.NamedTuple(typename="MyType", a=int, b=str)

View File

@@ -31,7 +31,6 @@ bool("foo")
bool("")
bool(b"")
bool(1.0)
int().denominator
# These become string or byte literals
str()
@@ -50,6 +49,3 @@ float(1.0)
bool()
bool(True)
bool(False)
# These become a literal but retain parentheses
int(1).denominator

View File

@@ -1,169 +0,0 @@
from typing import List
def func():
pass
nums = []
nums2 = []
nums3: list[int] = func()
nums4: List[int]
class C:
def append(self, x):
pass
# Errors.
# FURB113
nums.append(1)
nums.append(2)
pass
# FURB113
nums3.append(1)
nums3.append(2)
pass
# FURB113
nums4.append(1)
nums4.append(2)
pass
# FURB113
nums.append(1)
nums2.append(1)
nums.append(2)
nums.append(3)
pass
# FURB113
nums.append(1)
nums2.append(1)
nums.append(2)
# FURB113
nums3.append(1)
nums.append(3)
# FURB113
nums4.append(1)
nums4.append(2)
nums3.append(2)
pass
# FURB113
nums.append(1)
nums.append(2)
nums.append(3)
if True:
# FURB113
nums.append(1)
nums.append(2)
if True:
# FURB113
nums.append(1)
nums.append(2)
pass
if True:
# FURB113
nums.append(1)
nums2.append(1)
nums.append(2)
nums.append(3)
def yes_one(x: list[int]):
# FURB113
x.append(1)
x.append(2)
def yes_two(x: List[int]):
# FURB113
x.append(1)
x.append(2)
def yes_three(*, x: list[int]):
# FURB113
x.append(1)
x.append(2)
def yes_four(x: list[int], /):
# FURB113
x.append(1)
x.append(2)
def yes_five(x: list[int], y: list[int]):
# FURB113
x.append(1)
x.append(2)
y.append(1)
x.append(3)
def yes_six(x: list):
# FURB113
x.append(1)
x.append(2)
# Non-errors.
nums.append(1)
pass
nums.append(2)
if True:
nums.append(1)
pass
nums.append(2)
nums.append(1)
pass
nums.append(1)
nums2.append(2)
nums.copy()
nums.copy()
c = C()
c.append(1)
c.append(2)
def not_one(x):
x.append(1)
x.append(2)
def not_two(x: C):
x.append(1)
x.append(2)
# redefining a list variable with a new type shouldn't confuse ruff.
nums2 = C()
nums2.append(1)
nums2.append(2)

View File

@@ -1,64 +0,0 @@
from typing import Dict, List
names = {"key": "value"}
nums = [1, 2, 3]
x = 42
y = "hello"
# these should match
# FURB131
del nums[:]
# FURB131
del names[:]
# FURB131
del x, nums[:]
# FURB131
del y, names[:], x
def yes_one(x: list[int]):
# FURB131
del x[:]
def yes_two(x: dict[int, str]):
# FURB131
del x[:]
def yes_three(x: List[int]):
# FURB131
del x[:]
def yes_four(x: Dict[int, str]):
# FURB131
del x[:]
# these should not
del names["key"]
del nums[0]
x = 1
del x
del nums[1:2]
del nums[:2]
del nums[1:]
del nums[::2]
def no_one(param):
del param[:]

View File

@@ -1,80 +0,0 @@
from typing import Set
from some.package.name import foo, bar
s = set()
s2 = {}
s3: set[int] = foo()
# these should match
# FURB132
if "x" in s:
s.remove("x")
# FURB132
if "x" in s2:
s2.remove("x")
# FURB132
if "x" in s3:
s3.remove("x")
var = "y"
# FURB132
if var in s:
s.remove(var)
if f"{var}:{var}" in s:
s.remove(f"{var}:{var}")
def identity(x):
return x
# TODO: FURB132
if identity("x") in s2:
s2.remove(identity("x"))
# these should not
if "x" in s:
s.remove("y")
s.discard("x")
s2 = set()
if "x" in s:
s2.remove("x")
if "x" in s:
s.remove("x")
print("removed item")
if bar() in s:
# bar() might have a side effect
s.remove(bar())
if "x" in s:
s.remove("x")
else:
print("not found")
class Container:
def remove(self, item) -> None:
return
def __contains__(self, other) -> bool:
return True
c = Container()
if "x" in c:
c.remove("x")

View File

@@ -1,14 +0,0 @@
x = [1, 2, 3]
y = [4, 5, 6]
# RUF017
sum([x, y], start=[])
sum([x, y], [])
sum([[1, 2, 3], [4, 5, 6]], start=[])
sum([[1, 2, 3], [4, 5, 6]], [])
sum([[1, 2, 3], [4, 5, 6]],
[])
# OK
sum([x, y])
sum([[1, 2, 3], [4, 5, 6]])

View File

@@ -1,11 +0,0 @@
#import os # noqa
#import os # noqa: ERA001
dictionary = {
# "key1": 123, # noqa: ERA001
# "key2": 456, # noqa
# "key3": 789,
}
#import os # noqa: E501

View File

@@ -52,7 +52,3 @@ def good(a: int):
def another_good(a):
if a % 2 == 0:
raise GoodArgCantBeEven(a)
def another_good():
raise NotImplementedError("This is acceptable too")

View File

@@ -4,8 +4,8 @@ use anyhow::{bail, Result};
use libcst_native::{
Codegen, CodegenState, ImportNames, ParenthesizableWhitespace, SmallStatement, Statement,
};
use ruff_python_ast::{Ranged, Stmt};
use ruff_python_ast::Stmt;
use ruff_python_codegen::Stylist;
use ruff_source_file::Locator;
@@ -38,7 +38,7 @@ pub(crate) fn remove_imports<'a>(
locator: &Locator,
stylist: &Stylist,
) -> Result<Option<String>> {
let module_text = locator.slice(stmt);
let module_text = locator.slice(stmt.range());
let mut tree = match_statement(module_text)?;
let Statement::Simple(body) = &mut tree else {
@@ -117,7 +117,7 @@ pub(crate) fn retain_imports(
locator: &Locator,
stylist: &Stylist,
) -> Result<String> {
let module_text = locator.slice(stmt);
let module_text = locator.slice(stmt.range());
let mut tree = match_statement(module_text)?;
let Statement::Simple(body) = &mut tree else {

View File

@@ -1,16 +1,17 @@
//! Interface for generating autofix edits from higher-level actions (e.g., "remove an argument").
use anyhow::{Context, Result};
use anyhow::{bail, Result};
use ruff_diagnostics::Edit;
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, Keyword, Stmt};
use ruff_python_ast::{
self as ast, Arguments, ExceptHandler, Expr, Keyword, PySourceType, Ranged, Stmt,
};
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_trivia::{
has_leading_content, is_python_whitespace, PythonWhitespace, SimpleTokenKind, SimpleTokenizer,
};
use ruff_python_parser::{lexer, AsMode};
use ruff_python_trivia::{has_leading_content, is_python_whitespace, PythonWhitespace};
use ruff_source_file::{Locator, NewlineWithTrailingNewline};
use ruff_text_size::{Ranged, TextLen, TextSize};
use ruff_text_size::{TextLen, TextRange, TextSize};
use crate::autofix::codemods;
@@ -47,7 +48,7 @@ pub(crate) fn delete_stmt(
} else if has_leading_content(stmt.start(), locator) {
Edit::range_deletion(stmt.range())
} else if let Some(start) = indexer.preceded_by_continuations(stmt.start(), locator) {
Edit::deletion(start, stmt.end())
Edit::range_deletion(TextRange::new(start, stmt.end()))
} else {
let range = locator.full_lines_range(stmt.range());
Edit::range_deletion(range)
@@ -88,52 +89,83 @@ pub(crate) fn remove_argument<T: Ranged>(
argument: &T,
arguments: &Arguments,
parentheses: Parentheses,
source: &str,
locator: &Locator,
source_type: PySourceType,
) -> Result<Edit> {
// Partition into arguments before and after the argument to remove.
let (before, after): (Vec<_>, Vec<_>) = arguments
.args
.iter()
.map(Expr::range)
.chain(arguments.keywords.iter().map(Keyword::range))
.filter(|range| argument.range() != *range)
.partition(|range| range.start() < argument.start());
// TODO(sbrugman): Preserve trailing comments.
if arguments.keywords.len() + arguments.args.len() > 1 {
let mut fix_start = None;
let mut fix_end = None;
if !after.is_empty() {
// Case 1: argument or keyword is _not_ the last node, so delete from the start of the
// argument to the end of the subsequent comma.
let mut tokenizer = SimpleTokenizer::starts_at(argument.end(), source);
if arguments
.args
.iter()
.map(Expr::start)
.chain(arguments.keywords.iter().map(Keyword::start))
.any(|location| location > argument.start())
{
// Case 1: argument or keyword is _not_ the last node, so delete from the start of the
// argument to the end of the subsequent comma.
let mut seen_comma = false;
for (tok, range) in lexer::lex_starts_at(
locator.slice(arguments.range()),
source_type.as_mode(),
arguments.start(),
)
.flatten()
{
if seen_comma {
if tok.is_non_logical_newline() {
// Also delete any non-logical newlines after the comma.
continue;
}
fix_end = Some(if tok.is_newline() {
range.end()
} else {
range.start()
});
break;
}
if range.start() == argument.start() {
fix_start = Some(range.start());
}
if fix_start.is_some() && tok.is_comma() {
seen_comma = true;
}
}
} else {
// Case 2: argument or keyword is the last node, so delete from the start of the
// previous comma to the end of the argument.
for (tok, range) in lexer::lex_starts_at(
locator.slice(arguments.range()),
source_type.as_mode(),
arguments.start(),
)
.flatten()
{
if range.start() == argument.start() {
fix_end = Some(argument.end());
break;
}
if tok.is_comma() {
fix_start = Some(range.start());
}
}
}
// Find the trailing comma.
tokenizer
.find(|token| token.kind == SimpleTokenKind::Comma)
.context("Unable to find trailing comma")?;
// Find the next non-whitespace token.
let next = tokenizer
.find(|token| {
token.kind != SimpleTokenKind::Whitespace && token.kind != SimpleTokenKind::Newline
})
.context("Unable to find next token")?;
Ok(Edit::deletion(argument.start(), next.start()))
} else if let Some(previous) = before.iter().map(Ranged::end).max() {
// Case 2: argument or keyword is the last node, so delete from the start of the
// previous comma to the end of the argument.
let mut tokenizer = SimpleTokenizer::starts_at(previous, source);
// Find the trailing comma.
let comma = tokenizer
.find(|token| token.kind == SimpleTokenKind::Comma)
.context("Unable to find trailing comma")?;
Ok(Edit::deletion(comma.start(), argument.end()))
match (fix_start, fix_end) {
(Some(start), Some(end)) => Ok(Edit::deletion(start, end)),
_ => {
bail!("No fix could be constructed")
}
}
} else {
// Case 3: argument or keyword is the only node, so delete the arguments (but preserve
// parentheses, if needed).
// Only one argument; remove it (but preserve parentheses, if needed).
Ok(match parentheses {
Parentheses::Remove => Edit::range_deletion(arguments.range()),
Parentheses::Preserve => Edit::range_replacement("()".to_string(), arguments.range()),
Parentheses::Remove => Edit::deletion(arguments.start(), arguments.end()),
Parentheses::Preserve => {
Edit::replacement("()".to_string(), arguments.start(), arguments.end())
}
})
}
}
@@ -177,7 +209,14 @@ fn is_lone_child(child: &Stmt, parent: &Stmt) -> bool {
handlers,
orelse,
finalbody,
..
range: _,
})
| Stmt::TryStar(ast::StmtTryStar {
body,
handlers,
orelse,
finalbody,
range: _,
}) => {
if is_only(body, child)
|| is_only(orelse, child)
@@ -225,25 +264,25 @@ fn trailing_semicolon(offset: TextSize, locator: &Locator) -> Option<TextSize> {
fn next_stmt_break(semicolon: TextSize, locator: &Locator) -> TextSize {
let start_location = semicolon + TextSize::from(1);
for line in
NewlineWithTrailingNewline::with_offset(locator.after(start_location), start_location)
{
let contents = &locator.contents()[usize::from(start_location)..];
for line in NewlineWithTrailingNewline::from(contents) {
let trimmed = line.trim_whitespace();
// Skip past any continuations.
if trimmed.starts_with('\\') {
continue;
}
return if trimmed.is_empty() {
// If the line is empty, then despite the previous statement ending in a
// semicolon, we know that it's not a multi-statement line.
line.start()
} else {
// Otherwise, find the start of the next statement. (Or, anything that isn't
// whitespace.)
let relative_offset = line.find(|c: char| !is_python_whitespace(c)).unwrap();
line.start() + TextSize::try_from(relative_offset).unwrap()
};
return start_location
+ if trimmed.is_empty() {
// If the line is empty, then despite the previous statement ending in a
// semicolon, we know that it's not a multi-statement line.
line.start()
} else {
// Otherwise, find the start of the next statement. (Or, anything that isn't
// whitespace.)
let relative_offset = line.find(|c: char| !is_python_whitespace(c)).unwrap();
line.start() + TextSize::try_from(relative_offset).unwrap()
};
}
locator.line_end(start_location)
@@ -253,9 +292,10 @@ fn next_stmt_break(semicolon: TextSize, locator: &Locator) -> TextSize {
mod tests {
use anyhow::Result;
use ruff_python_ast::Ranged;
use ruff_python_parser::parse_suite;
use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextSize};
use ruff_text_size::TextSize;
use crate::autofix::edits::{next_stmt_break, trailing_semicolon};

View File

@@ -1,7 +1,7 @@
use itertools::Itertools;
use std::collections::BTreeSet;
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use ruff_text_size::{TextLen, TextRange, TextSize};
use rustc_hash::{FxHashMap, FxHashSet};
use ruff_diagnostics::{Diagnostic, Edit, Fix, IsolationLevel};
@@ -13,7 +13,6 @@ use crate::registry::{AsRule, Rule};
pub(crate) mod codemods;
pub(crate) mod edits;
pub(crate) mod snippet;
pub(crate) mod source_map;
pub(crate) struct FixResult {
@@ -138,9 +137,11 @@ fn cmp_fix(rule1: Rule, rule2: Rule, fix1: &Fix, fix2: &Fix) -> std::cmp::Orderi
#[cfg(test)]
mod tests {
use ruff_text_size::{Ranged, TextSize};
use ruff_text_size::TextSize;
use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_diagnostics::Diagnostic;
use ruff_diagnostics::Edit;
use ruff_diagnostics::Fix;
use ruff_source_file::Locator;
use crate::autofix::source_map::SourceMarker;

View File

@@ -1,40 +0,0 @@
use unicode_width::UnicodeWidthStr;
/// A snippet of source code for user-facing display, as in a diagnostic.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SourceCodeSnippet(String);
impl SourceCodeSnippet {
pub(crate) fn new(source_code: String) -> Self {
Self(source_code)
}
pub(crate) fn from_str(source_code: &str) -> Self {
Self(source_code.to_string())
}
/// Return the full snippet for user-facing display, or `None` if the snippet should be
/// truncated.
pub(crate) fn full_display(&self) -> Option<&str> {
if Self::should_truncate(&self.0) {
None
} else {
Some(&self.0)
}
}
/// Return a truncated snippet for user-facing display.
pub(crate) fn truncated_display(&self) -> &str {
if Self::should_truncate(&self.0) {
"..."
} else {
&self.0
}
}
/// Returns `true` if the source code should be truncated when included in a user-facing
/// diagnostic.
fn should_truncate(source_code: &str) -> bool {
source_code.width() > 50 || source_code.contains(['\r', '\n'])
}
}

View File

@@ -1,4 +1,4 @@
use ruff_text_size::{Ranged, TextSize};
use ruff_text_size::TextSize;
use ruff_diagnostics::Edit;

View File

@@ -1,5 +1,4 @@
use ruff_diagnostics::{Diagnostic, Fix};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::codes::Rule;
@@ -17,20 +16,14 @@ pub(crate) fn bindings(checker: &mut Checker) {
return;
}
for binding in &*checker.semantic.bindings {
for binding in checker.semantic.bindings.iter() {
if checker.enabled(Rule::UnusedVariable) {
if binding.kind.is_bound_exception()
&& !binding.is_used()
&& !checker
.settings
.dummy_variable_rgx
.is_match(binding.name(checker.locator))
{
if binding.kind.is_bound_exception() && !binding.is_used() {
let mut diagnostic = Diagnostic::new(
pyflakes::rules::UnusedVariable {
name: binding.name(checker.locator).to_string(),
},
binding.range(),
binding.range,
);
if checker.patch(Rule::UnusedVariable) {
diagnostic.try_set_fix(|| {

View File

@@ -7,6 +7,10 @@ use crate::rules::flake8_simplify;
/// Run lint rules over a [`Comprehension`] syntax nodes.
pub(crate) fn comprehension(comprehension: &Comprehension, checker: &mut Checker) {
if checker.enabled(Rule::InDictKeys) {
flake8_simplify::rules::key_in_dict_comprehension(checker, comprehension);
flake8_simplify::rules::key_in_dict_for(
checker,
&comprehension.target,
&comprehension.iter,
);
}
}

View File

@@ -1,8 +1,8 @@
use ruff_python_ast::Stmt;
use ruff_python_ast::{self as ast, Stmt};
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::rules::{flake8_bugbear, perflint, pyupgrade};
use crate::rules::{flake8_bugbear, perflint};
/// Run lint rules over all deferred for-loops in the [`SemanticModel`].
pub(crate) fn deferred_for_loops(checker: &mut Checker) {
@@ -11,18 +11,18 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
for snapshot in for_loops {
checker.semantic.restore(snapshot);
let Stmt::For(stmt_for) = checker.semantic.current_statement() else {
let Stmt::For(ast::StmtFor {
target, iter, body, ..
}) = checker.semantic.current_statement()
else {
unreachable!("Expected Stmt::For");
};
if checker.enabled(Rule::UnusedLoopControlVariable) {
flake8_bugbear::rules::unused_loop_control_variable(checker, stmt_for);
flake8_bugbear::rules::unused_loop_control_variable(checker, target, body);
}
if checker.enabled(Rule::IncorrectDictIterator) {
perflint::rules::incorrect_dict_iterator(checker, stmt_for);
}
if checker.enabled(Rule::YieldInForLoop) {
pyupgrade::rules::yield_in_for_loop(checker, stmt_for);
perflint::rules::incorrect_dict_iterator(checker, target, iter);
}
}
}

View File

@@ -1,7 +1,6 @@
use ruff_diagnostics::Diagnostic;
use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::analyze::{branch_detection, visibility};
use ruff_python_semantic::{Binding, BindingKind, ScopeKind};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::codes::Rule;
@@ -30,7 +29,6 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
Rule::UnusedPrivateTypedDict,
Rule::UnusedStaticMethodArgument,
Rule::UnusedVariable,
Rule::NoSelfUse,
]) {
return;
}
@@ -83,7 +81,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
pylint::rules::GlobalVariableNotAssigned {
name: (*name).to_string(),
},
binding.range(),
binding.range,
));
}
}
@@ -113,21 +111,25 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
// If the bindings are in different forks, abort.
if shadowed.source.map_or(true, |left| {
binding.source.map_or(true, |right| {
checker.semantic.different_branches(left, right)
branch_detection::different_forks(
left,
right,
checker.semantic.statements(),
)
})
}) {
continue;
}
#[allow(deprecated)]
let line = checker.locator.compute_line_index(shadowed.start());
let line = checker.locator.compute_line_index(shadowed.range.start());
checker.diagnostics.push(Diagnostic::new(
pyflakes::rules::ImportShadowedByLoopVar {
name: name.to_string(),
line,
},
binding.range(),
binding.range,
));
}
}
@@ -169,7 +171,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
continue;
}
let Some(node_id) = shadowed.source else {
let Some(statement_id) = shadowed.source else {
continue;
};
@@ -177,7 +179,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
if shadowed.kind.is_function_definition() {
if checker
.semantic
.statement(node_id)
.statement(statement_id)
.as_function_def_stmt()
.is_some_and(|function| {
visibility::is_overload(
@@ -205,20 +207,24 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
// If the bindings are in different forks, abort.
if shadowed.source.map_or(true, |left| {
binding.source.map_or(true, |right| {
checker.semantic.different_branches(left, right)
branch_detection::different_forks(
left,
right,
checker.semantic.statements(),
)
})
}) {
continue;
}
#[allow(deprecated)]
let line = checker.locator.compute_line_index(shadowed.start());
let line = checker.locator.compute_line_index(shadowed.range.start());
let mut diagnostic = Diagnostic::new(
pyflakes::rules::RedefinedWhileUnused {
name: (*name).to_string(),
line,
},
binding.range(),
binding.range,
);
if let Some(range) = binding.parent_range(&checker.semantic) {
diagnostic.set_parent(range.start());
@@ -303,12 +309,6 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
pyflakes::rules::unused_import(checker, scope, &mut diagnostics);
}
}
if scope.kind.is_function() {
if checker.enabled(Rule::NoSelfUse) {
pylint::rules::no_self_use(checker, scope, &mut diagnostics);
}
}
}
checker.diagnostics.extend(diagnostics);
}

View File

@@ -1,5 +1,6 @@
use ruff_python_ast::str::raw_contents_range;
use ruff_text_size::{Ranged, TextRange};
use ruff_python_ast::Ranged;
use ruff_text_size::TextRange;
use ruff_python_semantic::{BindingKind, ContextualizedDefinition, Export};
@@ -163,14 +164,14 @@ pub(crate) fn definitions(checker: &mut Checker) {
continue;
};
let contents = checker.locator().slice(expr);
let contents = checker.locator.slice(expr.range());
let indentation = checker.locator().slice(TextRange::new(
let indentation = checker.locator.slice(TextRange::new(
checker.locator.line_start(expr.start()),
expr.start(),
));
if pydocstyle::helpers::should_ignore_docstring(expr) {
if pydocstyle::helpers::should_ignore_docstring(contents) {
#[allow(deprecated)]
let location = checker.locator.compute_source_location(expr.start());
warn_user!(

View File

@@ -1,5 +1,4 @@
use ruff_python_ast::{self as ast, ExceptHandler};
use ruff_text_size::Ranged;
use ruff_python_ast::{self as ast, ExceptHandler, Ranged};
use crate::checkers::ast::Checker;
use crate::registry::Rule;

View File

@@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, ExprContext, Operator};
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, ExprContext, Operator, Ranged};
use ruff_python_literal::cformat::{CFormatError, CFormatErrorType};
use ruff_diagnostics::Diagnostic;
@@ -6,7 +6,6 @@ use ruff_diagnostics::Diagnostic;
use ruff_python_ast::types::Node;
use ruff_python_semantic::analyze::typing;
use ruff_python_semantic::ScopeKind;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::Rule;
@@ -262,7 +261,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pylint::rules::load_before_global_declaration(checker, id, expr);
}
}
Expr::Attribute(attribute) => {
Expr::Attribute(ast::ExprAttribute { attr, value, .. }) => {
// Ex) typing.List[...]
if checker.any_enabled(&[
Rule::FutureRewritableTypeAnnotation,
@@ -324,7 +323,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::CollectionsNamedTuple) {
flake8_pyi::rules::collections_named_tuple(checker, expr);
}
pandas_vet::rules::attr(checker, attribute);
pandas_vet::rules::attr(checker, attr, value, expr);
}
Expr::Call(
call @ ast::ExprCall {
@@ -382,34 +381,34 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
Ok(summary) => {
if checker.enabled(Rule::StringDotFormatExtraNamedArguments) {
pyflakes::rules::string_dot_format_extra_named_arguments(
checker, call, &summary, keywords,
checker, &summary, keywords, location,
);
}
if checker
.enabled(Rule::StringDotFormatExtraPositionalArguments)
{
pyflakes::rules::string_dot_format_extra_positional_arguments(
checker, call, &summary, args,
checker, &summary, args, location,
);
}
if checker.enabled(Rule::StringDotFormatMissingArguments) {
pyflakes::rules::string_dot_format_missing_argument(
checker, call, &summary, args, keywords,
checker, &summary, args, keywords, location,
);
}
if checker.enabled(Rule::StringDotFormatMixingAutomatic) {
pyflakes::rules::string_dot_format_mixing_automatic(
checker, call, &summary,
checker, &summary, location,
);
}
if checker.enabled(Rule::FormatLiterals) {
pyupgrade::rules::format_literals(checker, call, &summary);
pyupgrade::rules::format_literals(checker, &summary, call);
}
if checker.enabled(Rule::FString) {
pyupgrade::rules::f_strings(
checker,
call,
&summary,
expr,
value,
checker.settings.line_length,
);
@@ -419,7 +418,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::BadStringFormatCharacter) {
pylint::rules::bad_string_format_character::call(
checker, val, location,
checker,
val.as_str(),
location,
);
}
}
@@ -433,7 +434,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pyupgrade::rules::deprecated_unittest_alias(checker, func);
}
if checker.enabled(Rule::SuperCallWithParameters) {
pyupgrade::rules::super_call_with_parameters(checker, call);
pyupgrade::rules::super_call_with_parameters(checker, expr, func, args);
}
if checker.enabled(Rule::UnnecessaryEncodeUTF8) {
pyupgrade::rules::unnecessary_encode_utf8(checker, call);
@@ -442,11 +443,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pyupgrade::rules::redundant_open_modes(checker, call);
}
if checker.enabled(Rule::NativeLiterals) {
pyupgrade::rules::native_literals(
checker,
call,
checker.semantic().current_expression_parent(),
);
pyupgrade::rules::native_literals(checker, expr, func, args, keywords);
}
if checker.enabled(Rule::OpenAlias) {
pyupgrade::rules::open_alias(checker, expr, func);
@@ -536,9 +533,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::UnnecessaryDictKwargs) {
flake8_pie::rules::unnecessary_dict_kwargs(checker, expr, keywords);
}
if checker.enabled(Rule::UnnecessaryRangeStart) {
flake8_pie::rules::unnecessary_range_start(checker, call);
}
if checker.enabled(Rule::ExecBuiltin) {
flake8_bandit::rules::exec_used(checker, func);
}
@@ -681,8 +675,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
checker, expr, func, args, keywords,
);
}
if checker.enabled(Rule::BooleanPositionalValueInCall) {
flake8_boolean_trap::rules::boolean_positional_value_in_call(checker, args, func);
if checker.enabled(Rule::BooleanPositionalValueInFunctionCall) {
flake8_boolean_trap::rules::check_boolean_positional_value_in_function_call(
checker, args, func,
);
}
if checker.enabled(Rule::Debugger) {
flake8_debugger::rules::debugger_call(checker, expr, func);
@@ -764,19 +760,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::PytestUnittestRaisesAssertion) {
if let Some(diagnostic) =
flake8_pytest_style::rules::unittest_raises_assertion(checker, call)
{
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::SubprocessPopenPreexecFn) {
pylint::rules::subprocess_popen_preexec_fn(checker, call);
}
if checker.enabled(Rule::SubprocessRunWithoutCheck) {
pylint::rules::subprocess_run_without_check(checker, call);
}
if checker.any_enabled(&[
Rule::PytestRaisesWithoutException,
Rule::PytestRaisesTooBroad,
@@ -881,9 +867,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::UnsupportedMethodCallOnAll) {
flake8_pyi::rules::unsupported_method_call_on_all(checker, func);
}
if checker.enabled(Rule::QuadraticListSummation) {
ruff::rules::quadratic_list_summation(checker, call);
}
}
Expr::Dict(ast::ExprDict {
keys,
@@ -932,7 +915,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pylint::rules::await_outside_async(checker, expr);
}
}
Expr::FString(ast::ExprFString { values, .. }) => {
Expr::FString(ast::ExprFString { values, range: _ }) => {
if checker.enabled(Rule::FStringMissingPlaceholders) {
pyflakes::rules::f_string_missing_placeholders(expr, values, checker);
}
@@ -959,7 +942,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
range: _,
}) => {
if let Expr::Constant(ast::ExprConstant {
value: Constant::Str(ast::StringConstant { value, .. }),
value: Constant::Str(value),
..
}) = left.as_ref()
{
@@ -1039,7 +1022,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
}
if checker.enabled(Rule::PrintfStringFormatting) {
pyupgrade::rules::printf_string_formatting(checker, expr, right);
pyupgrade::rules::printf_string_formatting(
checker,
expr,
right,
checker.locator,
);
}
if checker.enabled(Rule::BadStringFormatCharacter) {
pylint::rules::bad_string_format_character::percent(checker, expr);
@@ -1107,15 +1095,22 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
}
}
Expr::UnaryOp(
unary_op @ ast::ExprUnaryOp {
op,
operand,
range: _,
},
) => {
if checker.any_enabled(&[Rule::NotInTest, Rule::NotIsTest]) {
pycodestyle::rules::not_tests(checker, unary_op);
Expr::UnaryOp(ast::ExprUnaryOp {
op,
operand,
range: _,
}) => {
let check_not_in = checker.enabled(Rule::NotInTest);
let check_not_is = checker.enabled(Rule::NotIsTest);
if check_not_in || check_not_is {
pycodestyle::rules::not_tests(
checker,
expr,
*op,
operand,
check_not_in,
check_not_is,
);
}
if checker.enabled(Rule::UnaryPrefixIncrementDecrement) {
flake8_bugbear::rules::unary_prefix_increment_decrement(
@@ -1140,8 +1135,18 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
range: _,
},
) => {
if checker.any_enabled(&[Rule::NoneComparison, Rule::TrueFalseComparison]) {
pycodestyle::rules::literal_comparisons(checker, compare);
let check_none_comparisons = checker.enabled(Rule::NoneComparison);
let check_true_false_comparisons = checker.enabled(Rule::TrueFalseComparison);
if check_none_comparisons || check_true_false_comparisons {
pycodestyle::rules::literal_comparisons(
checker,
expr,
left,
ops,
comparators,
check_none_comparisons,
check_true_false_comparisons,
);
}
if checker.enabled(Rule::IsLiteral) {
pyflakes::rules::invalid_literal_comparison(checker, left, ops, comparators, expr);
@@ -1178,7 +1183,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pylint::rules::magic_value_comparison(checker, left, comparators);
}
if checker.enabled(Rule::InDictKeys) {
flake8_simplify::rules::key_in_dict_compare(checker, compare);
flake8_simplify::rules::key_in_dict_compare(checker, expr, left, ops, comparators);
}
if checker.enabled(Rule::YodaConditions) {
flake8_simplify::rules::yoda_conditions(checker, expr, left, ops, comparators);

View File

@@ -1,5 +1,4 @@
use ruff_python_ast::Parameter;
use ruff_text_size::Ranged;
use ruff_python_ast::{Parameter, Ranged};
use crate::checkers::ast::Checker;
use crate::codes::Rule;

View File

@@ -6,6 +6,9 @@ use crate::rules::{flake8_bugbear, flake8_pyi, ruff};
/// Run lint rules over a [`Parameters`] syntax node.
pub(crate) fn parameters(parameters: &Parameters, checker: &mut Checker) {
if checker.enabled(Rule::MutableArgumentDefault) {
flake8_bugbear::rules::mutable_argument_default(checker, parameters);
}
if checker.enabled(Rule::FunctionCallInDefaultArgument) {
flake8_bugbear::rules::function_call_in_argument_default(checker, parameters);
}

View File

@@ -1,10 +1,11 @@
use ruff_python_ast::{self as ast, Expr, Ranged, Stmt};
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::helpers;
use ruff_python_ast::types::Node;
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_semantic::analyze::typing;
use ruff_python_semantic::ScopeKind;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::Rule;
@@ -13,7 +14,7 @@ use crate::rules::{
flake8_django, flake8_errmsg, flake8_import_conventions, flake8_pie, flake8_pyi,
flake8_pytest_style, flake8_raise, flake8_return, flake8_simplify, flake8_slots,
flake8_tidy_imports, flake8_type_checking, mccabe, pandas_vet, pep8_naming, perflint,
pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff, tryceratops,
pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
};
use crate::settings::types::PythonVersion;
@@ -68,18 +69,16 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
}
Stmt::FunctionDef(
function_def @ ast::StmtFunctionDef {
is_async,
name,
decorator_list,
returns,
parameters,
body,
type_params,
range: _,
},
) => {
Stmt::FunctionDef(ast::StmtFunctionDef {
is_async,
name,
decorator_list,
returns,
parameters,
body,
type_params,
range: _,
}) => {
if checker.enabled(Rule::DjangoNonLeadingReceiverDecorator) {
flake8_django::rules::non_leading_receiver_decorator(checker, decorator_list);
}
@@ -205,9 +204,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::CachedInstanceMethod) {
flake8_bugbear::rules::cached_instance_method(checker, decorator_list);
}
if checker.enabled(Rule::MutableArgumentDefault) {
flake8_bugbear::rules::mutable_argument_default(checker, function_def);
}
if checker.any_enabled(&[
Rule::UnnecessaryReturnNone,
Rule::ImplicitReturnValue,
@@ -299,7 +295,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.any_enabled(&[
Rule::PytestParametrizeNamesWrongType,
Rule::PytestParametrizeValuesWrongType,
Rule::PytestDuplicateParametrizeTestCases,
]) {
flake8_pytest_style::rules::parametrize(checker, decorator_list);
}
@@ -309,16 +304,16 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
]) {
flake8_pytest_style::rules::marks(checker, decorator_list);
}
if checker.enabled(Rule::BooleanTypeHintPositionalArgument) {
flake8_boolean_trap::rules::boolean_type_hint_positional_argument(
if checker.enabled(Rule::BooleanPositionalArgInFunctionDefinition) {
flake8_boolean_trap::rules::check_positional_boolean_in_def(
checker,
name,
decorator_list,
parameters,
);
}
if checker.enabled(Rule::BooleanDefaultValuePositionalArgument) {
flake8_boolean_trap::rules::boolean_default_value_positional_argument(
if checker.enabled(Rule::BooleanDefaultValueInFunctionDefinition) {
flake8_boolean_trap::rules::check_boolean_default_value_in_function_definition(
checker,
name,
decorator_list,
@@ -337,6 +332,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::FStringDocstring) {
flake8_bugbear::rules::f_string_docstring(checker, body);
}
if checker.enabled(Rule::YieldInForLoop) {
pyupgrade::rules::yield_in_for_loop(checker, stmt);
}
if let ScopeKind::Class(class_def) = checker.semantic.current_scope().kind {
if checker.enabled(Rule::BuiltinAttributeShadowing) {
flake8_builtins::rules::builtin_method_shadowing(
@@ -463,17 +461,17 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
flake8_pyi::rules::pass_statement_stub_body(checker, body);
}
if checker.enabled(Rule::PassInClassBody) {
flake8_pyi::rules::pass_in_class_body(checker, class_def);
flake8_pyi::rules::pass_in_class_body(checker, stmt, body);
}
}
if checker.enabled(Rule::EllipsisInNonEmptyClassBody) {
flake8_pyi::rules::ellipsis_in_non_empty_class_body(checker, body);
flake8_pyi::rules::ellipsis_in_non_empty_class_body(checker, stmt, body);
}
if checker.enabled(Rule::PytestIncorrectMarkParenthesesStyle) {
flake8_pytest_style::rules::marks(checker, decorator_list);
}
if checker.enabled(Rule::DuplicateClassFieldDefinition) {
flake8_pie::rules::duplicate_class_field_definition(checker, body);
flake8_pie::rules::duplicate_class_field_definition(checker, stmt, body);
}
if checker.enabled(Rule::NonUniqueEnums) {
flake8_pie::rules::non_unique_enums(checker, stmt, body);
@@ -508,16 +506,17 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::SingleStringSlots) {
pylint::rules::single_string_slots(checker, class_def);
}
if checker.enabled(Rule::BadDunderMethodName) {
pylint::rules::bad_dunder_method_name(checker, body);
}
}
Stmt::Import(ast::StmtImport { names, range: _ }) => {
if checker.enabled(Rule::MultipleImportsOnOneLine) {
pycodestyle::rules::multiple_imports_on_one_line(checker, stmt, names);
}
if checker.enabled(Rule::ModuleImportNotAtTopOfFile) {
pycodestyle::rules::module_import_not_at_top_of_file(checker, stmt);
pycodestyle::rules::module_import_not_at_top_of_file(
checker,
stmt,
checker.locator,
);
}
if checker.enabled(Rule::GlobalStatement) {
for name in names {
@@ -553,29 +552,12 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
if checker.enabled(Rule::BannedApi) {
flake8_tidy_imports::rules::banned_api(
flake8_tidy_imports::rules::name_or_parent_is_banned(
checker,
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
flake8_tidy_imports::matchers::MatchNameOrParent {
module: &alias.name,
},
),
&alias,
&alias.name,
alias,
);
}
if checker.enabled(Rule::BannedModuleLevelImports) {
flake8_tidy_imports::rules::banned_module_level_imports(
checker,
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
flake8_tidy_imports::matchers::MatchNameOrParent {
module: &alias.name,
},
),
&alias,
);
}
if !checker.source_type.is_stub() {
if checker.enabled(Rule::UselessImportAlias) {
pylint::rules::useless_import_alias(checker, alias);
@@ -690,7 +672,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
let module = module.as_deref();
let level = level.map(|level| level.to_u32());
if checker.enabled(Rule::ModuleImportNotAtTopOfFile) {
pycodestyle::rules::module_import_not_at_top_of_file(checker, stmt);
pycodestyle::rules::module_import_not_at_top_of_file(
checker,
stmt,
checker.locator,
);
}
if checker.enabled(Rule::GlobalStatement) {
for name in names {
@@ -726,56 +712,16 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if let Some(module) =
helpers::resolve_imported_module_path(level, module, checker.module_path)
{
flake8_tidy_imports::rules::banned_api(
checker,
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
flake8_tidy_imports::matchers::MatchNameOrParent { module: &module },
),
&stmt,
);
flake8_tidy_imports::rules::name_or_parent_is_banned(checker, &module, stmt);
for alias in names {
if &alias.name == "*" {
continue;
}
flake8_tidy_imports::rules::banned_api(
flake8_tidy_imports::rules::name_is_banned(
checker,
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchName(
flake8_tidy_imports::matchers::MatchName {
module: &module,
member: &alias.name,
},
),
&alias,
);
}
}
}
if checker.enabled(Rule::BannedModuleLevelImports) {
if let Some(module) =
helpers::resolve_imported_module_path(level, module, checker.module_path)
{
flake8_tidy_imports::rules::banned_module_level_imports(
checker,
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
flake8_tidy_imports::matchers::MatchNameOrParent { module: &module },
),
&stmt,
);
for alias in names {
if &alias.name == "*" {
continue;
}
flake8_tidy_imports::rules::banned_module_level_imports(
checker,
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchName(
flake8_tidy_imports::matchers::MatchName {
module: &module,
member: &alias.name,
},
),
&alias,
format!("{module}.{}", alias.name),
alias,
);
}
}
@@ -1056,9 +1002,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::CheckAndRemoveFromSet) {
refurb::rules::check_and_remove_from_set(checker, if_);
}
if checker.source_type.is_stub() {
if checker.any_enabled(&[
Rule::UnrecognizedVersionInfoCheck,
@@ -1141,7 +1084,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
pygrep_hooks::rules::non_existent_mock_method(checker, test);
}
}
Stmt::With(with_stmt @ ast::StmtWith { items, body, .. }) => {
Stmt::With(with_ @ ast::StmtWith { items, body, .. }) => {
if checker.enabled(Rule::AssertRaisesException) {
flake8_bugbear::rules::assert_raises_exception(checker, items);
}
@@ -1151,7 +1094,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::MultipleWithStatements) {
flake8_simplify::rules::multiple_with_statements(
checker,
with_stmt,
with_,
checker.semantic.current_statement_parent(),
);
}
@@ -1170,21 +1113,15 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
perflint::rules::try_except_in_loop(checker, body);
}
}
Stmt::For(
for_stmt @ ast::StmtFor {
target,
body,
iter,
orelse,
is_async,
..
},
) => {
if checker.any_enabled(&[
Rule::UnusedLoopControlVariable,
Rule::IncorrectDictIterator,
Rule::YieldInForLoop,
]) {
Stmt::For(ast::StmtFor {
target,
body,
iter,
orelse,
..
}) => {
if checker.any_enabled(&[Rule::UnusedLoopControlVariable, Rule::IncorrectDictIterator])
{
checker.deferred.for_loops.push(checker.semantic.snapshot());
}
if checker.enabled(Rule::LoopVariableOverridesIterator) {
@@ -1205,6 +1142,17 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::IterationOverSet) {
pylint::rules::iteration_over_set(checker, iter);
}
if stmt.is_for_stmt() {
if checker.enabled(Rule::ReimplementedBuiltin) {
flake8_simplify::rules::convert_for_loop_to_any_all(checker, stmt);
}
if checker.enabled(Rule::InDictKeys) {
flake8_simplify::rules::key_in_dict_for(checker, target, iter);
}
if checker.enabled(Rule::TryExceptInLoop) {
perflint::rules::try_except_in_loop(checker, body);
}
}
if checker.enabled(Rule::ManualListComprehension) {
perflint::rules::manual_list_comprehension(checker, target, body);
}
@@ -1214,24 +1162,20 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::UnnecessaryListCast) {
perflint::rules::unnecessary_list_cast(checker, iter);
}
if !is_async {
if checker.enabled(Rule::ReimplementedBuiltin) {
flake8_simplify::rules::convert_for_loop_to_any_all(checker, stmt);
}
if checker.enabled(Rule::InDictKeys) {
flake8_simplify::rules::key_in_dict_for(checker, for_stmt);
}
if checker.enabled(Rule::TryExceptInLoop) {
perflint::rules::try_except_in_loop(checker, body);
}
}
}
Stmt::Try(ast::StmtTry {
body,
handlers,
orelse,
finalbody,
..
range: _,
})
| Stmt::TryStar(ast::StmtTryStar {
body,
handlers,
orelse,
finalbody,
range: _,
}) => {
if checker.enabled(Rule::JumpStatementInFinally) {
flake8_bugbear::rules::jump_statement_in_finally(checker, finalbody);
@@ -1462,7 +1406,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
}
Stmt::Delete(delete @ ast::StmtDelete { targets, range: _ }) => {
Stmt::Delete(ast::StmtDelete { targets, range: _ }) => {
if checker.enabled(Rule::GlobalStatement) {
for target in targets {
if let Expr::Name(ast::ExprName { id, .. }) = target {
@@ -1470,9 +1414,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
}
if checker.enabled(Rule::DeleteFullSlice) {
refurb::rules::delete_full_slice(checker, delete);
}
}
Stmt::Expr(ast::StmtExpr { value, range: _ }) => {
if checker.enabled(Rule::UselessComparison) {
@@ -1490,9 +1431,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::AsyncioDanglingTask) {
ruff::rules::asyncio_dangling_task(checker, value);
}
if checker.enabled(Rule::RepeatedAppend) {
refurb::rules::repeated_append(checker, stmt);
}
}
_ => {}
}

View File

@@ -32,10 +32,10 @@ use itertools::Itertools;
use log::error;
use ruff_python_ast::{
self as ast, Arguments, Comprehension, Constant, ElifElseClause, ExceptHandler, Expr,
ExprContext, Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt,
ExprContext, Keyword, Parameter, ParameterWithDefault, Parameters, Pattern, Ranged, Stmt,
Suite, UnaryOp,
};
use ruff_text_size::{Ranged, TextRange, TextSize};
use ruff_text_size::{TextRange, TextSize};
use ruff_diagnostics::{Diagnostic, IsolationLevel};
use ruff_python_ast::all::{extract_all_names, DunderAllFlags};
@@ -52,8 +52,7 @@ use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind};
use ruff_python_semantic::analyze::{typing, visibility};
use ruff_python_semantic::{
BindingFlags, BindingId, BindingKind, Exceptions, Export, FromImport, Globals, Import, Module,
ModuleKind, NodeId, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags, StarImport,
SubmoduleImport,
ModuleKind, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags, StarImport, SubmoduleImport,
};
use ruff_python_stdlib::builtins::{BUILTINS, MAGIC_GLOBALS};
use ruff_source_file::Locator;
@@ -194,6 +193,20 @@ impl<'a> Checker<'a> {
}
}
/// Returns the [`IsolationLevel`] for fixes in the current context.
///
/// The primary use-case for fix isolation is to ensure that we don't delete all statements
/// in a given indented block, which would cause a syntax error. We therefore need to ensure
/// that we delete at most one statement per indented block per fixer pass. Fix isolation should
/// thus be applied whenever we delete a statement, but can otherwise be omitted.
pub(crate) fn isolation(&self, parent: Option<&Stmt>) -> IsolationLevel {
parent
.and_then(|stmt| self.semantic.statement_id(stmt))
.map_or(IsolationLevel::default(), |node_id| {
IsolationLevel::Group(node_id.into())
})
}
/// The [`Locator`] for the current file, which enables extraction of source code from byte
/// offsets.
pub(crate) const fn locator(&self) -> &'a Locator<'a> {
@@ -242,18 +255,6 @@ impl<'a> Checker<'a> {
pub(crate) const fn any_enabled(&self, rules: &[Rule]) -> bool {
self.settings.rules.any_enabled(rules)
}
/// Returns the [`IsolationLevel`] to isolate fixes for a given node.
///
/// The primary use-case for fix isolation is to ensure that we don't delete all statements
/// in a given indented block, which would cause a syntax error. We therefore need to ensure
/// that we delete at most one statement per indented block per fixer pass. Fix isolation should
/// thus be applied whenever we delete a statement, but can otherwise be omitted.
pub(crate) fn isolation(node_id: Option<NodeId>) -> IsolationLevel {
node_id
.map(|node_id| IsolationLevel::Group(node_id.into()))
.unwrap_or_default()
}
}
impl<'a, 'b> Visitor<'b> for Checker<'a>
@@ -262,7 +263,7 @@ where
{
fn visit_stmt(&mut self, stmt: &'b Stmt) {
// Step 0: Pre-processing
self.semantic.push_node(stmt);
self.semantic.push_statement(stmt);
// Track whether we've seen docstrings, non-imports, etc.
match stmt {
@@ -598,7 +599,14 @@ where
handlers,
orelse,
finalbody,
..
range: _,
})
| Stmt::TryStar(ast::StmtTryStar {
body,
handlers,
orelse,
finalbody,
range: _,
}) => {
let mut handled_exceptions = Exceptions::empty();
for type_ in extract_handled_exceptions(handlers) {
@@ -618,28 +626,16 @@ where
}
}
// Iterate over the `body`, then the `handlers`, then the `orelse`, then the
// `finalbody`, but treat the body and the `orelse` as a single branch for
// flow analysis purposes.
let branch = self.semantic.push_branch();
self.semantic.handled_exceptions.push(handled_exceptions);
self.visit_body(body);
self.semantic.handled_exceptions.pop();
self.semantic.pop_branch();
for except_handler in handlers {
self.semantic.push_branch();
self.visit_except_handler(except_handler);
self.semantic.pop_branch();
}
self.semantic.set_branch(branch);
self.visit_body(orelse);
self.semantic.pop_branch();
self.semantic.push_branch();
self.visit_body(finalbody);
self.semantic.pop_branch();
}
Stmt::AnnAssign(ast::StmtAnnAssign {
target,
@@ -719,7 +715,6 @@ where
) => {
self.visit_boolean_test(test);
self.semantic.push_branch();
if typing::is_type_checking_block(stmt_if, &self.semantic) {
if self.semantic.at_top_level() {
self.importer.visit_type_checking_block(stmt);
@@ -728,12 +723,9 @@ where
} else {
self.visit_body(body);
}
self.semantic.pop_branch();
for clause in elif_else_clauses {
self.semantic.push_branch();
self.visit_elif_else_clause(clause);
self.semantic.pop_branch();
}
}
_ => visitor::walk_stmt(self, stmt),
@@ -774,7 +766,7 @@ where
analyze::statement(stmt, self);
self.semantic.flags = flags_snapshot;
self.semantic.pop_node();
self.semantic.pop_statement();
}
fn visit_annotation(&mut self, expr: &'b Expr) {
@@ -810,7 +802,7 @@ where
return;
}
self.semantic.push_node(expr);
self.semantic.push_expression(expr);
// Store the flags prior to any further descent, so that we can restore them after visiting
// the node.
@@ -889,20 +881,18 @@ where
},
) => {
// Visit the default arguments, but avoid the body, which will be deferred.
if let Some(parameters) = parameters {
for ParameterWithDefault {
default,
parameter: _,
range: _,
} in parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
{
if let Some(expr) = &default {
self.visit_expr(expr);
}
for ParameterWithDefault {
default,
parameter: _,
range: _,
} in parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
{
if let Some(expr) = &default {
self.visit_expr(expr);
}
}
@@ -1230,7 +1220,7 @@ where
analyze::expression(expr, self);
self.semantic.flags = flags_snapshot;
self.semantic.pop_node();
self.semantic.pop_expression();
}
fn visit_except_handler(&mut self, except_handler: &'b ExceptHandler) {
@@ -1285,7 +1275,7 @@ where
fn visit_format_spec(&mut self, format_spec: &'b Expr) {
match format_spec {
Expr::FString(ast::ExprFString { values, .. }) => {
Expr::FString(ast::ExprFString { values, range: _ }) => {
for value in values {
self.visit_expr(value);
}
@@ -1368,17 +1358,6 @@ where
}
}
fn visit_match_case(&mut self, match_case: &'b MatchCase) {
self.visit_pattern(&match_case.pattern);
if let Some(expr) = &match_case.guard {
self.visit_expr(expr);
}
self.semantic.push_branch();
self.visit_body(&match_case.body);
self.semantic.pop_branch();
}
fn visit_type_param(&mut self, type_param: &'b ast::TypeParam) {
// Step 1: Binding
match type_param {
@@ -1862,9 +1841,7 @@ impl<'a> Checker<'a> {
range: _,
}) = expr
{
if let Some(parameters) = parameters {
self.visit_parameters(parameters);
}
self.visit_parameters(parameters);
self.visit_expr(body);
} else {
unreachable!("Expected Expr::Lambda");
@@ -1885,7 +1862,7 @@ impl<'a> Checker<'a> {
.map(|binding_id| &self.semantic.bindings[binding_id])
.filter_map(|binding| match &binding.kind {
BindingKind::Export(Export { names }) => {
Some(names.iter().map(|name| (*name, binding.range())))
Some(names.iter().map(|name| (*name, binding.range)))
}
_ => None,
})
@@ -1895,8 +1872,6 @@ impl<'a> Checker<'a> {
for (name, range) in exports {
if let Some(binding_id) = self.semantic.global_scope().get(name) {
// Mark anything referenced in `__all__` as used.
// TODO(charlie): `range` here should be the range of the name in `__all__`, not
// the range of `__all__` itself.
self.semantic.add_global_reference(binding_id, range);
} else {
if self.semantic.global_scope().uses_star_imports() {

View File

@@ -2,15 +2,16 @@
use std::borrow::Cow;
use std::path::Path;
use ruff_python_ast::{self as ast, PySourceType, Ranged, Stmt, Suite};
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::helpers::to_module_path;
use ruff_python_ast::imports::{ImportMap, ModuleImport};
use ruff_python_ast::statement_visitor::StatementVisitor;
use ruff_python_ast::{self as ast, PySourceType, Stmt, Suite};
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_source_file::Locator;
use ruff_text_size::Ranged;
use crate::directives::IsortDirectives;
use crate::registry::Rule;

View File

@@ -1,9 +1,10 @@
use ruff_python_parser::lexer::LexResult;
use ruff_text_size::TextRange;
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
use ruff_python_codegen::Stylist;
use ruff_python_parser::lexer::LexResult;
use ruff_python_parser::TokenKind;
use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextRange};
use crate::registry::{AsRule, Rule};
use crate::rules::pycodestyle::rules::logical_lines::{

View File

@@ -3,7 +3,8 @@
use std::path::Path;
use itertools::Itertools;
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use ruff_python_ast::Ranged;
use ruff_text_size::{TextLen, TextRange, TextSize};
use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_source_file::Locator;
@@ -109,8 +110,8 @@ pub(crate) fn check_noqa(
let mut diagnostic =
Diagnostic::new(UnusedNOQA { codes: None }, directive.range());
if settings.rules.should_fix(diagnostic.kind.rule()) {
diagnostic
.set_fix(Fix::suggested(delete_noqa(directive.range(), locator)));
#[allow(deprecated)]
diagnostic.set_fix_from_edit(delete_noqa(directive.range(), locator));
}
diagnostics.push(diagnostic);
}
@@ -174,12 +175,12 @@ pub(crate) fn check_noqa(
);
if settings.rules.should_fix(diagnostic.kind.rule()) {
if valid_codes.is_empty() {
diagnostic.set_fix(Fix::suggested(delete_noqa(
directive.range(),
locator,
)));
#[allow(deprecated)]
diagnostic
.set_fix_from_edit(delete_noqa(directive.range(), locator));
} else {
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
#[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
format!("# noqa: {}", valid_codes.join(", ")),
directive.range(),
)));

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